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

5 6
// @dart = 2.8

7
import 'dart:async';
8
import 'dart:convert';
9

10 11
import 'package:flutter_driver/src/common/error.dart';
import 'package:flutter_driver/src/common/health.dart';
12
import 'package:flutter_driver/src/common/layer_tree.dart';
13
import 'package:flutter_driver/src/common/wait.dart';
14 15
import 'package:flutter_driver/src/driver/driver.dart';
import 'package:flutter_driver/src/driver/timeline.dart';
16
import 'package:vm_service/vm_service.dart' as vms;
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
const String _kWebScriptPrefix = r"window.$flutterDriver('";
25
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
    FakeVmService fakeClient;
    FakeVM fakeVM;
    FakeIsolate fakeIsolate;
37

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

42
    setUp(() {
43
      log.clear();
44 45 46 47 48
      fakeIsolate = FakeIsolate();
      fakeVM = FakeVM(fakeIsolate);
      fakeClient = FakeVmService(fakeVM);
      vmServiceConnectFunction = (String url, Map<String, dynamic> headers) async {
        return fakeClient;
49
      };
50
      fakeClient.responses['get_health'] = makeFakeResponse(<String, dynamic>{'status': 'ok'});
51 52 53
    });

    tearDown(() async {
yjbanov's avatar
yjbanov committed
54
      restoreVmServiceConnectFunction();
55 56
    });

57 58 59 60 61 62

    test('throws after retries if no isolate', () async {
      fakeVM.numberOfTriesBeforeResolvingIsolate = 10000;
      FakeAsync().run((FakeAsync time) {
        FlutterDriver.connect(dartVmServiceUrl: '');
        time.elapse(kUnusuallyLongTimeout);
63
      });
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
      expect(log, <String>[
        'VMServiceFlutterDriver: Connecting to Flutter application at ',
        'VMServiceFlutterDriver: The root isolate is taking an unuusally long time to start.',
      ]);
    });

    test('Retries connections if isolate is not available', () async {
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0);
      fakeVM.numberOfTriesBeforeResolvingIsolate = 5;
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
      expect(driver, isNotNull);
      expect(
        fakeClient.connectionLog,
        <String>[
          'getIsolate',
          'setFlag pause_isolates_on_start false',
          'resume',
          'streamListen Isolate',
          'getIsolate',
          'onIsolateEvent',
          'streamCancel Isolate',
        ],
      );
    });

    test('Connects to isolate number', () async {
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0);
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '', isolateNumber: int.parse(fakeIsolate.number));
      expect(driver, isNotNull);
      expect(
        fakeClient.connectionLog,
        <String>[
          'getIsolate',
          'setFlag pause_isolates_on_start false',
          'resume',
          'streamListen Isolate',
          'getIsolate',
          'onIsolateEvent',
          'streamCancel Isolate',
        ],
      );
    });

    test('connects to isolate paused at start', () async {
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0);
109

110
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
111
      expect(driver, isNotNull);
112
      expectLogContains('Isolate is paused at start');
113 114 115 116 117 118 119 120 121 122 123 124
      expect(
        fakeClient.connectionLog,
        <String>[
          'getIsolate',
          'setFlag pause_isolates_on_start false',
          'resume',
          'streamListen Isolate',
          'getIsolate',
          'onIsolateEvent',
          'streamCancel Isolate',
        ],
      );
125 126
    });

127
    test('ignores setFlag failure', () async {
128 129
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0);
      fakeClient.failOnSetFlag = true;
130 131 132 133 134 135 136 137

      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
      expectLogContains('Failed to set pause_isolates_on_start=false, proceeding. '
                        'Error: Exception: setFlag failed');
      expect(driver, isNotNull);
    });


138
    test('connects to isolate paused mid-flight', () async {
139
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseBreakpoint, timestamp: 0);
140

141
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
142
      expect(driver, isNotNull);
143
      expectLogContains('Isolate is paused mid-flight');
144 145 146 147 148 149 150
    });

    // 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 {
151 152
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseBreakpoint, timestamp: 0);
      fakeClient.failOnResumeWith101 = true;
153

154
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
155 156 157 158 159
      expect(driver, isNotNull);
      expectLogContains('Attempted to resume an already resumed isolate');
    });

    test('connects to unpaused isolate', () async {
160 161
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kResume, timestamp: 0);

162
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
163 164 165
      expect(driver, isNotNull);
      expectLogContains('Isolate is not paused. Assuming application is ready.');
    });
