flutter_driver_test.dart 41.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6
import 'dart:convert';
7

8 9
import 'package:flutter_driver/src/common/error.dart';
import 'package:flutter_driver/src/common/health.dart';
10
import 'package:flutter_driver/src/common/layer_tree.dart';
11
import 'package:flutter_driver/src/common/wait.dart';
12 13
import 'package:flutter_driver/src/driver/driver.dart';
import 'package:flutter_driver/src/driver/timeline.dart';
14
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
15
import 'package:mockito/mockito.dart';
16
import 'package:vm_service_client/vm_service_client.dart';
17
import 'package:quiver/testing/async.dart';
18

19 20
import 'common.dart';

21
/// Magical timeout value that's different from the default.
22
const Duration _kTestTimeout = Duration(milliseconds: 1234);
23
const String _kSerializedTestTimeout = '1234';
24 25
const String _kWebScriptPrefix = "window.\$flutterDriver('";
const String _kWebScriptSuffix = "')";
26

27
void main() {
28 29 30 31 32
  final List<String> log = <String>[];
  driverLog = (String source, String message) {
    log.add('$source: $message');
  };

33
  group('VMServiceFlutterDriver.connect', () {
34 35 36
    MockVMServiceClient mockClient;
    MockVM mockVM;
    MockIsolate mockIsolate;
37
    MockPeer mockPeer;
38

39
    void expectLogContains(String message) {
40
      expect(log, anyElement(contains(message)));
41
    }
42

43
    setUp(() {
44
      log.clear();
45 46 47 48 49
      mockClient = MockVMServiceClient();
      mockVM = MockVM();
      mockIsolate = MockIsolate();
      mockPeer = MockPeer();
      when(mockClient.getVM()).thenAnswer((_) => Future<MockVM>.value(mockVM));
pq's avatar
pq committed
50
      when(mockVM.isolates).thenReturn(<VMRunnableIsolate>[mockIsolate]);
51
      when(mockIsolate.loadRunnable()).thenAnswer((_) => Future<MockIsolate>.value(mockIsolate));
52
      when(mockIsolate.invokeExtension(any, any)).thenAnswer(
53
          (Invocation invocation) => makeMockResponse(<String, dynamic>{'status': 'ok'}));
54
      vmServiceConnectFunction = (String url) {
55 56
        return Future<VMServiceClientConnection>.value(
          VMServiceClientConnection(mockClient, mockPeer)
57 58
        );
      };
59 60 61
    });

    tearDown(() async {
yjbanov's avatar
yjbanov committed
62
      restoreVmServiceConnectFunction();
63 64 65
    });

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

81
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
82 83
      expect(driver, isNotNull);
      expectLogContains('Isolate is paused at start');
84
      expect(connectionLog, <String>['streamListen', 'onExtensionAdded', 'resume']);
85 86 87
    });

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

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

108
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
109 110 111 112 113
      expect(driver, isNotNull);
      expectLogContains('Attempted to resume an already resumed isolate');
    });

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

121
  group('VMServiceFlutterDriver', () {
122
    MockVMServiceClient mockClient;
123
    MockPeer mockPeer;
124
    MockIsolate mockIsolate;
125
    VMServiceFlutterDriver driver;
126 127

    setUp(() {
128 129 130
      mockClient = MockVMServiceClient();
      mockPeer = MockPeer();
      mockIsolate = MockIsolate();
131
      driver = VMServiceFlutterDriver.connectedTo(mockClient, mockPeer, mockIsolate);
132 133 134
    });

    test('checks the health of the driver extension', () async {
135
      when(mockIsolate.invokeExtension(any, any)).thenAnswer(
136
          (Invocation invocation) => makeMockResponse(<String, dynamic>{'status': 'ok'}));
137
      final Health result = await driver.checkHealth();
138 139 140 141
      expect(result.status, HealthStatus.ok);
    });

    test('closes connection', () async {
142
      when(mockClient.close()).thenAnswer((Invocation invocation) => Future<dynamic>.value(null));
143 144 145
      await driver.close();
    });

146
    group('ByValueKey', () {
147
      test('restricts value types', () async {
Dan Field's avatar
Dan Field committed
148
        expect(() => find.byValueKey(null), throwsDriverError);
149 150 151
      });

      test('finds by ValueKey', () async {
152
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
pq's avatar
pq committed
153
          expect(i.positionalArguments[1], <String, String>{
154
            'command': 'tap',
155
            'timeout': _kSerializedTestTimeout,
156
            'finderType': 'ByValueKey',
157
            'keyValueString': 'foo',
158
            'keyValueType': 'String',
159
          });
160
          return makeMockResponse(<String, dynamic>{});
161
        });
162
        await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout);
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 190 191 192 193 194
    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);
      });
    });

