flutter_driver_test.dart 47.3 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
import 'dart:io';
8

9
import 'package:fake_async/fake_async.dart';
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

18
import '../../common.dart';
19

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

26
void main() {
27
  final List<String> log = <String>[];
28

29 30 31 32
  driverLog = (String source, String message) {
    log.add('$source: $message');
  };

33 34 35
  group('VMServiceFlutterDriver with logCommunicationToFile', () {
    late FakeVmService fakeClient;
    late FakeVM fakeVM;
36
    late vms.Isolate fakeIsolate;
37
    late VMServiceFlutterDriver driver;
38
    late File logFile;
39 40

    setUp(() {
41
      fakeIsolate = createFakeIsolate();
42 43 44 45 46
      fakeVM = FakeVM(fakeIsolate);
      fakeClient = FakeVmService(fakeVM);
      fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{'status':'ok'});
    });

47 48 49 50 51
    tearDown(() {
      if (logFile.existsSync()) {
        logFile.deleteSync();
      }
    });
52 53 54 55

    group('logCommunicationToFile', () {
      test('logCommunicationToFile = true', () async {
        driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate);
56
        logFile = File(driver.logFilePathName);
57 58 59

        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);

60 61
        final bool exists = logFile.existsSync();
        expect(exists, true, reason: 'Not found ${logFile.path}');
62

63
        final String commandLog = await logFile.readAsString();
64 65 66 67 68 69 70 71 72
        const String waitForCommandLog = '>>> {command: waitFor, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo}';
        const String responseLog = '<<< {isError: false, response: {status: ok}}';

        expect(commandLog.contains(waitForCommandLog), true, reason: '$commandLog not contains $waitForCommandLog');
        expect(commandLog.contains(responseLog), true, reason: '$commandLog not contains $responseLog');
      });

      test('logCommunicationToFile = false', () async {
        driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate, logCommunicationToFile: false);
73 74 75 76 77
        logFile = File(driver.logFilePathName);
        // clear log file if left in filetree from previous run
        if (logFile.existsSync()) {
          logFile.deleteSync();
        }
78 79
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);

80 81
        final bool exists = logFile.existsSync();
        expect(exists, false, reason: 'because ${logFile.path} exists');
82
      });
83 84

      test('logFilePathName was set when a new driver was created', () {
85
        driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate);
86 87 88
        logFile = File(driver.logFilePathName);
        expect(logFile.path, endsWith('.log'));
      });
89 90 91 92 93 94
    });
  });

  group('VMServiceFlutterDriver with printCommunication', () {
    late FakeVmService fakeClient;
    late FakeVM fakeVM;
95
    late vms.Isolate fakeIsolate;
96 97 98 99
    late VMServiceFlutterDriver driver;

    setUp(() async {
      log.clear();
100
      fakeIsolate = createFakeIsolate();
101 102 103 104 105 106 107 108 109 110
      fakeVM = FakeVM(fakeIsolate);
      fakeClient = FakeVmService(fakeVM);
      fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{'status':'ok'});
    });

    test('printCommunication = true', () async {
      driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate, printCommunication: true);
      await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
      expect(log, <String>[
        'VMServiceFlutterDriver: >>> {command: waitFor, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo}',
111
        'VMServiceFlutterDriver: <<< {isError: false, response: {status: ok}}',
112 113 114 115
      ]);
    });

    test('printCommunication = false', () async {
116
      driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate);
117 118 119 120 121
      await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
      expect(log, <String>[]);
    });
  });

122
  group('VMServiceFlutterDriver.connect', () {
123 124
    late FakeVmService fakeClient;
    late FakeVM fakeVM;
125
    late vms.Isolate fakeIsolate;
126

127
    void expectLogContains(String message) {
128
      expect(log, anyElement(contains(message)));
129
    }
130

131
    setUp(() {
132
      log.clear();
133
      fakeIsolate = createFakeIsolate();
134 135
      fakeVM = FakeVM(fakeIsolate);
      fakeClient = FakeVmService(fakeVM);
136
      vmServiceConnectFunction = (String url, Map<String, dynamic>? headers) async {
137
        return fakeClient;
138
      };
139
      fakeClient.responses['get_health'] = makeFakeResponse(<String, dynamic>{'status': 'ok'});
140 141 142
    });

    tearDown(() async {
yjbanov's avatar
yjbanov committed
143
      restoreVmServiceConnectFunction();
144 145
    });

146 147 148 149 150 151 152 153 154 155 156 157 158
    test('Retries while Dart VM service is not available', () async {
      // This test case will test the real implementation of `_waitAndConnect`.
      restoreVmServiceConnectFunction();

      // The actual behavior is to retry indefinitely until the Dart VM service
      // becomes available. `.timeout` is used here to exit the infinite loop,
      // expecting that no other types of error are thrown during the process.
      expect(
        vmServiceConnectFunction('http://foo.bar', <String, dynamic>{})
            .timeout(const Duration(seconds: 1)),
        throwsA(isA<TimeoutException>()),
      );
    });
159 160 161 162 163 164

    test('throws after retries if no isolate', () async {
      fakeVM.numberOfTriesBeforeResolvingIsolate = 10000;
      FakeAsync().run((FakeAsync time) {
        FlutterDriver.connect(dartVmServiceUrl: '');
        time.elapse(kUnusuallyLongTimeout);
165
      });
166 167
      expect(log, <String>[
        'VMServiceFlutterDriver: Connecting to Flutter application at ',
168
        'VMServiceFlutterDriver: The root isolate is taking an unusually long time to start.',
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
      ]);
    });

    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);
193
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '', isolateNumber: int.parse(fakeIsolate.number!));
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
      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);