166

167 168
    test('connects to unpaused when onExtensionAdded does not contain the '
      'driver extension', () async {
169 170 171
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kResume, timestamp: 0);
      fakeIsolate.extensionRPCs.add('ext.flutter.driver');

172 173 174 175
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
      expect(driver, isNotNull);
      expectLogContains('Isolate is not paused. Assuming application is ready.');
    });
176 177
  });

178
  group('VMServiceFlutterDriver', () {
179 180 181
    FakeVmService fakeClient;
    FakeVM fakeVM;
    FakeIsolate fakeIsolate;
182
    VMServiceFlutterDriver driver;
183 184

    setUp(() {
185 186 187 188 189
      fakeIsolate = FakeIsolate();
      fakeVM = FakeVM(fakeIsolate);
      fakeClient = FakeVmService(fakeVM);
      driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate);
      fakeClient.responses['tap'] = makeFakeResponse(<String, dynamic>{});
190 191 192
    });

    test('checks the health of the driver extension', () async {
193
      fakeClient.responses['get_health'] = makeFakeResponse(<String, dynamic>{'status': 'ok'});
194
      final Health result = await driver.checkHealth();
195 196 197 198 199
      expect(result.status, HealthStatus.ok);
    });

    test('closes connection', () async {
      await driver.close();
200
      expect(fakeClient.connectionLog.last, 'dispose');
201 202
    });

203
    group('ByValueKey', () {
204
      test('restricts value types', () async {
Dan Field's avatar
Dan Field committed
205
        expect(() => find.byValueKey(null), throwsDriverError);
206 207 208
      });

      test('finds by ValueKey', () async {
209
        await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout);
210 211 212
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: ByValueKey, keyValueString: foo, keyValueType: String}',
        ]);
213 214 215
      });
    });

216 217 218
    group('BySemanticsLabel', () {
      test('finds by Semantic label using String', () async {
        await driver.tap(find.bySemanticsLabel('foo'), timeout: _kTestTimeout);
219 220 221
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: BySemanticsLabel, label: foo}',
        ]);
222 223 224 225
      });

      test('finds by Semantic label using RegExp', () async {
        await driver.tap(find.bySemanticsLabel(RegExp('^foo')), timeout: _kTestTimeout);
226 227 228
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: BySemanticsLabel, label: ^foo, isRegExp: true}',
        ]);
229 230 231
      });
    });

232 233
    group('tap', () {
      test('requires a target reference', () async {
234
        expect(driver.tap(null), throwsAssertionError);
235 236 237
      });

      test('sends the tap command', () async {
238
        await driver.tap(find.text('foo'), timeout: _kTestTimeout);
239 240 241
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: ByText, text: foo}',
        ]);
242 243 244 245 246
      });
    });

    group('getText', () {
      test('requires a target reference', () async {
247
        expect(driver.getText(null), throwsAssertionError);
248 249 250
      });

      test('sends the getText command', () async {
251
        fakeClient.responses['get_text'] = makeFakeResponse(<String, dynamic>{'text': 'hello'});
252
        final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout);
253
        expect(result, 'hello');
254 255 256
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: get_text, timeout: $_kSerializedTestTimeout, finderType: ByValueKey, keyValueString: 123, keyValueType: int}',
        ]);
257 258
      });
    });
259

260 261
    group('getLayerTree', () {
      test('sends the getLayerTree command', () async {
262 263
        fakeClient.responses['get_layer_tree'] = makeFakeResponse(<String, String>{
          'tree': 'hello',
264 265 266 267 268 269
        });
        final LayerTree result = await driver.getLayerTree(timeout: _kTestTimeout);
        final LayerTree referenceTree = LayerTree.fromJson(<String, String>{
            'tree': 'hello',
          });
        expect(result.tree, referenceTree.tree);
270 271 272
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: get_layer_tree, timeout: $_kSerializedTestTimeout}',
        ]);
273 274 275
      });
    });

276
    group('waitFor', () {
277
      test('requires a target reference', () async {
278
        expect(driver.waitFor(null), throwsAssertionError);
279 280
      });

281
      test('sends the waitFor command', () async {
282
        fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{});
283
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
284 285 286
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitFor, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo}',
        ]);
287 288
      });
    });