195 196
    group('tap', () {
      test('requires a target reference', () async {
Dan Field's avatar
Dan Field committed
197
        expect(driver.tap(null), throwsDriverError);
198 199 200
      });

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

    group('getText', () {
      test('requires a target reference', () async {
Dan Field's avatar
Dan Field committed
216
        expect(driver.getText(null), throwsDriverError);
217 218 219
      });

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

237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
    group('getLayerTree', () {
      test('sends the getLayerTree command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'get_layer_tree',
            'timeout': _kSerializedTestTimeout,
          });
          return makeMockResponse(<String, String>{
            'tree': 'hello',
          });
        });
        final LayerTree result = await driver.getLayerTree(timeout: _kTestTimeout);
        final LayerTree referenceTree = LayerTree.fromJson(<String, String>{
            'tree': 'hello',
          });
        expect(result.tree, referenceTree.tree);
      });
    });

256
    group('waitFor', () {
257
      test('requires a target reference', () async {
Dan Field's avatar
Dan Field committed
258
        expect(driver.waitFor(null), throwsDriverError);
259 260
      });

261
      test('sends the waitFor command', () async {
262
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
263 264 265 266
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitFor',
            'finderType': 'ByTooltipMessage',
            'text': 'foo',
267
            'timeout': _kSerializedTestTimeout,
268
          });
269
          return makeMockResponse(<String, dynamic>{});
270
        });
271
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
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
    group('getWidgetDiagnostics', () {
      test('sends the getWidgetDiagnostics command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'get_diagnostics_tree',
            'diagnosticsType': 'widget',
            'finderType': 'ByTooltipMessage',
            'text': 'foo',
            'includeProperties': 'true',
            'subtreeDepth': '0',
            'timeout': _kSerializedTestTimeout,
          });
          return makeMockResponse(<String, dynamic>{});
        });
        await driver.getWidgetDiagnostics(find.byTooltip('foo'), timeout: _kTestTimeout);
      });
    });

    group('getRenderObjectDiagnostics', () {
      test('sends the getRenderObjectDiagnostics command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'get_diagnostics_tree',
            'diagnosticsType': 'renderObject',
            'finderType': 'ByTooltipMessage',
            'text': 'foo',
            'includeProperties': 'true',
            'subtreeDepth': '0',
            'timeout': _kSerializedTestTimeout,
          });
          return makeMockResponse(<String, dynamic>{});
        });
        await driver.getRenderObjectDiagnostics(find.byTooltip('foo'), timeout: _kTestTimeout);
      });
    });

311 312 313 314 315 316 317 318 319 320 321 322 323
    group('waitForCondition', () {
      test('sends the wait for NoPendingFrameCondition command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitForCondition',
            'timeout': _kSerializedTestTimeout,
            'conditionName': 'NoPendingFrameCondition',
          });
          return makeMockResponse(<String, dynamic>{});
        });
        await driver.waitForCondition(const NoPendingFrame(), timeout: _kTestTimeout);
      });

324 325 326 327 328 329 330 331 332 333 334 335
      test('sends the wait for NoPendingPlatformMessages command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitForCondition',
            'timeout': _kSerializedTestTimeout,
            'conditionName': 'NoPendingPlatformMessagesCondition',
          });
          return makeMockResponse(<String, dynamic>{});
        });
        await driver.waitForCondition(const NoPendingPlatformMessages(), timeout: _kTestTimeout);
      });

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
      test('sends the waitForCondition of combined conditions command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitForCondition',
            'timeout': _kSerializedTestTimeout,
            'conditionName': 'CombinedCondition',
            'conditions': '[{"conditionName":"NoPendingFrameCondition"},{"conditionName":"NoTransientCallbacksCondition"}]',
          });
          return makeMockResponse(<String, dynamic>{});
        });
        const SerializableWaitCondition combinedCondition =
            CombinedCondition(<SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()]);
        await driver.waitForCondition(combinedCondition, timeout: _kTestTimeout);
      });
    });