211

212
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
213
      expect(driver, isNotNull);
214
      expectLogContains('Isolate is paused at start');
215 216 217 218 219 220 221 222 223 224 225 226
      expect(
        fakeClient.connectionLog,
        <String>[
          'getIsolate',
          'setFlag pause_isolates_on_start false',
          'resume',
          'streamListen Isolate',
          'getIsolate',
          'onIsolateEvent',
          'streamCancel Isolate',
        ],
      );
227 228
    });

229
    test('ignores setFlag failure', () async {
230 231
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0);
      fakeClient.failOnSetFlag = true;
232 233 234 235 236 237 238 239

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


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

243
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
244
      expect(driver, isNotNull);
245
      expectLogContains('Isolate is paused mid-flight');
246 247 248 249 250 251 252
    });

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

256
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
257 258 259 260 261
      expect(driver, isNotNull);
      expectLogContains('Attempted to resume an already resumed isolate');
    });

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

264
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
265 266 267
      expect(driver, isNotNull);
      expectLogContains('Isolate is not paused. Assuming application is ready.');
    });
268

269 270
    test('connects to unpaused when onExtensionAdded does not contain the '
      'driver extension', () async {
271
      fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kResume, timestamp: 0);
272
      fakeIsolate.extensionRPCs!.add('ext.flutter.driver');
273

274 275 276 277
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
      expect(driver, isNotNull);
      expectLogContains('Isolate is not paused. Assuming application is ready.');
    });
278 279
  });

280
  group('VMServiceFlutterDriver', () {
281
    late FakeVmService fakeClient;
282
    late FakeVM fakeVM;
283
    late vms.Isolate fakeIsolate;
284
    late VMServiceFlutterDriver driver;
285 286

    setUp(() {
287
      fakeIsolate = createFakeIsolate();
288 289 290 291
      fakeVM = FakeVM(fakeIsolate);
      fakeClient = FakeVmService(fakeVM);
      driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate);
      fakeClient.responses['tap'] = makeFakeResponse(<String, dynamic>{});
292 293 294
    });

    test('checks the health of the driver extension', () async {
295
      fakeClient.responses['get_health'] = makeFakeResponse(<String, dynamic>{'status': 'ok'});
296
      final Health result = await driver.checkHealth();
297 298 299 300 301
      expect(result.status, HealthStatus.ok);
    });

    test('closes connection', () async {
      await driver.close();
302
      expect(fakeClient.connectionLog.last, 'dispose');
303 304
    });

305
    group('ByValueKey', () {
306
      test('restricts value types', () async {
Dan Field's avatar
Dan Field committed
307
        expect(() => find.byValueKey(null), throwsDriverError);
308 309 310
      });

      test('finds by ValueKey', () async {
311
        await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout);
312 313 314
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: ByValueKey, keyValueString: foo, keyValueType: String}',
        ]);
315 316 317
      });
    });

318 319 320
    group('BySemanticsLabel', () {
      test('finds by Semantic label using String', () async {
        await driver.tap(find.bySemanticsLabel('foo'), timeout: _kTestTimeout);
321 322 323
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: BySemanticsLabel, label: foo}',
        ]);
324 325 326 327
      });

      test('finds by Semantic label using RegExp', () async {
        await driver.tap(find.bySemanticsLabel(RegExp('^foo')), timeout: _kTestTimeout);
328 329 330
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: BySemanticsLabel, label: ^foo, isRegExp: true}',
        ]);
331 332 333
      });
    });

334 335
    group('tap', () {
      test('sends the tap command', () async {
336
        await driver.tap(find.text('foo'), timeout: _kTestTimeout);
337 338 339
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: ByText, text: foo}',
        ]);
340 341 342 343 344
      });
    });

    group('getText', () {
      test('sends the getText command', () async {
345
        fakeClient.responses['get_text'] = makeFakeResponse(<String, dynamic>{'text': 'hello'});
346
        final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout);
347
        expect(result, 'hello');
348 349 350
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: get_text, timeout: $_kSerializedTestTimeout, finderType: ByValueKey, keyValueString: 123, keyValueType: int}',
        ]);