289

290 291
    group('getWidgetDiagnostics', () {
      test('sends the getWidgetDiagnostics command', () async {
292
        fakeClient.responses['get_diagnostics_tree'] = makeFakeResponse(<String, dynamic>{});
293
        await driver.getWidgetDiagnostics(find.byTooltip('foo'), timeout: _kTestTimeout);
294 295 296
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: get_diagnostics_tree, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo, subtreeDepth: 0, includeProperties: true, diagnosticsType: widget}',
        ]);
297 298 299 300 301
      });
    });

    group('getRenderObjectDiagnostics', () {
      test('sends the getRenderObjectDiagnostics command', () async {
302
        fakeClient.responses['get_diagnostics_tree'] = makeFakeResponse(<String, dynamic>{});
303
        await driver.getRenderObjectDiagnostics(find.byTooltip('foo'), timeout: _kTestTimeout);
304 305 306
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: get_diagnostics_tree, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo, subtreeDepth: 0, includeProperties: true, diagnosticsType: renderObject}',
        ]);
307 308 309
      });
    });

310 311
    group('waitForCondition', () {
      test('sends the wait for NoPendingFrameCondition command', () async {
312
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
313
        await driver.waitForCondition(const NoPendingFrame(), timeout: _kTestTimeout);
314 315 316
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: NoPendingFrameCondition}',
        ]);
317 318
      });

319
      test('sends the wait for NoPendingPlatformMessages command', () async {
320
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
321
        await driver.waitForCondition(const NoPendingPlatformMessages(), timeout: _kTestTimeout);
322 323 324
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: NoPendingPlatformMessagesCondition}',
        ]);
325 326
      });

327
      test('sends the waitForCondition of combined conditions command', () async {
328
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
329 330 331
        const SerializableWaitCondition combinedCondition =
            CombinedCondition(<SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()]);
        await driver.waitForCondition(combinedCondition, timeout: _kTestTimeout);
332 333 334
         expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: CombinedCondition, conditions: [{"conditionName":"NoPendingFrameCondition"},{"conditionName":"NoTransientCallbacksCondition"}]}',
        ]);
335 336 337
      });
    });

338 339
    group('waitUntilNoTransientCallbacks', () {
      test('sends the waitUntilNoTransientCallbacks command', () async {
340
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
341
        await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout);
342 343 344
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: NoTransientCallbacksCondition}',
        ]);
345 346
      });
    });
347 348 349

    group('waitUntilFirstFrameRasterized', () {
      test('sends the waitUntilFirstFrameRasterized command', () async {
350
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
351
        await driver.waitUntilFirstFrameRasterized();
352 353 354
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, conditionName: FirstFrameRasterizedCondition}',
        ]);
355 356
      });
    });
357