352 353
    group('waitUntilNoTransientCallbacks', () {
      test('sends the waitUntilNoTransientCallbacks command', () async {
354
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
355
          expect(i.positionalArguments[1], <String, dynamic>{
356
            'command': 'waitForCondition',
357
            'timeout': _kSerializedTestTimeout,
358
            'conditionName': 'NoTransientCallbacksCondition',
359 360 361
          });
          return makeMockResponse(<String, dynamic>{});
        });
362
        await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout);
363 364
      });
    });
365 366 367 368 369 370 371 372 373 374 375 376 377

    group('waitUntilFirstFrameRasterized', () {
      test('sends the waitUntilFirstFrameRasterized command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitForCondition',
            'conditionName': 'FirstFrameRasterizedCondition',
          });
          return makeMockResponse(<String, dynamic>{});
        });
        await driver.waitUntilFirstFrameRasterized();
      });
    });
378

379 380
    group('getOffset', () {
      test('requires a target reference', () async {
Dan Field's avatar
Dan Field committed
381 382 383 384 385
        expect(driver.getCenter(null), throwsDriverError);
        expect(driver.getTopLeft(null), throwsDriverError);
        expect(driver.getTopRight(null), throwsDriverError);
        expect(driver.getBottomLeft(null), throwsDriverError);
        expect(driver.getBottomRight(null), throwsDriverError);
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
      });

      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));
      });
    });

484 485 486
    group('clearTimeline', () {
      test('clears timeline', () async {
        bool clearWasCalled = false;
487
        when(mockPeer.sendRequest('clearVMTimeline', argThat(equals(<String, dynamic>{}))))
488 489 490 491
          .thenAnswer((Invocation invocation) async {
            clearWasCalled = true;
            return null;
          });
492 493 494 495 496
        await driver.clearTimeline();
        expect(clearWasCalled, isTrue);
      });
    });

497
    group('traceAction', () {
498 499 500 501 502
      List<String> log;

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

503
        when(mockPeer.sendRequest('clearVMTimeline', argThat(equals(<String, dynamic>{}))))
504 505 506 507
          .thenAnswer((Invocation invocation) async {
            log.add('clear');
            return null;
          });
508

509
        when(mockPeer.sendRequest('setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[all]'}))))
510 511 512 513
          .thenAnswer((Invocation invocation) async {
            log.add('startTracing');
            return null;
          });
514

515
        when(mockPeer.sendRequest('setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
516 517 518 519
          .thenAnswer((Invocation invocation) async {
            log.add('stopTracing');
            return null;
          });
520

521
        when(mockPeer.sendRequest('getVMTimeline')).thenAnswer((Invocation invocation) async {
522
          log.add('download');
523
          return <String, dynamic>{
pq's avatar
pq committed
524 525
            'traceEvents': <dynamic>[
              <String, String>{
526 527
                'name': 'test event',
              },
528
            ],
529 530
          };
        });
531
      });
532