351 352
      });
    });
353

354 355
    group('getLayerTree', () {
      test('sends the getLayerTree command', () async {
356 357
        fakeClient.responses['get_layer_tree'] = makeFakeResponse(<String, String>{
          'tree': 'hello',
358 359 360 361 362 363
        });
        final LayerTree result = await driver.getLayerTree(timeout: _kTestTimeout);
        final LayerTree referenceTree = LayerTree.fromJson(<String, String>{
            'tree': 'hello',
          });
        expect(result.tree, referenceTree.tree);
364 365 366
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: get_layer_tree, timeout: $_kSerializedTestTimeout}',
        ]);
367 368 369
      });
    });

370
    group('waitFor', () {
371
      test('sends the waitFor command', () async {
372
        fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{});
373
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
374 375 376
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitFor, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo}',
        ]);
377 378
      });
    });
379

380 381
    group('getWidgetDiagnostics', () {
      test('sends the getWidgetDiagnostics command', () async {
382
        fakeClient.responses['get_diagnostics_tree'] = makeFakeResponse(<String, dynamic>{});
383
        await driver.getWidgetDiagnostics(find.byTooltip('foo'), timeout: _kTestTimeout);
384 385 386
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: get_diagnostics_tree, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo, subtreeDepth: 0, includeProperties: true, diagnosticsType: widget}',
        ]);
387 388 389 390 391
      });
    });

    group('getRenderObjectDiagnostics', () {
      test('sends the getRenderObjectDiagnostics command', () async {
392
        fakeClient.responses['get_diagnostics_tree'] = makeFakeResponse(<String, dynamic>{});
393
        await driver.getRenderObjectDiagnostics(find.byTooltip('foo'), timeout: _kTestTimeout);
394 395 396
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: get_diagnostics_tree, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo, subtreeDepth: 0, includeProperties: true, diagnosticsType: renderObject}',
        ]);
397 398 399
      });
    });

400 401
    group('waitForCondition', () {
      test('sends the wait for NoPendingFrameCondition command', () async {
402
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
403
        await driver.waitForCondition(const NoPendingFrame(), timeout: _kTestTimeout);
404 405 406
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: NoPendingFrameCondition}',
        ]);
407 408
      });

409
      test('sends the wait for NoPendingPlatformMessages command', () async {
410
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
411
        await driver.waitForCondition(const NoPendingPlatformMessages(), timeout: _kTestTimeout);
412 413 414
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: NoPendingPlatformMessagesCondition}',
        ]);
415 416
      });

417
      test('sends the waitForCondition of combined conditions command', () async {
418
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
419 420 421
        const SerializableWaitCondition combinedCondition =
            CombinedCondition(<SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()]);
        await driver.waitForCondition(combinedCondition, timeout: _kTestTimeout);
422 423 424
         expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: CombinedCondition, conditions: [{"conditionName":"NoPendingFrameCondition"},{"conditionName":"NoTransientCallbacksCondition"}]}',
        ]);
425 426 427
      });
    });

428 429
    group('waitUntilNoTransientCallbacks', () {
      test('sends the waitUntilNoTransientCallbacks command', () async {
430
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
431
        await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout);
432 433 434
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: NoTransientCallbacksCondition}',
        ]);
435 436
      });
    });
437 438 439

    group('waitUntilFirstFrameRasterized', () {
      test('sends the waitUntilFirstFrameRasterized command', () async {
440
        fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{});
441
        await driver.waitUntilFirstFrameRasterized();
442 443 444
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: waitForCondition, conditionName: FirstFrameRasterizedCondition}',
        ]);
445 446
      });
    });
447