358
    group('getOffset', () {
359 360 361 362 363 364 365
      setUp(() {
        fakeClient.responses['get_offset'] = makeFakeResponse(<String, double>{
          'dx': 11,
          'dy': 12,
        });
      });

366
      test('requires a target reference', () async {
367 368 369 370 371
        expect(driver.getCenter(null), throwsAssertionError);
        expect(driver.getTopLeft(null), throwsAssertionError);
        expect(driver.getTopRight(null), throwsAssertionError);
        expect(driver.getBottomLeft(null), throwsAssertionError);
        expect(driver.getBottomRight(null), throwsAssertionError);
372 373 374 375 376
      });

      test('sends the getCenter command', () async {
        final DriverOffset result = await driver.getCenter(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
377 378 379
        expect(fakeClient.commandLog, <String>[
           'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: center}',
        ]);
380 381 382 383 384
      });

      test('sends the getTopLeft command', () async {
        final DriverOffset result = await driver.getTopLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
385 386 387
        expect(fakeClient.commandLog, <String>[
           'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: topLeft}',
        ]);
388 389 390 391 392
      });

      test('sends the getTopRight command', () async {
        final DriverOffset result = await driver.getTopRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
393 394 395
        expect(fakeClient.commandLog, <String>[
           'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: topRight}',
        ]);
396 397 398 399 400
      });

      test('sends the getBottomLeft command', () async {
        final DriverOffset result = await driver.getBottomLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
401 402 403
        expect(fakeClient.commandLog, <String>[
           'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: bottomLeft}',
        ]);
404 405 406 407 408
      });

      test('sends the getBottomRight command', () async {
        final DriverOffset result = await driver.getBottomRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
409 410 411
        expect(fakeClient.commandLog, <String>[
           'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: bottomRight}',
        ]);
412 413 414
      });
    });

415 416 417
    group('clearTimeline', () {
      test('clears timeline', () async {
        await driver.clearTimeline();
418
        expect(fakeClient.connectionLog, contains('clearVMTimeline'));
419 420 421
      });
    });

422
    group('traceAction', () {
423
      test('without clearing timeline', () async {
424
        final Timeline timeline = await driver.traceAction(() async {
425
          fakeClient.connectionLog.add('action');
426 427
        }, retainPriorEvents: true);

428 429
        expect(fakeClient.connectionLog, const <String>[
          'setVMTimelineFlags [all]',
430
          'action',
431 432 433
          'getFlagList',
          'setVMTimelineFlags []',
          'getVMTimeline null null',
434 435 436 437 438
        ]);
        expect(timeline.events.single.name, 'test event');
      });

      test('with clearing timeline', () async {
439
        final Timeline timeline = await driver.traceAction(() async {
440
          fakeClient.connectionLog.add('action');
441 442
        });

443 444
        expect(fakeClient.connectionLog, const <String>[
          'clearVMTimeline',
445
          'getVMTimelineMicros',
446
          'setVMTimelineFlags [all]',
447
          'action',
448
          'getVMTimelineMicros',
449 450 451
          'getFlagList',
          'setVMTimelineFlags []',
          'getVMTimeline 1 999999',
452
        ]);
453
        expect(timeline.events.single.name, 'test event');
454
      });
455 456

      test('with time interval', () async {
457 458 459 460 461 462 463
        fakeClient.incrementMicros = true;
        fakeClient.timelineResponses[1000001] = vms.Timeline.parse(<String, dynamic>{
          'traceEvents': <dynamic>[
            <String, dynamic>{
              'name': 'test event 2',
            },
          ],
464 465
          'timeOriginMicros': 1000000,
          'timeExtentMicros': 999999,
466
        });
467
        final Timeline timeline = await driver.traceAction(() async {
468
          fakeClient.connectionLog.add('action');
469 470
        });

471 472
        expect(fakeClient.connectionLog, const <String>[
          'clearVMTimeline',
473
          'getVMTimelineMicros',
474
          'setVMTimelineFlags [all]',
475 476
          'action',
          'getVMTimelineMicros',
477 478 479 480
          'getFlagList',
          'setVMTimelineFlags []',
          'getVMTimeline 1 999999',
          'getVMTimeline 1000001 999999',
481 482
        ]);
        expect(timeline.events.map((TimelineEvent event) => event.name), <String>[
483
          'test event',
484 485 486
          'test event 2',
        ]);
      });
487
    });
488

489 490
    group('traceAction with timeline streams', () {
      test('specify non-default timeline streams', () async {
491 492
        bool actionCalled = false;

493
        final Timeline timeline = await driver.traceAction(() async {
494 495
          actionCalled = true;
        },
496 497 498
        streams: const <TimelineStream>[
          TimelineStream.dart,
          TimelineStream.gc,
499
          TimelineStream.compiler,
500 501
        ],
        retainPriorEvents: true);
502 503

        expect(actionCalled, isTrue);
504 505 506 507 508 509 510
        expect(fakeClient.connectionLog, <String>[
          'setVMTimelineFlags [Dart, GC, Compiler]',
          'getFlagList',
          'setVMTimelineFlags []',
          'getVMTimeline null null'
        ]);

511 512 513
        expect(timeline.events.single.name, 'test event');
      });
    });
514 515

    group('sendCommand error conditions', () {
516
      test('local default timeout', () async {
517
        log.clear();
518
        fakeClient.artificialExtensionDelay = Completer<void>().future;
519 520 521
        FakeAsync().run((FakeAsync time) {
          driver.waitFor(find.byTooltip('foo'));
          expect(log, <String>[]);
522 523
          time.elapse(kUnusuallyLongTimeout);
        });
524
        expect(log, <String>['VMServiceFlutterDriver: waitFor message is taking a long time to complete...']);
525 526 527
      });

      test('local custom timeout', () async {
528
        log.clear();
529
        fakeClient.artificialExtensionDelay = Completer<void>().future;
530 531 532 533 534
        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);
535
        });
536
        expect(log, <String>['VMServiceFlutterDriver: waitFor message is taking a long time to complete...']);
537 538 539
      });

      test('remote error', () async {
540 541 542
        fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{
          'message': 'This is a failure',
        }, isError: true);
543 544 545
        try {
          await driver.waitFor(find.byTooltip('foo'));
          fail('expected an exception');
546
        } catch (error) {
Dan Field's avatar
Dan Field committed
547
          expect(error, isA<DriverError>());
548 549 550
          expect(error.message, 'Error in Flutter application: {message: This is a failure}');
        }
      });
