flutter_driver_test.dart 14.5 KB
Newer Older
1 2 3 4 5
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6

7 8 9 10
import 'package:flutter_driver/src/common/error.dart';
import 'package:flutter_driver/src/common/health.dart';
import 'package:flutter_driver/src/driver/driver.dart';
import 'package:flutter_driver/src/driver/timeline.dart';
11
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
12
import 'package:mockito/mockito.dart';
13
import 'package:test/test.dart';
14 15
import 'package:vm_service_client/vm_service_client.dart';

16 17 18 19
/// Magical timeout value that's different from the default.
const Duration _kTestTimeout = const Duration(milliseconds: 1234);
const String _kSerializedTestTimeout = '1234';

20
void main() {
21 22
  group('FlutterDriver.connect', () {
    List<LogRecord> log;
Ian Hickson's avatar
Ian Hickson committed
23
    StreamSubscription<LogRecord> logSub;
24 25 26
    MockVMServiceClient mockClient;
    MockVM mockVM;
    MockIsolate mockIsolate;
27
    MockPeer mockPeer;
28

29
    void expectLogContains(String message) {
Ian Hickson's avatar
Ian Hickson committed
30
      expect(log.map((LogRecord r) => '$r'), anyElement(contains(message)));
31
    }
32

33 34 35 36 37 38
    setUp(() {
      log = <LogRecord>[];
      logSub = flutterDriverLog.listen(log.add);
      mockClient = new MockVMServiceClient();
      mockVM = new MockVM();
      mockIsolate = new MockIsolate();
39
      mockPeer = new MockPeer();
40
      when(mockClient.getVM()).thenAnswer((_) => new Future<MockVM>.value(mockVM));
pq's avatar
pq committed
41
      when(mockVM.isolates).thenReturn(<VMRunnableIsolate>[mockIsolate]);
42
      when(mockIsolate.loadRunnable()).thenAnswer((_) => new Future<MockIsolate>.value(mockIsolate));
43
      when(mockIsolate.invokeExtension(any, any)).thenAnswer(
44
          (Invocation invocation) => makeMockResponse(<String, dynamic>{'status': 'ok'}));
45 46
      vmServiceConnectFunction = (String url) {
        return new Future<VMServiceClientConnection>.value(
47
          new VMServiceClientConnection(mockClient, mockPeer)
48 49
        );
      };
50 51 52 53
    });

    tearDown(() async {
      await logSub.cancel();
yjbanov's avatar
yjbanov committed
54
      restoreVmServiceConnectFunction();
55 56 57
    });

    test('connects to isolate paused at start', () async {
58
      final List<String> connectionLog = <String>[];
59
      when(mockPeer.sendRequest('streamListen', any)).thenAnswer((Invocation invocation) {
60 61 62
        connectionLog.add('streamListen');
        return null;
      });
63
      when(mockIsolate.pauseEvent).thenReturn(new MockVMPauseStartEvent());
64
      when(mockIsolate.resume()).thenAnswer((Invocation invocation) {
65 66 67
        connectionLog.add('resume');
        return new Future<Null>.value();
      });
68
      when(mockIsolate.onExtensionAdded).thenAnswer((Invocation invocation) {
69 70 71
        connectionLog.add('onExtensionAdded');
        return new Stream<String>.fromIterable(<String>['ext.flutter.driver']);
      });
72

73
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
74 75
      expect(driver, isNotNull);
      expectLogContains('Isolate is paused at start');
76
      expect(connectionLog, <String>['streamListen', 'onExtensionAdded', 'resume']);
77 78 79 80
    });

    test('connects to isolate paused mid-flight', () async {
      when(mockIsolate.pauseEvent).thenReturn(new MockVMPauseBreakpointEvent());
81
      when(mockIsolate.resume()).thenAnswer((Invocation invocation) => new Future<Null>.value());
82

83
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
84 85 86 87 88 89 90 91 92 93
      expect(driver, isNotNull);
      expectLogContains('Isolate is paused mid-flight');
    });

    // This test simulates a situation when we believe that the isolate is
    // currently paused, but something else (e.g. a debugger) resumes it before
    // we do. There's no need to fail as we should be able to drive the app
    // just fine.
    test('connects despite losing the race to resume isolate', () async {
      when(mockIsolate.pauseEvent).thenReturn(new MockVMPauseBreakpointEvent());
94
      when(mockIsolate.resume()).thenAnswer((Invocation invocation) {
95 96
        // This needs to be wrapped in a closure to not be considered uncaught
        // by package:test
Ian Hickson's avatar
Ian Hickson committed
97
        return new Future<Null>.error(new rpc.RpcException(101, ''));
98 99
      });

100
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
101 102 103 104 105 106
      expect(driver, isNotNull);
      expectLogContains('Attempted to resume an already resumed isolate');
    });

    test('connects to unpaused isolate', () async {
      when(mockIsolate.pauseEvent).thenReturn(new MockVMResumeEvent());
107
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
108 109 110 111 112 113 114
      expect(driver, isNotNull);
      expectLogContains('Isolate is not paused. Assuming application is ready.');
    });
  });

  group('FlutterDriver', () {
    MockVMServiceClient mockClient;
115
    MockPeer mockPeer;
116 117 118 119 120
    MockIsolate mockIsolate;
    FlutterDriver driver;

    setUp(() {
      mockClient = new MockVMServiceClient();
121
      mockPeer = new MockPeer();
122
      mockIsolate = new MockIsolate();
123
      driver = new FlutterDriver.connectedTo(mockClient, mockPeer, mockIsolate);
124 125 126
    });

    test('checks the health of the driver extension', () async {
127
      when(mockIsolate.invokeExtension(any, any)).thenAnswer(
128
          (Invocation invocation) => makeMockResponse(<String, dynamic>{'status': 'ok'}));
129
      final Health result = await driver.checkHealth();
130 131 132 133
      expect(result.status, HealthStatus.ok);
    });

    test('closes connection', () async {
134
      when(mockClient.close()).thenAnswer((Invocation invocation) => new Future<Null>.value());
135 136 137
      await driver.close();
    });

138
    group('ByValueKey', () {
139
      test('restricts value types', () async {
140
        expect(() => find.byValueKey(null),
141
            throwsA(const isInstanceOf<DriverError>()));
142 143 144
      });

      test('finds by ValueKey', () async {
145
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
pq's avatar
pq committed
146
          expect(i.positionalArguments[1], <String, String>{
147
            'command': 'tap',
148
            'timeout': _kSerializedTestTimeout,
149
            'finderType': 'ByValueKey',
150
            'keyValueString': 'foo',
151
            'keyValueType': 'String'
152
          });
153
          return makeMockResponse(<String, dynamic>{});
154
        });
155
        await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout);
156 157 158 159 160
      });
    });

    group('tap', () {
      test('requires a target reference', () async {
161
        expect(driver.tap(null), throwsA(const isInstanceOf<DriverError>()));
162 163 164
      });

      test('sends the tap command', () async {
165
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
Ian Hickson's avatar
Ian Hickson committed
166
          expect(i.positionalArguments[1], <String, dynamic>{
167
            'command': 'tap',
168
            'timeout': _kSerializedTestTimeout,
169 170
            'finderType': 'ByText',
            'text': 'foo',
171
          });
172
          return makeMockResponse(<String, dynamic>{});
173
        });
174
        await driver.tap(find.text('foo'), timeout: _kTestTimeout);
175 176 177 178 179
      });
    });

    group('getText', () {
      test('requires a target reference', () async {
180
        expect(driver.getText(null), throwsA(const isInstanceOf<DriverError>()));
181 182 183
      });

      test('sends the getText command', () async {
184
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
Ian Hickson's avatar
Ian Hickson committed
185
          expect(i.positionalArguments[1], <String, dynamic>{
186
            'command': 'get_text',
187
            'timeout': _kSerializedTestTimeout,
188 189 190
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int'
191
          });
192
          return makeMockResponse(<String, String>{
193 194 195
            'text': 'hello'
          });
        });
196
        final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout);
197 198 199
        expect(result, 'hello');
      });
    });