533
      test('without clearing timeline', () async {
534
        final Timeline timeline = await driver.traceAction(() async {
535 536 537 538 539 540 541 542 543 544 545 546 547
          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 {
548
        final Timeline timeline = await driver.traceAction(() async {
549
          log.add('action');
550 551
        });

552 553 554 555 556 557 558
        expect(log, const <String>[
          'clear',
          'startTracing',
          'action',
          'stopTracing',
          'download',
        ]);
559
        expect(timeline.events.single.name, 'test event');
560 561
      });
    });
562

563 564
    group('traceAction with timeline streams', () {
      test('specify non-default timeline streams', () async {
565 566 567 568
        bool actionCalled = false;
        bool startTracingCalled = false;
        bool stopTracingCalled = false;

569
        when(mockPeer.sendRequest('setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[Dart, GC, Compiler]'}))))
570
          .thenAnswer((Invocation invocation) async {
571 572 573 574
            startTracingCalled = true;
            return null;
          });

575
        when(mockPeer.sendRequest('setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
576
          .thenAnswer((Invocation invocation) async {
577 578 579 580
            stopTracingCalled = true;
            return null;
          });

581
        when(mockPeer.sendRequest('getVMTimeline')).thenAnswer((Invocation invocation) async {
582
          return <String, dynamic>{
pq's avatar
pq committed
583 584
            'traceEvents': <dynamic>[
              <String, String>{
585 586
                'name': 'test event',
              },
587 588 589 590
            ],
          };
        });

591
        final Timeline timeline = await driver.traceAction(() async {
592 593
          actionCalled = true;
        },
594 595 596
        streams: const <TimelineStream>[
          TimelineStream.dart,
          TimelineStream.gc,
597
          TimelineStream.compiler,
598 599
        ],
        retainPriorEvents: true);
600 601 602 603 604 605 606

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

    group('sendCommand error conditions', () {
609
      test('local default timeout', () async {
610
        log.clear();
611
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
612
          // completer never completed to trigger timeout
613
          return Completer<Map<String, dynamic>>().future;
614
        });
615 616 617
        FakeAsync().run((FakeAsync time) {
          driver.waitFor(find.byTooltip('foo'));
          expect(log, <String>[]);
618 619
          time.elapse(kUnusuallyLongTimeout);
        });
620
        expect(log, <String>['VMServiceFlutterDriver: waitFor message is taking a long time to complete...']);
621 622 623
      });

      test('local custom timeout', () async {
624
        log.clear();
625 626 627 628 629 630 631 632 633
        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);
634
        });
635
        expect(log, <String>['VMServiceFlutterDriver: waitFor message is taking a long time to complete...']);
636 637 638
      });

      test('remote error', () async {
639
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
640
          return makeMockResponse(<String, dynamic>{
641
            'message': 'This is a failure',
642 643 644 645 646
          }, isError: true);
        });
        try {
          await driver.waitFor(find.byTooltip('foo'));
          fail('expected an exception');
647
        } catch (error) {
Dan Field's avatar
Dan Field committed
648
          expect(error, isA<DriverError>());
649 650 651 652 653
          expect(error.message, 'Error in Flutter application: {message: This is a failure}');
        }
      });
    });
  });
654

655
  group('VMServiceFlutterDriver with custom timeout', () {
656 657 658
    MockVMServiceClient mockClient;
    MockPeer mockPeer;
    MockIsolate mockIsolate;
659
    VMServiceFlutterDriver driver;
660 661 662 663 664

    setUp(() {
      mockClient = MockVMServiceClient();
      mockPeer = MockPeer();
      mockIsolate = MockIsolate();
665
      driver = VMServiceFlutterDriver.connectedTo(mockClient, mockPeer, mockIsolate);
666 667
    });

668
    test('GetHealth has no default timeout', () async {
669 670 671 672 673 674 675 676 677
      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();
    });

678
    test('does not interfere with explicit timeouts', () async {
679 680 681 682 683 684 685 686 687 688
      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);
    });
  });