551 552

      test('uncaught remote error', () async {
553 554 555
        fakeClient.artificialExtensionDelay = Future<void>.error(
          vms.RPCError('callServiceExtension', 9999, 'test error'),
        );
556 557 558

        expect(driver.waitFor(find.byTooltip('foo')), throwsDriverError);
      });
559
    });
560 561 562 563 564 565 566 567 568 569

    group('VMServiceFlutterDriver Unsupported error', () {
      test('enableAccessibility', () async {
        expect(driver.enableAccessibility(), throwsA(isA<UnsupportedError>()));
      });

      test('webDriver', () async {
        expect(() => driver.webDriver, throwsA(isA<UnsupportedError>()));
      });
    });
570
  });
571

572
  group('VMServiceFlutterDriver with custom timeout', () {
573 574 575
    FakeVmService fakeClient;
    FakeVM fakeVM;
    FakeIsolate fakeIsolate;
576
    VMServiceFlutterDriver driver;
577 578

    setUp(() {
579 580 581 582 583
      fakeIsolate = FakeIsolate();
      fakeVM = FakeVM(fakeIsolate);
      fakeClient = FakeVmService(fakeVM);
      driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate);
      fakeClient.responses['get_health'] = makeFakeResponse(<String, dynamic>{'status': 'ok'});
584 585
    });

586
    test('GetHealth has no default timeout', () async {
587
      await driver.checkHealth();
588 589 590 591
      expect(
        fakeClient.commandLog,
        <String>['ext.flutter.driver {command: get_health}'],
      );
592 593
    });

594
    test('does not interfere with explicit timeouts', () async {
595
      await driver.checkHealth(timeout: _kTestTimeout);
596 597 598 599
      expect(
        fakeClient.commandLog,
        <String>['ext.flutter.driver {command: get_health, timeout: $_kSerializedTestTimeout}'],
      );
600 601
    });
  });
602 603

  group('WebFlutterDriver', () {
604
    FakeFlutterWebConnection fakeConnection;
605 606 607
    WebFlutterDriver driver;

    setUp(() {
608 609 610
      fakeConnection = FakeFlutterWebConnection();
      fakeConnection.supportsTimelineAction = true;
      driver = WebFlutterDriver.connectedTo(fakeConnection);
611 612 613 614 615 616 617 618 619
    });

    test('closes connection', () async {
      await driver.close();
    });

    group('ByValueKey', () {
      test('restricts value types', () async {
        expect(() => find.byValueKey(null),
Dan Field's avatar
Dan Field committed
620
            throwsDriverError);
621 622 623
      });

      test('finds by ValueKey', () async {
624
        fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
625
        await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout);
626 627 628
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"ByValueKey","keyValueString":"foo","keyValueType":"String"}') 0:00:01.234000''',
        ]);
629 630 631 632 633
      });
    });

    group('BySemanticsLabel', () {
      test('finds by Semantic label using String', () async {
634
        fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
635
        await driver.tap(find.bySemanticsLabel('foo'), timeout: _kTestTimeout);
636 637 638
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"BySemanticsLabel","label":"foo"}') 0:00:01.234000''',
        ]);
639 640 641
      });

      test('finds by Semantic label using RegExp', () async {
642
        fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
643
        await driver.tap(find.bySemanticsLabel(RegExp('^foo')), timeout: _kTestTimeout);
644 645 646
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"BySemanticsLabel","label":"^foo","isRegExp":"true"}') 0:00:01.234000''',
        ]);