448
    group('getOffset', () {
449 450 451 452 453 454 455
      setUp(() {
        fakeClient.responses['get_offset'] = makeFakeResponse(<String, double>{
          'dx': 11,
          'dy': 12,
        });
      });

456 457 458
      test('sends the getCenter command', () async {
        final DriverOffset result = await driver.getCenter(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
459 460 461
        expect(fakeClient.commandLog, <String>[
           'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: center}',
        ]);
462 463 464 465 466
      });

      test('sends the getTopLeft command', () async {
        final DriverOffset result = await driver.getTopLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
467 468 469
        expect(fakeClient.commandLog, <String>[
           'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: topLeft}',
        ]);
470 471 472 473 474
      });

      test('sends the getTopRight command', () async {
        final DriverOffset result = await driver.getTopRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
475 476 477
        expect(fakeClient.commandLog, <String>[
           'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: topRight}',
        ]);
478 479 480 481 482
      });

      test('sends the getBottomLeft command', () async {
        final DriverOffset result = await driver.getBottomLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
483 484 485
        expect(fakeClient.commandLog, <String>[
           'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: bottomLeft}',
        ]);
486 487 488 489 490
      });

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

497 498 499
    group('clearTimeline', () {
      test('clears timeline', () async {
        await driver.clearTimeline();
500
        expect(fakeClient.connectionLog, contains('clearVMTimeline'));
501 502 503
      });
    });

504
    group('traceAction', () {
505
      test('without clearing timeline', () async {
506
        final Timeline timeline = await driver.traceAction(() async {
507
          fakeClient.connectionLog.add('action');
508 509
        }, retainPriorEvents: true);

510 511
        expect(fakeClient.connectionLog, const <String>[
          'setVMTimelineFlags [all]',
512
          'action',
513 514 515
          'getFlagList',
          'setVMTimelineFlags []',
          'getVMTimeline null null',
516
        ]);
517
        expect(timeline.events!.single.name, 'test event');
518 519 520
      });

      test('with clearing timeline', () async {
521
        final Timeline timeline = await driver.traceAction(() async {
522
          fakeClient.connectionLog.add('action');
523 524
        });

525 526
        expect(fakeClient.connectionLog, const <String>[
          'clearVMTimeline',
527
          'getVMTimelineMicros',
528
          'setVMTimelineFlags [all]',
529
          'action',
530
          'getVMTimelineMicros',
531 532 533
          'getFlagList',
          'setVMTimelineFlags []',
          'getVMTimeline 1 999999',
534
        ]);
535
        expect(timeline.events!.single.name, 'test event');
536
      });
537 538

      test('with time interval', () async {
539 540 541 542 543 544 545
        fakeClient.incrementMicros = true;
        fakeClient.timelineResponses[1000001] = vms.Timeline.parse(<String, dynamic>{
          'traceEvents': <dynamic>[
            <String, dynamic>{
              'name': 'test event 2',
            },
          ],
546 547
          'timeOriginMicros': 1000000,
          'timeExtentMicros': 999999,
548
        });
549
        final Timeline timeline = await driver.traceAction(() async {
550
          fakeClient.connectionLog.add('action');
551 552
        });

553 554
        expect(fakeClient.connectionLog, const <String>[
          'clearVMTimeline',
555
          'getVMTimelineMicros',
556
          'setVMTimelineFlags [all]',
557 558
          'action',
          'getVMTimelineMicros',
559 560 561 562
          'getFlagList',
          'setVMTimelineFlags []',
          'getVMTimeline 1 999999',
          'getVMTimeline 1000001 999999',
563
        ]);
564
        expect(timeline.events!.map((TimelineEvent event) => event.name), <String>[
565
          'test event',
566 567 568
          'test event 2',
        ]);
      });
569
    });
570

571 572
    group('traceAction with timeline streams', () {
      test('specify non-default timeline streams', () async {
573 574
        bool actionCalled = false;

575
        final Timeline timeline = await driver.traceAction(() async {
576 577
          actionCalled = true;
        },
578 579 580
        streams: const <TimelineStream>[
          TimelineStream.dart,
          TimelineStream.gc,
581
          TimelineStream.compiler,
582 583
        ],
        retainPriorEvents: true);
584 585

        expect(actionCalled, isTrue);
586 587 588 589
        expect(fakeClient.connectionLog, <String>[
          'setVMTimelineFlags [Dart, GC, Compiler]',
          'getFlagList',
          'setVMTimelineFlags []',
590
          'getVMTimeline null null',
591 592
        ]);

593
        expect(timeline.events!.single.name, 'test event');
594 595
      });
    });
596 597

    group('sendCommand error conditions', () {
598
      test('local default timeout', () async {
599
        log.clear();
600
        fakeClient.artificialExtensionDelay = Completer<void>().future;
601 602 603
        FakeAsync().run((FakeAsync time) {
          driver.waitFor(find.byTooltip('foo'));
          expect(log, <String>[]);
604 605
          time.elapse(kUnusuallyLongTimeout);
        });
606
        expect(log, <String>['VMServiceFlutterDriver: waitFor message is taking a long time to complete...']);
607 608 609
      });

      test('local custom timeout', () async {
610
        log.clear();
611
        fakeClient.artificialExtensionDelay = Completer<void>().future;
612 613 614 615 616
        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);
617
        });
618
        expect(log, <String>['VMServiceFlutterDriver: waitFor message is taking a long time to complete...']);
619 620 621
      });

      test('remote error', () async {
622 623 624
        fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{
          'message': 'This is a failure',
        }, isError: true);
625 626 627 628 629 630 631 632
        await expectLater(
          () => driver.waitFor(find.byTooltip('foo')),
          throwsA(isA<DriverError>().having(
            (DriverError error) => error.message,
            'message',
            'Error in Flutter application: {message: This is a failure}',
          )),
        );
633
      });
634 635

      test('uncaught remote error', () async {
636 637 638
        fakeClient.artificialExtensionDelay = Future<void>.error(
          vms.RPCError('callServiceExtension', 9999, 'test error'),
        );
639 640 641

        expect(driver.waitFor(find.byTooltip('foo')), throwsDriverError);
      });
642
    });
