flutter_driver_test.dart 21.9 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:vm_service_client/vm_service_client.dart';
14
import 'package:quiver/testing/async.dart';
15

16 17
import 'common.dart';

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

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

31
    void expectLogContains(String message) {
32
      expect(log.map<String>((LogRecord r) => '$r'), anyElement(contains(message)));
33
    }
34

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

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

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

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

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

85
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
86 87 88 89 90 91 92 93 94
      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 {
95
      when(mockIsolate.pauseEvent).thenReturn(MockVMPauseBreakpointEvent());
96
      when(mockIsolate.resume()).thenAnswer((Invocation invocation) {
97 98
        // This needs to be wrapped in a closure to not be considered uncaught
        // by package:test
99
        return Future<dynamic>.error(rpc.RpcException(101, ''));
100 101
      });

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

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

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

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

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

    test('closes connection', () async {
136
      when(mockClient.close()).thenAnswer((Invocation invocation) => Future<dynamic>.value(null));
137 138 139
      await driver.close();
    });

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

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

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
    group('BySemanticsLabel', () {
      test('finds by Semantic label using String', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, String>{
            'command': 'tap',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'BySemanticsLabel',
            'label': 'foo',
          });
          return makeMockResponse(<String, dynamic>{});
        });
        await driver.tap(find.bySemanticsLabel('foo'), timeout: _kTestTimeout);
      });

      test('finds by Semantic label using RegExp', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, String>{
            'command': 'tap',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'BySemanticsLabel',
            'label': '^foo',
            'isRegExp': 'true',
          });
          return makeMockResponse(<String, dynamic>{});
        });
        await driver.tap(find.bySemanticsLabel(RegExp('^foo')), timeout: _kTestTimeout);
      });
    });

190 191
    group('tap', () {
      test('requires a target reference', () async {
192
        expect(driver.tap(null), throwsA(isInstanceOf<DriverError>()));
193 194 195
      });

      test('sends the tap command', () async {
196
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
Ian Hickson's avatar
Ian Hickson committed
197
          expect(i.positionalArguments[1], <String, dynamic>{
198
            'command': 'tap',
199
            'timeout': _kSerializedTestTimeout,
200 201
            'finderType': 'ByText',
            'text': 'foo',
202
          });
203
          return makeMockResponse(<String, dynamic>{});
204
        });
205
        await driver.tap(find.text('foo'), timeout: _kTestTimeout);
206 207 208 209 210
      });
    });

    group('getText', () {
      test('requires a target reference', () async {
211
        expect(driver.getText(null), throwsA(isInstanceOf<DriverError>()));
212 213 214
      });

      test('sends the getText command', () async {
215
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
Ian Hickson's avatar
Ian Hickson committed
216
          expect(i.positionalArguments[1], <String, dynamic>{
217
            'command': 'get_text',
218
            'timeout': _kSerializedTestTimeout,
219 220
            'finderType': 'ByValueKey',
            'keyValueString': '123',
221
            'keyValueType': 'int',
222
          });
223
          return makeMockResponse(<String, String>{
224
            'text': 'hello',
225 226
          });
        });
227
        final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout);
228 229 230
        expect(result, 'hello');
      });
    });
231 232

    group('waitFor', () {
233
      test('requires a target reference', () async {
234
        expect(driver.waitFor(null), throwsA(isInstanceOf<DriverError>()));
235 236
      });

237
      test('sends the waitFor command', () async {
238
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
239 240 241 242
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitFor',
            'finderType': 'ByTooltipMessage',
            'text': 'foo',
243
            'timeout': _kSerializedTestTimeout,
244
          });
245
          return makeMockResponse(<String, dynamic>{});
246
        });
247
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
248 249
      });
    });
250

251 252
    group('waitUntilNoTransientCallbacks', () {
      test('sends the waitUntilNoTransientCallbacks command', () async {
253
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
254 255
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitUntilNoTransientCallbacks',
256
            'timeout': _kSerializedTestTimeout,
257 258 259
          });
          return makeMockResponse(<String, dynamic>{});
        });
260
        await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout);