647 648 649 650 651
      });
    });

    group('tap', () {
      test('requires a target reference', () async {
652
        expect(driver.tap(null), throwsAssertionError);
653 654 655
      });

      test('sends the tap command', () async {
656
        fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
657
        await driver.tap(find.text('foo'), timeout: _kTestTimeout);
658 659 660
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"ByText","text":"foo"}') 0:00:01.234000''',
        ]);
661 662 663 664 665
      });
    });

    group('getText', () {
      test('requires a target reference', () async {
666
        expect(driver.getText(null), throwsAssertionError);
667 668 669
      });

      test('sends the getText command', () async {
670
        fakeConnection.responses['get_text'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'}));
671 672
        final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, 'hello');
673 674 675
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_text","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int"}') 0:00:01.234000''',
        ]);
676 677 678 679 680
      });
    });

    group('waitFor', () {
      test('requires a target reference', () async {
681
        expect(driver.waitFor(null), throwsAssertionError);
682 683 684
      });

      test('sends the waitFor command', () async {
685
        fakeConnection.responses['waitFor'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'}));
686
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
687 688 689
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitFor","timeout":"1234","finderType":"ByTooltipMessage","text":"foo"}') 0:00:01.234000''',
        ]);
690 691 692 693
      });
    });

    group('waitForCondition', () {
694 695 696 697
      setUp(() {
        fakeConnection.responses['waitForCondition'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'}));
      });

698 699
      test('sends the wait for NoPendingFrameCondition command', () async {
        await driver.waitForCondition(const NoPendingFrame(), timeout: _kTestTimeout);
700 701 702
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"NoPendingFrameCondition"}') 0:00:01.234000''',
        ]);
703 704 705 706
      });

      test('sends the wait for NoPendingPlatformMessages command', () async {
        await driver.waitForCondition(const NoPendingPlatformMessages(), timeout: _kTestTimeout);
707 708 709
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"NoPendingPlatformMessagesCondition"}') 0:00:01.234000''',
        ]);
710 711 712
      });

      test('sends the waitForCondition of combined conditions command', () async {
713 714 715
        const SerializableWaitCondition combinedCondition = CombinedCondition(
          <SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()],
        );
716
        await driver.waitForCondition(combinedCondition, timeout: _kTestTimeout);
717 718 719
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"CombinedCondition","conditions":"[{\"conditionName\":\"NoPendingFrameCondition\"},{\"conditionName\":\"NoTransientCallbacksCondition\"}]"}') 0:00:01.234000''',
        ]);
720 721 722 723 724
      });
    });

    group('waitUntilNoTransientCallbacks', () {
      test('sends the waitUntilNoTransientCallbacks command', () async {
725
        fakeConnection.responses['waitForCondition'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
726
        await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout);
727 728 729
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"NoTransientCallbacksCondition"}') 0:00:01.234000''',
        ]);
730 731 732 733
      });
    });

    group('getOffset', () {
734 735 736 737 738 739
      setUp(() {
        fakeConnection.responses['get_offset'] = jsonEncode(makeFakeResponse(<String, double>{
          'dx': 11,
          'dy': 12,
        }));
      });
740
      test('requires a target reference', () async {
741 742 743 744 745
        expect(driver.getCenter(null), throwsAssertionError);
        expect(driver.getTopLeft(null), throwsAssertionError);
        expect(driver.getTopRight(null), throwsAssertionError);
        expect(driver.getBottomLeft(null), throwsAssertionError);
        expect(driver.getBottomRight(null), throwsAssertionError);
746 747 748 749 750
      });

      test('sends the getCenter command', () async {
        final DriverOffset result = await driver.getCenter(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
751 752 753
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"center"}') 0:00:01.234000''',
        ]);
754 755 756 757 758
      });

      test('sends the getTopLeft command', () async {
        final DriverOffset result = await driver.getTopLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
759 760 761
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"topLeft"}') 0:00:01.234000''',
        ]);
762 763 764 765 766
      });

      test('sends the getTopRight command', () async {
        final DriverOffset result = await driver.getTopRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
767 768 769
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"topRight"}') 0:00:01.234000''',
        ]);
770 771 772 773 774
      });

      test('sends the getBottomLeft command', () async {
        final DriverOffset result = await driver.getBottomLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
775 776 777
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"bottomLeft"}') 0:00:01.234000''',
        ]);
778 779 780 781 782
      });

      test('sends the getBottomRight command', () async {
        final DriverOffset result = await driver.getBottomRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
783 784 785
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"bottomRight"}') 0:00:01.234000''',
        ]);
