mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
1013 lines
38 KiB
Dart
1013 lines
38 KiB
Dart
// 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.
|
|
|
|
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:fake_async/fake_async.dart';
|
|
import 'package:vm_service/vm_service.dart' as vms;
|
|
|
|
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', () {
|
|
late FakeVmService fakeClient;
|
|
late FakeVM fakeVM;
|
|
late 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', () {
|
|
late FakeVmService fakeClient;
|
|
FakeVM fakeVM;
|
|
FakeIsolate fakeIsolate;
|
|
late 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('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('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('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('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 as DriverError).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', () {
|
|
late FakeVmService fakeClient;
|
|
FakeVM fakeVM;
|
|
FakeIsolate fakeIsolate;
|
|
late 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', () {
|
|
late FakeFlutterWebConnection fakeConnection;
|
|
late 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('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('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('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('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;
|
|
late 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!;
|
|
}
|
|
throw UnimplementedError('getIsolate called with unrecognized $isolateId');
|
|
}
|
|
|
|
@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
|
|
Future<void> dispose() async {
|
|
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>[];
|
|
} |