261 262 263
      });
    });

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
    group('getOffset', () {
      test('requires a target reference', () async {
        expect(driver.getCenter(null), throwsA(isInstanceOf<DriverError>()));
        expect(driver.getTopLeft(null), throwsA(isInstanceOf<DriverError>()));
        expect(driver.getTopRight(null), throwsA(isInstanceOf<DriverError>()));
        expect(driver.getBottomLeft(null), throwsA(isInstanceOf<DriverError>()));
        expect(driver.getBottomRight(null), throwsA(isInstanceOf<DriverError>()));
      });

      test('sends the getCenter command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'get_offset',
            'offsetType': 'center',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return makeMockResponse(<String, double>{
            'dx': 11,
            'dy': 12,
          });
        });
        final DriverOffset result = await driver.getCenter(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
      });

      test('sends the getTopLeft command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'get_offset',
            'offsetType': 'topLeft',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return makeMockResponse(<String, double>{
            'dx': 11,
            'dy': 12,
          });
        });
        final DriverOffset result = await driver.getTopLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
      });

      test('sends the getTopRight command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'get_offset',
            'offsetType': 'topRight',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return makeMockResponse(<String, double>{
            'dx': 11,
            'dy': 12,
          });
        });
        final DriverOffset result = await driver.getTopRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
      });

      test('sends the getBottomLeft command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'get_offset',
            'offsetType': 'bottomLeft',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return makeMockResponse(<String, double>{
            'dx': 11,
            'dy': 12,
          });
        });
        final DriverOffset result = await driver.getBottomLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
      });

      test('sends the getBottomRight command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'get_offset',
            'offsetType': 'bottomRight',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return makeMockResponse(<String, double>{
            'dx': 11,
            'dy': 12,
          });
        });
        final DriverOffset result = await driver.getBottomRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
      });
    });

369 370 371 372
    group('clearTimeline', () {
      test('clears timeline', () async {
        bool clearWasCalled = false;
        when(mockPeer.sendRequest('_clearVMTimeline', argThat(equals(<String, dynamic>{}))))
373 374 375 376
          .thenAnswer((Invocation invocation) async {
            clearWasCalled = true;
            return null;
          });
377 378 379 380 381
        await driver.clearTimeline();
        expect(clearWasCalled, isTrue);
      });
    });

382
    group('traceAction', () {
383 384 385 386 387 388
      List<String> log;

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

        when(mockPeer.sendRequest('_clearVMTimeline', argThat(equals(<String, dynamic>{}))))
389 390 391 392
          .thenAnswer((Invocation invocation) async {
            log.add('clear');
            return null;
          });
393

pq's avatar
pq committed
394
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[all]'}))))
395 396 397 398
          .thenAnswer((Invocation invocation) async {
            log.add('startTracing');
            return null;
          });
399

pq's avatar
pq committed
400
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
401 402 403 404
          .thenAnswer((Invocation invocation) async {
            log.add('stopTracing');
            return null;
          });
405

406
        when(mockPeer.sendRequest('_getVMTimeline')).thenAnswer((Invocation invocation) async {
407
          log.add('download');
408
          return <String, dynamic>{
pq's avatar
pq committed
409 410
            'traceEvents': <dynamic>[
              <String, String>{
411 412
                'name': 'test event',
              },
413
            ],
414 415
          };
        });
416
      });
417