786 787 788 789
      });
    });

    test('checks the health of the driver extension', () async {
790
      fakeConnection.responses['get_health'] = jsonEncode(makeFakeResponse(<String, dynamic>{'status': 'ok'}));
791
      await driver.checkHealth();
792 793 794
      expect(fakeConnection.commandLog, <String>[
        r'''window.$flutterDriver('{"command":"get_health"}') null''',
      ]);
795 796
    });

797
    group('WebFlutterDriver Unimplemented/Unsupported error', () {
798 799
      test('forceGC', () async {
        expect(driver.forceGC(),
Dan Field's avatar
Dan Field committed
800
            throwsA(isA<UnimplementedError>()));
801 802 803 804
      });

      test('getVmFlags', () async {
        expect(driver.getVmFlags(),
Dan Field's avatar
Dan Field committed
805
            throwsA(isA<UnimplementedError>()));
806 807 808 809
      });

      test('waitUntilFirstFrameRasterized', () async {
        expect(driver.waitUntilFirstFrameRasterized(),
Dan Field's avatar
Dan Field committed
810
            throwsA(isA<UnimplementedError>()));
811 812 813
      });

      test('appIsoloate', () async {
814
        expect(() => driver.appIsolate.extensionRPCs,
Dan Field's avatar
Dan Field committed
815
            throwsA(isA<UnsupportedError>()));
816 817 818 819
      });

      test('serviceClient', () async {
        expect(() => driver.serviceClient.getVM(),
Dan Field's avatar
Dan Field committed
820
            throwsA(isA<UnsupportedError>()));
821 822 823 824 825
      });
    });
  });

  group('WebFlutterDriver with non-chrome browser', () {
826
    FakeFlutterWebConnection fakeConnection;
827 828 829
    WebFlutterDriver driver;

    setUp(() {
830 831
      fakeConnection = FakeFlutterWebConnection();
      driver = WebFlutterDriver.connectedTo(fakeConnection);
832 833 834 835
    });

    test('tracing', () async {
      expect(driver.traceAction(() async { return Future<dynamic>.value(); }),
836
          throwsA(isA<UnsupportedError>()));
837
      expect(driver.startTracing(),
838
          throwsA(isA<UnsupportedError>()));
839
      expect(driver.stopTracingAndDownloadTimeline(),
840
          throwsA(isA<UnsupportedError>()));
841
      expect(driver.clearTimeline(),
842
          throwsA(isA<UnsupportedError>()));
843 844 845 846 847 848 849 850 851
    });
  });
}

/// 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
852
  expect(script, isA<String>());
853 854 855 856
  expect(script.startsWith(_kWebScriptPrefix), isTrue);
  expect(script.endsWith(_kWebScriptSuffix), isTrue);
  // Strip prefix and suffix
  return script.substring(_kWebScriptPrefix.length, script.length - 2) as String;
857 858
}

859
vms.Response makeFakeResponse(
860 861 862
  Map<String, dynamic> response, {
  bool isError = false,
}) {
863
  return vms.Response.parse(<String, dynamic>{
864
    'isError': isError,
865
    'response': response,
866 867 868
  });
}

869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 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 1005 1006
class FakeFlutterWebConnection extends Fake implements FlutterWebConnection {
  @override
  bool supportsTimelineAction = false;

  Map<String, dynamic> responses = <String, dynamic>{};
  List<String> commandLog = <String>[];
  @override
  Future<dynamic> sendCommand(String script, Duration duration) async {
    commandLog.add('$script $duration');
    final Map<String, dynamic> decoded = jsonDecode(_checkAndEncode(script)) as Map<String, dynamic>;
    final dynamic response = responses[decoded['command']];
    assert(response != null, 'Missing ${decoded['command']} in responses.');
    return response;
  }

  @override
  Future<void> close() async {
    return;
  }
}

class FakeVmService extends Fake implements vms.VmService {
  FakeVmService(this.vm);

  FakeVM vm;
  bool failOnSetFlag = false;
  bool failOnResumeWith101 = false;

  final List<String> connectionLog = <String>[];

  @override
  Future<vms.VM> getVM() async => vm;

  @override
  Future<vms.Isolate> getIsolate(String isolateId) async {
    connectionLog.add('getIsolate');
    if (isolateId == vm.isolate.id) {
      return vm.isolate;
    }
    return null;
  }