200 201

    group('waitFor', () {
202
      test('requires a target reference', () async {
203
        expect(driver.waitFor(null), throwsA(const isInstanceOf<DriverError>()));
204 205
      });

206
      test('sends the waitFor command', () async {
207
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
208 209 210 211
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitFor',
            'finderType': 'ByTooltipMessage',
            'text': 'foo',
212
            'timeout': _kSerializedTestTimeout,
213
          });
214
          return makeMockResponse(<String, dynamic>{});
215
        });
216
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
217 218
      });
    });
219

220 221
    group('waitUntilNoTransientCallbacks', () {
      test('sends the waitUntilNoTransientCallbacks command', () async {
222
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
223 224
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitUntilNoTransientCallbacks',
225
            'timeout': _kSerializedTestTimeout,
226 227 228
          });
          return makeMockResponse(<String, dynamic>{});
        });
229
        await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout);
230 231 232
      });
    });

233 234 235 236
    group('clearTimeline', () {
      test('clears timeline', () async {
        bool clearWasCalled = false;
        when(mockPeer.sendRequest('_clearVMTimeline', argThat(equals(<String, dynamic>{}))))
237
            .thenAnswer((Invocation invocation) async {
238 239 240 241 242 243 244 245
          clearWasCalled = true;
          return null;
        });
        await driver.clearTimeline();
        expect(clearWasCalled, isTrue);
      });
    });