643

644 645 646 647 648 649 650 651 652
    group('setSemantics', () {
      test('can be enabled', () async {
        fakeClient.responses['set_semantics'] = makeFakeResponse(<String, Object>{
          'changedState': true,
        });
        await driver.setSemantics(true, timeout: _kTestTimeout);
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: set_semantics, timeout: $_kSerializedTestTimeout, enabled: true}',
        ]);
653 654
      });

655 656 657 658 659 660 661 662
      test('can be disabled', () async {
        fakeClient.responses['set_semantics'] = makeFakeResponse(<String, Object>{
          'changedState': false,
        });
        await driver.setSemantics(false, timeout: _kTestTimeout);
        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: set_semantics, timeout: $_kSerializedTestTimeout, enabled: false}',
        ]);
663 664
      });
    });
665 666 667 668

    test('VMServiceFlutterDriver does not support webDriver', () async {
      expect(() => driver.webDriver, throwsUnsupportedError);
    });
669 670 671 672 673 674 675 676 677 678 679 680 681

    group('runUnsynchronized', () {
      test('wrap waitFor with runUnsynchronized', () async {
        fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{});
        fakeClient.responses['set_frame_sync'] = makeFakeResponse(<String, dynamic>{});

        await driver.runUnsynchronized(() async  {
          await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
        });

        expect(fakeClient.commandLog, <String>[
          'ext.flutter.driver {command: set_frame_sync, enabled: false}',
          'ext.flutter.driver {command: waitFor, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo}',
682
          'ext.flutter.driver {command: set_frame_sync, enabled: true}',
683 684 685
        ]);
      });
    });
686
  });
687

688
  group('VMServiceFlutterDriver with custom timeout', () {
689
    late FakeVmService fakeClient;
690
    late FakeVM fakeVM;
691
    late vms.Isolate fakeIsolate;
692
    late VMServiceFlutterDriver driver;
693 694

    setUp(() {
695
      fakeIsolate = createFakeIsolate();
696 697 698 699
      fakeVM = FakeVM(fakeIsolate);
      fakeClient = FakeVmService(fakeVM);
      driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate);
      fakeClient.responses['get_health'] = makeFakeResponse(<String, dynamic>{'status': 'ok'});
700 701
    });

702
    test('GetHealth has no default timeout', () async {
703
      await driver.checkHealth();
704 705 706 707
      expect(
        fakeClient.commandLog,
        <String>['ext.flutter.driver {command: get_health}'],
      );
708 709
    });

710
    test('does not interfere with explicit timeouts', () async {
711
      await driver.checkHealth(timeout: _kTestTimeout);
712 713 714 715
      expect(
        fakeClient.commandLog,
        <String>['ext.flutter.driver {command: get_health, timeout: $_kSerializedTestTimeout}'],
      );
716 717
    });
  });
718

719 720 721
  group('WebFlutterDriver with logCommunicationToFile', () {
    late FakeFlutterWebConnection fakeConnection;
    late WebFlutterDriver driver;
722
    late File logFile;
723 724 725 726 727

    setUp(() {
      fakeConnection = FakeFlutterWebConnection();
      fakeConnection.supportsTimelineAction = true;
      fakeConnection.responses['waitFor'] = jsonEncode(makeFakeResponse(<String, dynamic>{'status': 'ok'}));
728 729 730 731 732 733
    });

    tearDown(() {
      if (logFile.existsSync()) {
        logFile.deleteSync();
      }
734 735 736 737
    });

    test('logCommunicationToFile = true', () async {
      driver = WebFlutterDriver.connectedTo(fakeConnection);
738
      logFile = File(driver.logFilePathName);
739 740
      await driver.waitFor(find.byTooltip('logCommunicationToFile test'), timeout: _kTestTimeout);

741 742
      final bool exists = logFile.existsSync();
      expect(exists, true, reason: 'Not found ${logFile.path}');
743

744
      final String commandLog = await logFile.readAsString();
745 746 747
      const String waitForCommandLog = '>>> {command: waitFor, timeout: 1234, finderType: ByTooltipMessage, text: logCommunicationToFile test}';
      const String responseLog = '<<< {isError: false, response: {status: ok}, type: Response}';

748 749
      expect(commandLog, contains(waitForCommandLog), reason: '$commandLog not contains $waitForCommandLog');
      expect(commandLog, contains(responseLog), reason: '$commandLog not contains $responseLog');
750 751 752 753
    });

    test('logCommunicationToFile = false', () async {
      driver = WebFlutterDriver.connectedTo(fakeConnection, logCommunicationToFile: false);
754 755 756 757 758
      logFile = File(driver.logFilePathName);
      // clear log file if left in filetree from previous run
      if (logFile.existsSync()) {
        logFile.deleteSync();
      }
759
      await driver.waitFor(find.byTooltip('logCommunicationToFile test'), timeout: _kTestTimeout);
760 761
      final bool exists = logFile.existsSync();
      expect(exists, false, reason: 'because ${logFile.path} exists');
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
    });
  });

  group('WebFlutterDriver with printCommunication', () {
    late FakeFlutterWebConnection fakeConnection;
    late WebFlutterDriver driver;

    setUp(() {
      log.clear();
      fakeConnection = FakeFlutterWebConnection();
      fakeConnection.supportsTimelineAction = true;
      fakeConnection.responses['waitFor'] = jsonEncode(makeFakeResponse(<String, dynamic>{'status': 'ok'}));
    });

    test('printCommunication = true', () async {
      driver = WebFlutterDriver.connectedTo(fakeConnection, printCommunication: true);
      await driver.waitFor(find.byTooltip('printCommunication test'), timeout: _kTestTimeout);
      expect(log, <String>[
        'WebFlutterDriver: >>> {command: waitFor, timeout: 1234, finderType: ByTooltipMessage, text: printCommunication test}',
        'WebFlutterDriver: <<< {isError: false, response: {status: ok}, type: Response}',
      ]);
    });

    test('printCommunication = false', () async {
786
      driver = WebFlutterDriver.connectedTo(fakeConnection);
787 788 789 790 791
      await driver.waitFor(find.byTooltip('printCommunication test'), timeout: _kTestTimeout);
      expect(log, <String>[]);
    });
  });