  @override
  Future<vms.Success> resume(String isolateId, {String step, int frameIndex}) async {
    assert(isolateId == vm.isolate.id);
    connectionLog.add('resume');
    if (failOnResumeWith101) {
      throw vms.RPCError('resume', 101, '');
    }
    return vms.Success();
  }

  @override
  Future<vms.Success> streamListen(String streamId) async {
    connectionLog.add('streamListen $streamId');
    return vms.Success();
  }

  @override
  Future<vms.Success> streamCancel(String streamId) async {
    connectionLog.add('streamCancel $streamId');
    return vms.Success();
  }

  @override
  Future<vms.Response> setFlag(String name, String value) async {
    connectionLog.add('setFlag $name $value');
    if (failOnSetFlag) {
      throw Exception('setFlag failed');
    }
    return vms.Success();
  }

  @override
  Stream<vms.Event> get onIsolateEvent async* {
    connectionLog.add('onIsolateEvent');
    yield vms.Event(
      kind: vms.EventKind.kServiceExtensionAdded,
      extensionRPC: 'ext.flutter.driver',
      timestamp: 0,
    );
  }

  List<String> commandLog = <String>[];
  Map<String, vms.Response> responses = <String, vms.Response>{};
  Future<void> artificialExtensionDelay;

  @override
  Future<vms.Response> callServiceExtension(String method, {Map<dynamic, dynamic> args, String isolateId}) async {
    commandLog.add('$method $args');
    await artificialExtensionDelay;

    final vms.Response response = responses[args['command']];
    assert(response != null, 'Failed to create a response for ${args['command']}');
    return response;
  }

  @override
  Future<vms.Success> clearVMTimeline() async {
    connectionLog.add('clearVMTimeline');
    return vms.Success();
  }

  @override
  Future<vms.FlagList> getFlagList() async {
    connectionLog.add('getFlagList');
    return vms.FlagList(flags: <vms.Flag>[]);
  }

  int vmTimelineMicros = -1000000;
  bool incrementMicros = false;

  @override
  Future<vms.Timestamp> getVMTimelineMicros() async {
    connectionLog.add('getVMTimelineMicros');
    if (incrementMicros || vmTimelineMicros < 0) {
      vmTimelineMicros = vmTimelineMicros + 1000001;
    }
    return vms.Timestamp(timestamp: vmTimelineMicros);
  }

  @override
  Future<vms.Success> setVMTimelineFlags(List<String> recordedStreams) async {
    connectionLog.add('setVMTimelineFlags $recordedStreams');
    return vms.Success();
  }

  final Map<int, vms.Timeline> timelineResponses = <int, vms.Timeline>{
    1: vms.Timeline.parse(<String, dynamic>{
      'traceEvents': <dynamic>[
        <String, dynamic>{
          'name': 'test event',
        },
      ],
      'timeOriginMicros': 0,
      'timeExtentMicros': 999999,
    }),
  };
1007

1008 1009 1010 1011 1012 1013 1014
  @override
  Future<vms.Timeline> getVMTimeline({int timeOriginMicros, int timeExtentMicros}) async {
    connectionLog.add('getVMTimeline $timeOriginMicros $timeExtentMicros');
    final vms.Timeline timeline = timelineResponses[timeOriginMicros ?? 1];
    assert(timeline != null, 'Missing entry in timelineResponses[$timeOriginMicros]');
    return timeline;
  }
1015

1016
  @override
1017
  Future<void> dispose() async {
1018 1019
    connectionLog.add('dispose');
  }
1020

1021 1022 1023
  @override
  Future<void> get onDone async {}
}
1024

1025 1026
class FakeVM extends Fake implements vms.VM {
  FakeVM(this.isolate);
1027

1028
  vms.Isolate isolate;
1029

1030
  int numberOfTriesBeforeResolvingIsolate = 0;
1031

1032
  @override
1033 1034 1035 1036 1037 1038 1039
  List<vms.IsolateRef> get isolates {
    numberOfTriesBeforeResolvingIsolate -= 1;
    return <vms.Isolate>[
      if (numberOfTriesBeforeResolvingIsolate <= 0)
        isolate,
    ];
  }
1040
}
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054

class FakeIsolate extends Fake implements vms.Isolate {
  @override
  String get number => '123';

  @override
  String get id => number;

  @override
  vms.Event pauseEvent;

  @override
  List<String> get extensionRPCs => <String>[];
}