// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // @dart = 2.8 import 'dart:async'; import 'dart:convert'; import 'package:flutter_driver/src/common/error.dart'; import 'package:flutter_driver/src/common/health.dart'; import 'package:flutter_driver/src/common/layer_tree.dart'; import 'package:flutter_driver/src/common/wait.dart'; import 'package:flutter_driver/src/driver/driver.dart'; import 'package:flutter_driver/src/driver/timeline.dart'; import 'package:vm_service/vm_service.dart' as vms; import 'package:quiver/testing/async.dart'; import 'common.dart'; /// Magical timeout value that's different from the default. const Duration _kTestTimeout = Duration(milliseconds: 1234); const String _kSerializedTestTimeout = '1234'; const String _kWebScriptPrefix = r"window.$flutterDriver('"; const String _kWebScriptSuffix = "')"; void main() { final List<String> log = <String>[]; driverLog = (String source, String message) { log.add('$source: $message'); }; group('VMServiceFlutterDriver.connect', () { FakeVmService fakeClient; FakeVM fakeVM; FakeIsolate fakeIsolate; void expectLogContains(String message) { expect(log, anyElement(contains(message))); } setUp(() { log.clear(); fakeIsolate = FakeIsolate(); fakeVM = FakeVM(fakeIsolate); fakeClient = FakeVmService(fakeVM); vmServiceConnectFunction = (String url, Map<String, dynamic> headers) async { return fakeClient; }; fakeClient.responses['get_health'] = makeFakeResponse(<String, dynamic>{'status': 'ok'}); }); tearDown(() async { restoreVmServiceConnectFunction(); }); test('throws after retries if no isolate', () async { fakeVM.numberOfTriesBeforeResolvingIsolate = 10000; FakeAsync().run((FakeAsync time) { FlutterDriver.connect(dartVmServiceUrl: ''); time.elapse(kUnusuallyLongTimeout); }); 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); final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: ''); expect(driver, isNotNull); expectLogContains('Isolate is paused at start'); expect( fakeClient.connectionLog, <String>[ 'getIsolate', 'setFlag pause_isolates_on_start false', 'resume', 'streamListen Isolate', 'getIsolate', 'onIsolateEvent', 'streamCancel Isolate', ], ); }); test('ignores setFlag failure', () async { fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0); fakeClient.failOnSetFlag = true; final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: ''); expectLogContains('Failed to set pause_isolates_on_start=false, proceeding. ' 'Error: Exception: setFlag failed'); expect(driver, isNotNull); }); test('connects to isolate paused mid-flight', () async { fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseBreakpoint, timestamp: 0); final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: ''); expect(driver, isNotNull); expectLogContains('Isolate is paused mid-flight'); }); // This test simulates a situation when we believe that the isolate is // currently paused, but something else (e.g. a debugger) resumes it before // we do. There's no need to fail as we should be able to drive the app // just fine. test('connects despite losing the race to resume isolate', () async { fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseBreakpoint, timestamp: 0); fakeClient.failOnResumeWith101 = true; final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: ''); expect(driver, isNotNull); expectLogContains('Attempted to resume an already resumed isolate'); }); test('connects to unpaused isolate', () async { fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kResume, timestamp: 0); final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: ''); expect(driver, isNotNull); expectLogContains('Isolate is not paused. Assuming application is ready.'); }); test('connects to unpaused when onExtensionAdded does not contain the ' 'driver extension', () async { fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kResume, timestamp: 0); fakeIsolate.extensionRPCs.add('ext.flutter.driver'); final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: ''); expect(driver, isNotNull); expectLogContains('Isolate is not paused. Assuming application is ready.'); }); }); group('VMServiceFlutterDriver', () { FakeVmService fakeClient; FakeVM fakeVM; FakeIsolate fakeIsolate; VMServiceFlutterDriver driver; setUp(() { fakeIsolate = FakeIsolate(); fakeVM = FakeVM(fakeIsolate); fakeClient = FakeVmService(fakeVM); driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate); fakeClient.responses['tap'] = makeFakeResponse(<String, dynamic>{}); }); test('checks the health of the driver extension', () async { fakeClient.responses['get_health'] = makeFakeResponse(<String, dynamic>{'status': 'ok'}); final Health result = await driver.checkHealth(); expect(result.status, HealthStatus.ok); }); test('closes connection', () async { await driver.close(); expect(fakeClient.connectionLog.last, 'dispose'); }); group('ByValueKey', () { test('restricts value types', () async { expect(() => find.byValueKey(null), throwsDriverError); }); test('finds by ValueKey', () async { await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: ByValueKey, keyValueString: foo, keyValueType: String}', ]); }); }); group('BySemanticsLabel', () { test('finds by Semantic label using String', () async { await driver.tap(find.bySemanticsLabel('foo'), timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: BySemanticsLabel, label: foo}', ]); }); test('finds by Semantic label using RegExp', () async { await driver.tap(find.bySemanticsLabel(RegExp('^foo')), timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: BySemanticsLabel, label: ^foo, isRegExp: true}', ]); }); }); group('tap', () { test('requires a target reference', () async { expect(driver.tap(null), throwsAssertionError); }); test('sends the tap command', () async { await driver.tap(find.text('foo'), timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: tap, timeout: $_kSerializedTestTimeout, finderType: ByText, text: foo}', ]); }); }); group('getText', () { test('requires a target reference', () async { expect(driver.getText(null), throwsAssertionError); }); test('sends the getText command', () async { fakeClient.responses['get_text'] = makeFakeResponse(<String, dynamic>{'text': 'hello'}); final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout); expect(result, 'hello'); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: get_text, timeout: $_kSerializedTestTimeout, finderType: ByValueKey, keyValueString: 123, keyValueType: int}', ]); }); }); group('getLayerTree', () { test('sends the getLayerTree command', () async { fakeClient.responses['get_layer_tree'] = makeFakeResponse(<String, String>{ 'tree': 'hello', }); final LayerTree result = await driver.getLayerTree(timeout: _kTestTimeout); final LayerTree referenceTree = LayerTree.fromJson(<String, String>{ 'tree': 'hello', }); expect(result.tree, referenceTree.tree); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: get_layer_tree, timeout: $_kSerializedTestTimeout}', ]); }); }); group('waitFor', () { test('requires a target reference', () async { expect(driver.waitFor(null), throwsAssertionError); }); test('sends the waitFor command', () async { fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{}); await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: waitFor, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo}', ]); }); }); group('getWidgetDiagnostics', () { test('sends the getWidgetDiagnostics command', () async { fakeClient.responses['get_diagnostics_tree'] = makeFakeResponse(<String, dynamic>{}); await driver.getWidgetDiagnostics(find.byTooltip('foo'), timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: get_diagnostics_tree, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo, subtreeDepth: 0, includeProperties: true, diagnosticsType: widget}', ]); }); }); group('getRenderObjectDiagnostics', () { test('sends the getRenderObjectDiagnostics command', () async { fakeClient.responses['get_diagnostics_tree'] = makeFakeResponse(<String, dynamic>{}); await driver.getRenderObjectDiagnostics(find.byTooltip('foo'), timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: get_diagnostics_tree, timeout: $_kSerializedTestTimeout, finderType: ByTooltipMessage, text: foo, subtreeDepth: 0, includeProperties: true, diagnosticsType: renderObject}', ]); }); }); group('waitForCondition', () { test('sends the wait for NoPendingFrameCondition command', () async { fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{}); await driver.waitForCondition(const NoPendingFrame(), timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: NoPendingFrameCondition}', ]); }); test('sends the wait for NoPendingPlatformMessages command', () async { fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{}); await driver.waitForCondition(const NoPendingPlatformMessages(), timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: NoPendingPlatformMessagesCondition}', ]); }); test('sends the waitForCondition of combined conditions command', () async { fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{}); const SerializableWaitCondition combinedCondition = CombinedCondition(<SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()]); await driver.waitForCondition(combinedCondition, timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: CombinedCondition, conditions: [{"conditionName":"NoPendingFrameCondition"},{"conditionName":"NoTransientCallbacksCondition"}]}', ]); }); }); group('waitUntilNoTransientCallbacks', () { test('sends the waitUntilNoTransientCallbacks command', () async { fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{}); await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: waitForCondition, timeout: $_kSerializedTestTimeout, conditionName: NoTransientCallbacksCondition}', ]); }); }); group('waitUntilFirstFrameRasterized', () { test('sends the waitUntilFirstFrameRasterized command', () async { fakeClient.responses['waitForCondition'] = makeFakeResponse(<String, dynamic>{}); await driver.waitUntilFirstFrameRasterized(); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: waitForCondition, conditionName: FirstFrameRasterizedCondition}', ]); }); }); group('getOffset', () { setUp(() { fakeClient.responses['get_offset'] = makeFakeResponse(<String, double>{ 'dx': 11, 'dy': 12, }); }); test('requires a target reference', () async { 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); }); test('sends the getCenter command', () async { final DriverOffset result = await driver.getCenter(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: center}', ]); }); test('sends the getTopLeft command', () async { final DriverOffset result = await driver.getTopLeft(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: topLeft}', ]); }); test('sends the getTopRight command', () async { final DriverOffset result = await driver.getTopRight(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: topRight}', ]); }); test('sends the getBottomLeft command', () async { final DriverOffset result = await driver.getBottomLeft(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: bottomLeft}', ]); }); test('sends the getBottomRight command', () async { final DriverOffset result = await driver.getBottomRight(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeClient.commandLog, <String>[ 'ext.flutter.driver {command: get_offset, timeout: 1234, finderType: ByValueKey, keyValueString: 123, keyValueType: int, offsetType: bottomRight}', ]); }); }); group('clearTimeline', () { test('clears timeline', () async { await driver.clearTimeline(); expect(fakeClient.connectionLog, contains('clearVMTimeline')); }); }); group('traceAction', () { test('without clearing timeline', () async { final Timeline timeline = await driver.traceAction(() async { fakeClient.connectionLog.add('action'); }, retainPriorEvents: true); expect(fakeClient.connectionLog, const <String>[ 'setVMTimelineFlags [all]', 'action', 'getFlagList', 'setVMTimelineFlags []', 'getVMTimeline null null', ]); expect(timeline.events.single.name, 'test event'); }); test('with clearing timeline', () async { final Timeline timeline = await driver.traceAction(() async { fakeClient.connectionLog.add('action'); }); expect(fakeClient.connectionLog, const <String>[ 'clearVMTimeline', 'getVMTimelineMicros', 'setVMTimelineFlags [all]', 'action', 'getVMTimelineMicros', 'getFlagList', 'setVMTimelineFlags []', 'getVMTimeline 1 999999', ]); expect(timeline.events.single.name, 'test event'); }); test('with time interval', () async { fakeClient.incrementMicros = true; fakeClient.timelineResponses[1000001] = vms.Timeline.parse(<String, dynamic>{ 'traceEvents': <dynamic>[ <String, dynamic>{ 'name': 'test event 2', }, ], 'timeOriginMicros': 1000000, 'timeExtentMicros': 999999, }); final Timeline timeline = await driver.traceAction(() async { fakeClient.connectionLog.add('action'); }); expect(fakeClient.connectionLog, const <String>[ 'clearVMTimeline', 'getVMTimelineMicros', 'setVMTimelineFlags [all]', 'action', 'getVMTimelineMicros', 'getFlagList', 'setVMTimelineFlags []', 'getVMTimeline 1 999999', 'getVMTimeline 1000001 999999', ]); expect(timeline.events.map((TimelineEvent event) => event.name), <String>[ 'test event', 'test event 2', ]); }); }); group('traceAction with timeline streams', () { test('specify non-default timeline streams', () async { bool actionCalled = false; final Timeline timeline = await driver.traceAction(() async { actionCalled = true; }, streams: const <TimelineStream>[ TimelineStream.dart, TimelineStream.gc, TimelineStream.compiler, ], retainPriorEvents: true); expect(actionCalled, isTrue); expect(fakeClient.connectionLog, <String>[ 'setVMTimelineFlags [Dart, GC, Compiler]', 'getFlagList', 'setVMTimelineFlags []', 'getVMTimeline null null' ]); expect(timeline.events.single.name, 'test event'); }); }); group('sendCommand error conditions', () { test('local default timeout', () async { log.clear(); fakeClient.artificialExtensionDelay = Completer<void>().future; FakeAsync().run((FakeAsync time) { driver.waitFor(find.byTooltip('foo')); expect(log, <String>[]); time.elapse(kUnusuallyLongTimeout); }); expect(log, <String>['VMServiceFlutterDriver: waitFor message is taking a long time to complete...']); }); test('local custom timeout', () async { log.clear(); fakeClient.artificialExtensionDelay = Completer<void>().future; FakeAsync().run((FakeAsync time) { final Duration customTimeout = kUnusuallyLongTimeout - const Duration(seconds: 1); driver.waitFor(find.byTooltip('foo'), timeout: customTimeout); expect(log, <String>[]); time.elapse(customTimeout); }); expect(log, <String>['VMServiceFlutterDriver: waitFor message is taking a long time to complete...']); }); test('remote error', () async { fakeClient.responses['waitFor'] = makeFakeResponse(<String, dynamic>{ 'message': 'This is a failure', }, isError: true); try { await driver.waitFor(find.byTooltip('foo')); fail('expected an exception'); } catch (error) { expect(error, isA<DriverError>()); expect(error.message, 'Error in Flutter application: {message: This is a failure}'); } }); test('uncaught remote error', () async { fakeClient.artificialExtensionDelay = Future<void>.error( vms.RPCError('callServiceExtension', 9999, 'test error'), ); expect(driver.waitFor(find.byTooltip('foo')), throwsDriverError); }); }); group('VMServiceFlutterDriver Unsupported error', () { test('enableAccessibility', () async { expect(driver.enableAccessibility(), throwsA(isA<UnsupportedError>())); }); test('webDriver', () async { expect(() => driver.webDriver, throwsA(isA<UnsupportedError>())); }); }); }); group('VMServiceFlutterDriver with custom timeout', () { FakeVmService fakeClient; FakeVM fakeVM; FakeIsolate fakeIsolate; VMServiceFlutterDriver driver; setUp(() { fakeIsolate = FakeIsolate(); fakeVM = FakeVM(fakeIsolate); fakeClient = FakeVmService(fakeVM); driver = VMServiceFlutterDriver.connectedTo(fakeClient, fakeIsolate); fakeClient.responses['get_health'] = makeFakeResponse(<String, dynamic>{'status': 'ok'}); }); test('GetHealth has no default timeout', () async { await driver.checkHealth(); expect( fakeClient.commandLog, <String>['ext.flutter.driver {command: get_health}'], ); }); test('does not interfere with explicit timeouts', () async { await driver.checkHealth(timeout: _kTestTimeout); expect( fakeClient.commandLog, <String>['ext.flutter.driver {command: get_health, timeout: $_kSerializedTestTimeout}'], ); }); }); group('WebFlutterDriver', () { FakeFlutterWebConnection fakeConnection; WebFlutterDriver driver; setUp(() { fakeConnection = FakeFlutterWebConnection(); fakeConnection.supportsTimelineAction = true; driver = WebFlutterDriver.connectedTo(fakeConnection); }); test('closes connection', () async { await driver.close(); }); group('ByValueKey', () { test('restricts value types', () async { expect(() => find.byValueKey(null), throwsDriverError); }); test('finds by ValueKey', () async { fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{})); await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"ByValueKey","keyValueString":"foo","keyValueType":"String"}') 0:00:01.234000''', ]); }); }); group('BySemanticsLabel', () { test('finds by Semantic label using String', () async { fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{})); await driver.tap(find.bySemanticsLabel('foo'), timeout: _kTestTimeout); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"BySemanticsLabel","label":"foo"}') 0:00:01.234000''', ]); }); test('finds by Semantic label using RegExp', () async { fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{})); await driver.tap(find.bySemanticsLabel(RegExp('^foo')), timeout: _kTestTimeout); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"BySemanticsLabel","label":"^foo","isRegExp":"true"}') 0:00:01.234000''', ]); }); }); group('tap', () { test('requires a target reference', () async { expect(driver.tap(null), throwsAssertionError); }); test('sends the tap command', () async { fakeConnection.responses['tap'] = jsonEncode(makeFakeResponse(<String, dynamic>{})); await driver.tap(find.text('foo'), timeout: _kTestTimeout); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"tap","timeout":"1234","finderType":"ByText","text":"foo"}') 0:00:01.234000''', ]); }); }); group('getText', () { test('requires a target reference', () async { expect(driver.getText(null), throwsAssertionError); }); test('sends the getText command', () async { fakeConnection.responses['get_text'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'})); final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout); expect(result, 'hello'); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"get_text","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int"}') 0:00:01.234000''', ]); }); }); group('waitFor', () { test('requires a target reference', () async { expect(driver.waitFor(null), throwsAssertionError); }); test('sends the waitFor command', () async { fakeConnection.responses['waitFor'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'})); await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"waitFor","timeout":"1234","finderType":"ByTooltipMessage","text":"foo"}') 0:00:01.234000''', ]); }); }); group('waitForCondition', () { setUp(() { fakeConnection.responses['waitForCondition'] = jsonEncode(makeFakeResponse(<String, dynamic>{'text': 'hello'})); }); test('sends the wait for NoPendingFrameCondition command', () async { await driver.waitForCondition(const NoPendingFrame(), timeout: _kTestTimeout); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"NoPendingFrameCondition"}') 0:00:01.234000''', ]); }); test('sends the wait for NoPendingPlatformMessages command', () async { await driver.waitForCondition(const NoPendingPlatformMessages(), timeout: _kTestTimeout); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"NoPendingPlatformMessagesCondition"}') 0:00:01.234000''', ]); }); test('sends the waitForCondition of combined conditions command', () async { const SerializableWaitCondition combinedCondition = CombinedCondition( <SerializableWaitCondition>[NoPendingFrame(), NoTransientCallbacks()], ); await driver.waitForCondition(combinedCondition, timeout: _kTestTimeout); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"CombinedCondition","conditions":"[{\"conditionName\":\"NoPendingFrameCondition\"},{\"conditionName\":\"NoTransientCallbacksCondition\"}]"}') 0:00:01.234000''', ]); }); }); group('waitUntilNoTransientCallbacks', () { test('sends the waitUntilNoTransientCallbacks command', () async { fakeConnection.responses['waitForCondition'] = jsonEncode(makeFakeResponse(<String, dynamic>{})); await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"waitForCondition","timeout":"1234","conditionName":"NoTransientCallbacksCondition"}') 0:00:01.234000''', ]); }); }); group('getOffset', () { setUp(() { fakeConnection.responses['get_offset'] = jsonEncode(makeFakeResponse(<String, double>{ 'dx': 11, 'dy': 12, })); }); test('requires a target reference', () async { 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); }); test('sends the getCenter command', () async { final DriverOffset result = await driver.getCenter(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"center"}') 0:00:01.234000''', ]); }); test('sends the getTopLeft command', () async { final DriverOffset result = await driver.getTopLeft(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"topLeft"}') 0:00:01.234000''', ]); }); test('sends the getTopRight command', () async { final DriverOffset result = await driver.getTopRight(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"topRight"}') 0:00:01.234000''', ]); }); test('sends the getBottomLeft command', () async { final DriverOffset result = await driver.getBottomLeft(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"bottomLeft"}') 0:00:01.234000''', ]); }); test('sends the getBottomRight command', () async { final DriverOffset result = await driver.getBottomRight(find.byValueKey(123), timeout: _kTestTimeout); expect(result, const DriverOffset(11, 12)); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"get_offset","timeout":"1234","finderType":"ByValueKey","keyValueString":"123","keyValueType":"int","offsetType":"bottomRight"}') 0:00:01.234000''', ]); }); }); test('checks the health of the driver extension', () async { fakeConnection.responses['get_health'] = jsonEncode(makeFakeResponse(<String, dynamic>{'status': 'ok'})); await driver.checkHealth(); expect(fakeConnection.commandLog, <String>[ r'''window.$flutterDriver('{"command":"get_health"}') null''', ]); }); group('WebFlutterDriver Unimplemented/Unsupported error', () { test('forceGC', () async { expect(driver.forceGC(), throwsA(isA<UnimplementedError>())); }); test('getVmFlags', () async { expect(driver.getVmFlags(), throwsA(isA<UnimplementedError>())); }); test('waitUntilFirstFrameRasterized', () async { expect(driver.waitUntilFirstFrameRasterized(), throwsA(isA<UnimplementedError>())); }); test('appIsoloate', () async { expect(() => driver.appIsolate.extensionRPCs, throwsA(isA<UnsupportedError>())); }); test('serviceClient', () async { expect(() => driver.serviceClient.getVM(), throwsA(isA<UnsupportedError>())); }); }); }); group('WebFlutterDriver with non-chrome browser', () { FakeFlutterWebConnection fakeConnection; WebFlutterDriver driver; setUp(() { fakeConnection = FakeFlutterWebConnection(); driver = WebFlutterDriver.connectedTo(fakeConnection); }); test('tracing', () async { expect(driver.traceAction(() async { return Future<dynamic>.value(); }), throwsA(isA<UnsupportedError>())); expect(driver.startTracing(), throwsA(isA<UnsupportedError>())); expect(driver.stopTracingAndDownloadTimeline(), throwsA(isA<UnsupportedError>())); expect(driver.clearTimeline(), throwsA(isA<UnsupportedError>())); }); }); } /// 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) { expect(script, isA<String>()); expect(script.startsWith(_kWebScriptPrefix), isTrue); expect(script.endsWith(_kWebScriptSuffix), isTrue); // Strip prefix and suffix return script.substring(_kWebScriptPrefix.length, script.length - 2) as String; } vms.Response makeFakeResponse( Map<String, dynamic> response, { bool isError = false, }) { return vms.Response.parse(<String, dynamic>{ 'isError': isError, 'response': response, }); } 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, }), }; @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; } @override void dispose() { connectionLog.add('dispose'); } @override Future<void> get onDone async {} } class FakeVM extends Fake implements vms.VM { FakeVM(this.isolate); vms.Isolate isolate; int numberOfTriesBeforeResolvingIsolate = 0; @override List<vms.IsolateRef> get isolates { numberOfTriesBeforeResolvingIsolate -= 1; return <vms.Isolate>[ if (numberOfTriesBeforeResolvingIsolate <= 0) isolate, ]; } } 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>[]; }