792
  group('WebFlutterDriver', () {
793 794
    late FakeFlutterWebConnection fakeConnection;
    late WebFlutterDriver driver;
795 796

    setUp(() {
797 798 799
      fakeConnection = FakeFlutterWebConnection();
      fakeConnection.supportsTimelineAction = true;
      driver = WebFlutterDriver.connectedTo(fakeConnection);
800 801 802 803 804 805 806 807 808
    });

    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
809
            throwsDriverError);
810 811 812
      });

      test('finds by ValueKey', () async {
813
        fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
814
        await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout);
815 816 817
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"ByValueKey","keyValueString":"foo","keyValueType":"String"}') 0:00:01.234000''',
        ]);
818 819 820 821 822
      });
    });

    group('BySemanticsLabel', () {
      test('finds by Semantic label using String', () async {
823
        fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
824
        await driver.tap(find.bySemanticsLabel('foo'), timeout: _kTestTimeout);
825 826 827
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"BySemanticsLabel","label":"foo"}') 0:00:01.234000''',
        ]);
828 829 830
      });

      test('finds by Semantic label using RegExp', () async {
831
        fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
832
        await driver.tap(find.bySemanticsLabel(RegExp('^foo')), timeout: _kTestTimeout);
833 834 835
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"BySemanticsLabel","label":"^foo","isRegExp":"true"}') 0:00:01.234000''',
        ]);
836 837 838 839 840
      });
    });

    group('tap', () {
      test('sends the tap command', () async {
841
        fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
842
        await driver.tap(find.text('foo'), timeout: _kTestTimeout);
843 844 845
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"ByText","text":"foo"}') 0:00:01.234000''',
        ]);
846 847 848 849 850
      });
    });

    group('getText', () {
      test('sends the getText command', () async {
851
        fakeConnection.responses['get_text'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'}));
852 853
        final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, 'hello');
854 855 856
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_text","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int"}') 0:00:01.234000''',
        ]);
857 858 859 860 861
      });
    });

    group('waitFor', () {
      test('sends the waitFor command', () async {
862
        fakeConnection.responses['waitFor'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'}));
863
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
864 865 866
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitFor","timeout":"1234","finderType":"ByTooltipMessage","text":"foo"}') 0:00:01.234000''',
        ]);
867 868 869 870
      });
    });

    group('waitForCondition', () {
871 872 873 874
      setUp(() {
        fakeConnection.responses['waitForCondition'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'}));
      });

875 876
      test('sends the wait for NoPendingFrameCondition command', () async {
        await driver.waitForCondition(const NoPendingFrame(), timeout: _kTestTimeout);
877 878 879
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"NoPendingFrameCondition"}') 0:00:01.234000''',
        ]);
880 881 882 883
      });

      test('sends the wait for NoPendingPlatformMessages command', () async {
        await driver.waitForCondition(const NoPendingPlatformMessages(), timeout: _kTestTimeout);
884 885 886
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"NoPendingPlatformMessagesCondition"}') 0:00:01.234000''',
        ]);