418
      test('without clearing timeline', () async {
419
        final Timeline timeline = await driver.traceAction(() async {
420 421 422 423 424 425 426 427 428 429 430 431 432
          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 {
433
        final Timeline timeline = await driver.traceAction(() async {
434
          log.add('action');
435 436
        });

437 438 439 440 441 442 443
        expect(log, const <String>[
          'clear',
          'startTracing',
          'action',
          'stopTracing',
          'download',
        ]);
444
        expect(timeline.events.single.name, 'test event');
445 446
      });
    });
447

448 449
    group('traceAction with timeline streams', () {
      test('specify non-default timeline streams', () async {
450 451 452 453
        bool actionCalled = false;
        bool startTracingCalled = false;
        bool stopTracingCalled = false;

pq's avatar
pq committed
454
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[Dart, GC, Compiler]'}))))
455
          .thenAnswer((Invocation invocation) async {
456 457 458 459
            startTracingCalled = true;
            return null;
          });

pq's avatar
pq committed
460
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
461
          .thenAnswer((Invocation invocation) async {
462 463 464 465
            stopTracingCalled = true;
            return null;
          });

466
        when(mockPeer.sendRequest('_getVMTimeline')).thenAnswer((Invocation invocation) async {
467
          return <String, dynamic>{
pq's avatar
pq committed
468 469
            'traceEvents': <dynamic>[
              <String, String>{
470 471
                'name': 'test event',
              },
472 473 474 475
            ],
          };
        });

476
        final Timeline timeline = await driver.traceAction(() async {
477 478
          actionCalled = true;
        },
479 480 481
        streams: const <TimelineStream>[
          TimelineStream.dart,
          TimelineStream.gc,
482
          TimelineStream.compiler,
483 484
        ],
        retainPriorEvents: true);
485 486 487 488 489 490 491

        expect(actionCalled, isTrue);
        expect(startTracingCalled, isTrue);
        expect(stopTracingCalled, isTrue);
        expect(timeline.events.single.name, 'test event');
      });
    });
492 493

    group('sendCommand error conditions', () {
494
      test('local default timeout', () async {
495 496
        final List<String> log = <String>[];
        final StreamSubscription<LogRecord> logSub = flutterDriverLog.listen((LogRecord s) => log.add(s.toString()));
497
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
498
          // completer never completed to trigger timeout
499
          return Completer<Map<String, dynamic>>().future;
500
        });
501 502 503
        FakeAsync().run((FakeAsync time) {
          driver.waitFor(find.byTooltip('foo'));
          expect(log, <String>[]);
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
          time.elapse(kUnusuallyLongTimeout);
        });
        expect(log, <String>['[warning] FlutterDriver: waitFor message is taking a long time to complete...']);
        await logSub.cancel();
      });

      test('local custom timeout', () async {
        final List<String> log = <String>[];
        final StreamSubscription<LogRecord> logSub = flutterDriverLog.listen((LogRecord s) => log.add(s.toString()));
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          // completer never completed to trigger timeout
          return Completer<Map<String, dynamic>>().future;
        });
        FakeAsync().run((FakeAsync time) {
          final Duration customTimeout = kUnusuallyLongTimeout - const Duration(seconds: 1);
          driver.waitFor(find.byTooltip('foo'), timeout: customTimeout);
          expect(log, <String>[]);
          time.elapse(customTimeout);
522 523 524
        });
        expect(log, <String>['[warning] FlutterDriver: waitFor message is taking a long time to complete...']);
        await logSub.cancel();
525 526 527
      });

      test('remote error', () async {
528
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
529
          return makeMockResponse(<String, dynamic>{
530
            'message': 'This is a failure',
531 532 533 534 535
          }, isError: true);
        });
        try {
          await driver.waitFor(find.byTooltip('foo'));
          fail('expected an exception');
536
        } catch (error) {
537 538 539 540 541 542
          expect(error is DriverError, isTrue);
          expect(error.message, 'Error in Flutter application: {message: This is a failure}');
        }
      });
    });
  });
543 544 545 546 547 548 549 550 551 552 553

  group('FlutterDriver with custom timeout', () {
    MockVMServiceClient mockClient;
    MockPeer mockPeer;
    MockIsolate mockIsolate;
    FlutterDriver driver;

    setUp(() {
      mockClient = MockVMServiceClient();
      mockPeer = MockPeer();
      mockIsolate = MockIsolate();
554
      driver = FlutterDriver.connectedTo(mockClient, mockPeer, mockIsolate);
555 556
    });

557
    test('GetHealth has no default timeout', () async {
558 559 560 561 562 563 564 565 566
      when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
        expect(i.positionalArguments[1], <String, String>{
          'command': 'get_health',
        });
        return makeMockResponse(<String, dynamic>{'status': 'ok'});
      });
      await driver.checkHealth();
    });

567
    test('does not interfere with explicit timeouts', () async {
568 569 570 571 572 573 574 575 576 577
      when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
        expect(i.positionalArguments[1], <String, String>{
          'command': 'get_health',
          'timeout': _kSerializedTestTimeout,
        });
        return makeMockResponse(<String, dynamic>{'status': 'ok'});
      });
      await driver.checkHealth(timeout: _kTestTimeout);
    });
  });
578 579 580
}

Future<Map<String, dynamic>> makeMockResponse(
581 582 583
  Map<String, dynamic> response, {
  bool isError = false,
}) {
584
  return Future<Map<String, dynamic>>.value(<String, dynamic>{
585
    'isError': isError,
586
    'response': response,
587 588 589 590 591 592 593 594 595 596 597 598 599 600
  });
}

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 { }
601 602

class MockPeer extends Mock implements rpc.Peer { }