246
    group('traceAction', () {
247 248 249 250 251 252
      List<String> log;

      setUp(() async {
        log = <String>[];

        when(mockPeer.sendRequest('_clearVMTimeline', argThat(equals(<String, dynamic>{}))))
253
            .thenAnswer((Invocation invocation) async {
254 255 256
          log.add('clear');
          return null;
        });
257

pq's avatar
pq committed
258
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[all]'}))))
259
            .thenAnswer((Invocation invocation) async {
260 261 262
          log.add('startTracing');
          return null;
        });
263

pq's avatar
pq committed
264
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
265
            .thenAnswer((Invocation invocation) async {
266 267 268
          log.add('stopTracing');
          return null;
        });
269

270
        when(mockPeer.sendRequest('_getVMTimeline')).thenAnswer((Invocation invocation) async {
271
          log.add('download');
272
          return <String, dynamic> {
pq's avatar
pq committed
273 274
            'traceEvents': <dynamic>[
              <String, String>{
275 276 277
                'name': 'test event'
              }
            ],
278 279
          };
        });
280
      });
281

282
      test('without clearing timeline', () async {
283
        final Timeline timeline = await driver.traceAction(() {
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
          log.add('action');
        }, retainPriorEvents: true);

        expect(log, const <String>[
          'startTracing',
          'action',
          'stopTracing',
          'download',
        ]);
        expect(timeline.events.single.name, 'test event');
      });

      test('with clearing timeline', () async {
        final Timeline timeline = await driver.traceAction(() {
          log.add('action');
299 300
        });

301 302 303 304 305 306 307
        expect(log, const <String>[
          'clear',
          'startTracing',
          'action',
          'stopTracing',
          'download',
        ]);
308
        expect(timeline.events.single.name, 'test event');
309 310
      });
    });
311

312 313
    group('traceAction with timeline streams', () {
      test('specify non-default timeline streams', () async {
314 315 316 317
        bool actionCalled = false;
        bool startTracingCalled = false;
        bool stopTracingCalled = false;

pq's avatar
pq committed
318
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[Dart, GC, Compiler]'}))))
319
          .thenAnswer((Invocation invocation) async {
320 321 322 323
            startTracingCalled = true;
            return null;
          });

pq's avatar
pq committed
324
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
325
          .thenAnswer((Invocation invocation) async {
326 327 328 329
            stopTracingCalled = true;
            return null;
          });

330
        when(mockPeer.sendRequest('_getVMTimeline')).thenAnswer((Invocation invocation) async {
331
          return <String, dynamic> {
pq's avatar
pq committed
332 333
            'traceEvents': <dynamic>[
              <String, String>{
334 335 336 337 338 339
                'name': 'test event'
              }
            ],
          };
        });

340
        final Timeline timeline = await driver.traceAction(() {
341 342
          actionCalled = true;
        },
343 344 345 346
        streams: const <TimelineStream>[
          TimelineStream.dart,
          TimelineStream.gc,
          TimelineStream.compiler
347 348
        ],
        retainPriorEvents: true);
349 350 351 352 353 354 355

        expect(actionCalled, isTrue);
        expect(startTracingCalled, isTrue);
        expect(stopTracingCalled, isTrue);
        expect(timeline.events.single.name, 'test event');
      });
    });
356 357 358

    group('sendCommand error conditions', () {
      test('local timeout', () async {
359
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
360 361 362 363
          // completer never competed to trigger timeout
          return new Completer<Map<String, dynamic>>().future;
        });
        try {
364
          await driver.waitFor(find.byTooltip('foo'), timeout: const Duration(milliseconds: 100));
365
          fail('expected an exception');
366
        } catch (error) {
367 368 369 370 371 372
          expect(error is DriverError, isTrue);
          expect(error.message, 'Failed to fulfill WaitFor: Flutter application not responding');
        }
      });

      test('remote error', () async {
373
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
374 375 376 377 378 379 380
          return makeMockResponse(<String, dynamic>{
            'message': 'This is a failure'
          }, isError: true);
        });
        try {
          await driver.waitFor(find.byTooltip('foo'));
          fail('expected an exception');
381
        } catch (error) {
382 383 384 385 386 387 388 389 390
          expect(error is DriverError, isTrue);
          expect(error.message, 'Error in Flutter application: {message: This is a failure}');
        }
      });
    });
  });
}

Future<Map<String, dynamic>> makeMockResponse(
391
    Map<String, dynamic> response, {bool isError = false}) {
392 393 394
  return new Future<Map<String, dynamic>>.value(<String, dynamic>{
    'isError': isError,
    'response': response
395 396 397 398 399 400 401 402 403 404 405 406 407 408
  });
}

class MockVMServiceClient extends Mock implements VMServiceClient { }

class MockVM extends Mock implements VM { }

class MockIsolate extends Mock implements VMRunnableIsolate { }

class MockVMPauseStartEvent extends Mock implements VMPauseStartEvent { }

class MockVMPauseBreakpointEvent extends Mock implements VMPauseBreakpointEvent { }

class MockVMResumeEvent extends Mock implements VMResumeEvent { }
409 410

class MockPeer extends Mock implements rpc.Peer { }