887 888 889
      });

      test('sends the waitForCondition of combined conditions command', () async {
890 891 892
        const SerializableWaitCondition combinedCondition = CombinedCondition(
          <SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()],
        );
893
        await driver.waitForCondition(combinedCondition, timeout: _kTestTimeout);
894 895 896
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"CombinedCondition","conditions":"[{\"conditionName\":\"NoPendingFrameCondition\"},{\"conditionName\":\"NoTransientCallbacksCondition\"}]"}') 0:00:01.234000''',
        ]);
897 898 899 900 901
      });
    });

    group('waitUntilNoTransientCallbacks', () {
      test('sends the waitUntilNoTransientCallbacks command', () async {
902
        fakeConnection.responses['waitForCondition'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));
903
        await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout);
904 905 906
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"NoTransientCallbacksCondition"}') 0:00:01.234000''',
        ]);
907 908 909 910
      });
    });

    group('getOffset', () {
911 912 913 914 915 916
      setUp(() {
        fakeConnection.responses['get_offset'] = jsonEncode(makeFakeResponse(<String, double>{
          'dx': 11,
          'dy': 12,
        }));
      });
917 918 919 920

      test('sends the getCenter command', () async {
        final DriverOffset result = await driver.getCenter(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
921 922 923
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"center"}') 0:00:01.234000''',
        ]);
924 925 926 927 928
      });

      test('sends the getTopLeft command', () async {
        final DriverOffset result = await driver.getTopLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
929 930 931
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"topLeft"}') 0:00:01.234000''',
        ]);
932 933 934 935 936
      });

      test('sends the getTopRight command', () async {
        final DriverOffset result = await driver.getTopRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
937 938 939
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"topRight"}') 0:00:01.234000''',
        ]);
940 941 942 943 944
      });

      test('sends the getBottomLeft command', () async {
        final DriverOffset result = await driver.getBottomLeft(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
945 946 947
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"bottomLeft"}') 0:00:01.234000''',
        ]);
948 949 950 951 952
      });

      test('sends the getBottomRight command', () async {
        final DriverOffset result = await driver.getBottomRight(find.byValueKey(123), timeout: _kTestTimeout);
        expect(result, const DriverOffset(11, 12));
953 954 955
        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"bottomRight"}') 0:00:01.234000''',
        ]);
956 957 958 959
      });
    });

    test('checks the health of the driver extension', () async {
960
      fakeConnection.responses['get_health'] = jsonEncode(makeFakeResponse(<String, dynamic>{'status': 'ok'}));
961
      await driver.checkHealth();
962 963 964
      expect(fakeConnection.commandLog, <String>[
        r'''window.$flutterDriver('{"command":"get_health"}') null''',
      ]);
965 966
    });

967
    group('WebFlutterDriver Unimplemented/Unsupported error', () {
968
      test('forceGC', () async {
969
        expect(driver.forceGC(), throwsUnimplementedError);
970 971 972
      });

      test('getVmFlags', () async {
973
        expect(driver.getVmFlags(), throwsUnimplementedError);
974 975 976
      });

      test('waitUntilFirstFrameRasterized', () async {
977
        expect(driver.waitUntilFirstFrameRasterized(), throwsUnimplementedError);
978 979
      });

980
      test('appIsolate', () async {
981
        expect(() => driver.appIsolate.extensionRPCs, throwsUnsupportedError);
982 983 984
      });

      test('serviceClient', () async {
985
        expect(() => driver.serviceClient.getVM(), throwsUnsupportedError);
986 987
      });
    });
988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004

    group('runUnsynchronized', () {
      test('wrap waitFor with runUnsynchronized', () async {
        fakeConnection.responses['waitFor'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'}));
        fakeConnection.responses['set_frame_sync'] = jsonEncode(makeFakeResponse(<String, dynamic>{}));

        await driver.runUnsynchronized(() async {
          await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
        });

        expect(fakeConnection.commandLog, <String>[
          r'''window.$flutterDriver('{"command":"set_frame_sync","enabled":"false"}') null''',
          r'''window.$flutterDriver('{"command":"waitFor","timeout":"1234","finderType":"ByTooltipMessage","text":"foo"}') 0:00:01.234000''',
          r'''window.$flutterDriver('{"command":"set_frame_sync","enabled":"true"}') null''',
        ]);
      });
    });
1005 1006 1007
  });

  group('WebFlutterDriver with non-chrome browser', () {
1008
    FakeFlutterWebConnection fakeConnection;
1009
    late WebFlutterDriver driver;
1010 1011

    setUp(() {
1012 1013
      fakeConnection = FakeFlutterWebConnection();
      driver = WebFlutterDriver.connectedTo(fakeConnection);
1014 1015 1016
    });

    test('tracing', () async {
1017 1018 1019 1020
      expect(driver.traceAction(() async { return Future<dynamic>.value(); }), throwsUnsupportedError);
      expect(driver.startTracing(), throwsUnsupportedError);
      expect(driver.stopTracingAndDownloadTimeline(), throwsUnsupportedError);
      expect(driver.clearTimeline(), throwsUnsupportedError);
1021 1022 1023 1024
    });
  });
}