689 690 691 692 693 694 695

  group('WebFlutterDriver', () {
    MockFlutterWebConnection mockConnection;
    WebFlutterDriver driver;

    setUp(() {
      mockConnection = MockFlutterWebConnection();
696 697
      mockConnection.supportsTimelineAction = true;
      driver = WebFlutterDriver.connectedTo(mockConnection);
698 699 700 701 702 703 704 705 706 707
    });

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

    group('ByValueKey', () {
      test('restricts value types', () async {
        expect(() => find.byValueKey(null),
Dan Field's avatar
Dan Field committed
708
            throwsDriverError);
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
      });

      test('finds by ValueKey', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'tap',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': 'foo',
            'keyValueType': 'String',
          });
          return jsonEncode(await makeMockResponse(<String, dynamic>{}));
        });
        await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout);
      });
    });

    group('BySemanticsLabel', () {
      test('finds by Semantic label using String', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'tap',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'BySemanticsLabel',
            'label': 'foo',
          });
          return jsonEncode(await makeMockResponse(<String, dynamic>{}));
        });
        await driver.tap(find.bySemanticsLabel('foo'), timeout: _kTestTimeout);
      });

      test('finds by Semantic label using RegExp', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'tap',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'BySemanticsLabel',
            'label': '^foo',
            'isRegExp': 'true',
          });
          return jsonEncode(await makeMockResponse(<String, dynamic>{}));
        });
        await driver.tap(find.bySemanticsLabel(RegExp('^foo')), timeout: _kTestTimeout);
      });
    });

    group('tap', () {
      test('requires a target reference', () async {
Dan Field's avatar
Dan Field committed
760
        expect(driver.tap(null), throwsDriverError);
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
      });

      test('sends the tap command', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'tap',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByText',
            'text': 'foo',
          });
          return jsonEncode(await makeMockResponse(<String, dynamic>{}));
        });
        await driver.tap(find.text('foo'), timeout: _kTestTimeout);
      });
    });

    group('getText', () {
      test('requires a target reference', () async {
Dan Field's avatar
Dan Field committed
780
        expect(driver.getText(null), throwsDriverError);
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803
      });

      test('sends the getText command', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'get_text',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return jsonEncode(await makeMockResponse(<String, String>{
            'text': 'hello',
          }));
        });
        final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, 'hello');
      });
    });

    group('waitFor', () {
      test('requires a target reference', () async {
Dan Field's avatar
Dan Field committed
804
        expect(driver.waitFor(null), throwsDriverError);
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882
      });

      test('sends the waitFor command', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'waitFor',
            'finderType': 'ByTooltipMessage',
            'text': 'foo',
            'timeout': _kSerializedTestTimeout,
          });
          return jsonEncode(await makeMockResponse(<String, dynamic>{}));
        });
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
      });
    });

    group('waitForCondition', () {
      test('sends the wait for NoPendingFrameCondition command', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'waitForCondition',
            'timeout': _kSerializedTestTimeout,
            'conditionName': 'NoPendingFrameCondition',
          });
          return jsonEncode(await makeMockResponse(<String, dynamic>{}));
        });
        await driver.waitForCondition(const NoPendingFrame(), timeout: _kTestTimeout);
      });

      test('sends the wait for NoPendingPlatformMessages command', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'waitForCondition',
            'timeout': _kSerializedTestTimeout,
            'conditionName': 'NoPendingPlatformMessagesCondition',
          });
          return jsonEncode(await makeMockResponse(<String, dynamic>{}));
        });
        await driver.waitForCondition(const NoPendingPlatformMessages(), timeout: _kTestTimeout);
      });

      test('sends the waitForCondition of combined conditions command', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'waitForCondition',
            'timeout': _kSerializedTestTimeout,
            'conditionName': 'CombinedCondition',
            'conditions': '[{"conditionName":"NoPendingFrameCondition"},{"conditionName":"NoTransientCallbacksCondition"}]',
          });
          return jsonEncode(await makeMockResponse(<String, dynamic>{}));
        });
        const SerializableWaitCondition combinedCondition =
        CombinedCondition(<SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()]);
        await driver.waitForCondition(combinedCondition, timeout: _kTestTimeout);
      });
    });

    group('waitUntilNoTransientCallbacks', () {
      test('sends the waitUntilNoTransientCallbacks command', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'waitForCondition',
            'timeout': _kSerializedTestTimeout,
            'conditionName': 'NoTransientCallbacksCondition',
          });
          return jsonEncode(await makeMockResponse(<String, dynamic>{}));
        });
        await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout);
      });
    });

    group('getOffset', () {
      test('requires a target reference', () async {
Dan Field's avatar
Dan Field committed
883 884 885 886 887
        expect(driver.getCenter(null), throwsDriverError);
        expect(driver.getTopLeft(null), throwsDriverError);
        expect(driver.getTopRight(null), throwsDriverError);
        expect(driver.getBottomLeft(null), throwsDriverError);
        expect(driver.getBottomRight(null), throwsDriverError);
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004
      });

      test('sends the getCenter command', () async {
        when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'get_offset',
            'offsetType': 'center',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return jsonEncode(await 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(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'get_offset',
            'offsetType': 'topLeft',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return jsonEncode(await 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(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'get_offset',
            'offsetType': 'topRight',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return jsonEncode(await 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(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'get_offset',
            'offsetType': 'bottomLeft',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return jsonEncode(await 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(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
          final String script = _checkAndEncode(i.positionalArguments[0]);
          expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
            'command': 'get_offset',
            'offsetType': 'bottomRight',
            'timeout': _kSerializedTestTimeout,
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int',
          });
          return jsonEncode(await makeMockResponse(<String, double>{
            'dx': 11,
            'dy': 12,
          }));
        });
        final DriverOffset result = await driver.getBottomRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
      });
    });

    test('checks the health of the driver extension', () async {
      when(mockConnection.sendCommand(any, any)).thenAnswer((Invocation i) async {
        final String script = _checkAndEncode(i.positionalArguments[0]);
        expect(Map<String, String>.from(jsonDecode(script) as Map<String, dynamic>), <String, String>{
          'command': 'get_health',
        });
        return jsonEncode(await makeMockResponse(<String, dynamic>{'status': 'ok'}));
      });
      await driver.checkHealth();
    });

    group('WebFlutterDriver Unimplemented error', () {
      test('forceGC', () async {
        expect(driver.forceGC(),
Dan Field's avatar
Dan Field committed
1005
            throwsA(isA<UnimplementedError>()));
1006 1007 1008 1009
      });

      test('getVmFlags', () async {
        expect(driver.getVmFlags(),
Dan Field's avatar
Dan Field committed
1010
            throwsA(isA<UnimplementedError>()));
1011 1012 1013 1014
      });

      test('waitUntilFirstFrameRasterized', () async {
        expect(driver.waitUntilFirstFrameRasterized(),
Dan Field's avatar
Dan Field committed
1015
            throwsA(isA<UnimplementedError>()));
1016 1017 1018 1019
      });

      test('appIsoloate', () async {
        expect(() => driver.appIsolate.invokeExtension('abc', <String, String>{'abc': '123'}),
Dan Field's avatar
Dan Field committed
1020
            throwsA(isA<UnsupportedError>()));
1021 1022 1023 1024
      });

      test('serviceClient', () async {
        expect(() => driver.serviceClient.getVM(),
Dan Field's avatar
Dan Field committed
1025
            throwsA(isA<UnsupportedError>()));
1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
      });
    });
  });

  group('WebFlutterDriver with non-chrome browser', () {
    MockFlutterWebConnection mockConnection;
    WebFlutterDriver driver;

    setUp(() {
      mockConnection = MockFlutterWebConnection();
1036 1037
      mockConnection.supportsTimelineAction = false;
      driver = WebFlutterDriver.connectedTo(mockConnection);
1038 1039 1040 1041
    });

    test('tracing', () async {
      expect(driver.traceAction(() async { return Future<dynamic>.value(); }),
Dan Field's avatar
Dan Field committed
1042
          throwsA(isA<UnimplementedError>()));
1043
      expect(driver.startTracing(),
Dan Field's avatar
Dan Field committed
1044
          throwsA(isA<UnimplementedError>()));
1045
      expect(driver.stopTracingAndDownloadTimeline(),
Dan Field's avatar
Dan Field committed
1046
          throwsA(isA<UnimplementedError>()));
1047
      expect(driver.clearTimeline(),
Dan Field's avatar
Dan Field committed
1048
          throwsA(isA<UnimplementedError>()));
1049 1050 1051 1052 1053 1054 1055 1056 1057
    });
  });
}

/// This function will verify the format of the script
/// and return the actual script.
/// script will be in the following format:
//   window.flutterDriver('[actual script]')
String _checkAndEncode(dynamic script) {
Dan Field's avatar
Dan Field committed
1058
  expect(script, isA<String>());
1059 1060 1061 1062
  expect(script.startsWith(_kWebScriptPrefix), isTrue);
  expect(script.endsWith(_kWebScriptSuffix), isTrue);
  // Strip prefix and suffix
  return script.substring(_kWebScriptPrefix.length, script.length - 2) as String;
1063 1064 1065
}

Future<Map<String, dynamic>> makeMockResponse(
1066 1067 1068
  Map<String, dynamic> response, {
  bool isError = false,
}) {
1069
  return Future<Map<String, dynamic>>.value(<String, dynamic>{
1070
    'isError': isError,
1071
    'response': response,
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085
  });
}

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 { }
1086

1087 1088
class MockFlutterWebConnection extends Mock implements FlutterWebConnection { }

1089 1090 1091 1092
class MockPeer extends Mock implements rpc.Peer {
  @override
  bool get isClosed => false;
}