1025 1026
// This function will verify the format of the script and return the actual
// script. The script will be in the following format:
1027
//   window.flutterDriver('[actual script]')
1028
String _checkAndEncode(dynamic script) {
Dan Field's avatar
Dan Field committed
1029
  expect(script, isA<String>());
1030 1031 1032
  final String scriptString = script as String;
  expect(scriptString.startsWith(_kWebScriptPrefix), isTrue);
  expect(scriptString.endsWith(_kWebScriptSuffix), isTrue);
1033
  // Strip prefix and suffix
1034
  return scriptString.substring(_kWebScriptPrefix.length, script.length - 2);
1035 1036
}

1037
vms.Response? makeFakeResponse(
1038 1039 1040
  Map<String, dynamic> response, {
  bool isError = false,
}) {
1041
  return vms.Response.parse(<String, dynamic>{
1042
    'isError': isError,
1043
    'response': response,
1044 1045 1046
  });
}

1047 1048 1049 1050 1051 1052 1053
class FakeFlutterWebConnection extends Fake implements FlutterWebConnection {
  @override
  bool supportsTimelineAction = false;

  Map<String, dynamic> responses = <String, dynamic>{};
  List<String> commandLog = <String>[];
  @override
1054
  Future<dynamic> sendCommand(String script, Duration? duration) async {
1055
    commandLog.add('$script $duration');
1056
    final Map<String, dynamic> decoded = jsonDecode(_checkAndEncode(script)) as Map<String, dynamic>;
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
    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);

1071
  FakeVM? vm;
1072 1073 1074 1075 1076 1077
  bool failOnSetFlag = false;
  bool failOnResumeWith101 = false;

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

  @override
1078
  Future<vms.VM> getVM() async => vm!;
1079 1080 1081 1082

  @override
  Future<vms.Isolate> getIsolate(String isolateId) async {
    connectionLog.add('getIsolate');
1083 1084
    if (isolateId == vm!.isolate!.id) {
      return vm!.isolate!;
1085
    }
1086
    throw UnimplementedError('getIsolate called with unrecognized $isolateId');
1087 1088 1089
  }

  @override
1090 1091
  Future<vms.Success> resume(String isolateId, {String? step, int? frameIndex}) async {
    assert(isolateId == vm!.isolate!.id);
1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130
    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>[];
1131 1132
  Map<String, vms.Response?> responses = <String, vms.Response?>{};
  Future<void>? artificialExtensionDelay;
1133 1134

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

1139
    final vms.Response response = responses[args!['command']]!;
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173
    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();
  }

1174
  final Map<int, vms.Timeline?> timelineResponses = <int, vms.Timeline?>{
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184
    1: vms.Timeline.parse(<String, dynamic>{
      'traceEvents': <dynamic>[
        <String, dynamic>{
          'name': 'test event',
        },
      ],
      'timeOriginMicros': 0,
      'timeExtentMicros': 999999,
    }),
  };
1185

1186
  @override
1187
  Future<vms.Timeline> getVMTimeline({int? timeOriginMicros, int? timeExtentMicros}) async {
1188
    connectionLog.add('getVMTimeline $timeOriginMicros $timeExtentMicros');
1189
    final vms.Timeline timeline = timelineResponses[timeOriginMicros ?? 1]!;
1190 1191 1192
    assert(timeline != null, 'Missing entry in timelineResponses[$timeOriginMicros]');
    return timeline;
  }
1193

1194
  @override
1195
  Future<void> dispose() async {
1196 1197
    connectionLog.add('dispose');
  }
1198

1199 1200 1201
  @override
  Future<void> get onDone async {}
}
1202

1203 1204
class FakeVM extends Fake implements vms.VM {
  FakeVM(this.isolate);
1205

1206
  vms.Isolate? isolate;
1207

1208
  int numberOfTriesBeforeResolvingIsolate = 0;
1209

1210
  @override
1211 1212 1213 1214
  List<vms.IsolateRef> get isolates {
    numberOfTriesBeforeResolvingIsolate -= 1;
    return <vms.Isolate>[
      if (numberOfTriesBeforeResolvingIsolate <= 0)
1215
        isolate!,
1216 1217
    ];
  }
1218
}
1219

1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235
vms.Isolate createFakeIsolate() => vms.Isolate(
  id: '123',
  number: '123',
  name: null,
  isSystemIsolate: null,
  isolateFlags: null,
  startTime: null,
  runnable: null,
  livePorts: null,
  pauseOnExit: null,
  pauseEvent: null,
  libraries: null,
  breakpoints: null,
  exceptionPauseMode: null,
  extensionRPCs: <String>[],
);