2016-02-08 17:30:20 +00:00
|
|
|
// Copyright 2016 The Chromium 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 'package:test/test.dart';
|
|
|
|
import 'package:flutter_driver/src/driver.dart';
|
|
|
|
import 'package:flutter_driver/src/error.dart';
|
|
|
|
import 'package:flutter_driver/src/health.dart';
|
|
|
|
import 'package:flutter_driver/src/message.dart';
|
|
|
|
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
|
|
|
|
import 'package:mockito/mockito.dart';
|
2016-02-24 01:44:09 +00:00
|
|
|
import 'package:quiver/testing/async.dart';
|
2016-02-08 17:30:20 +00:00
|
|
|
import 'package:vm_service_client/vm_service_client.dart';
|
|
|
|
|
|
|
|
main() {
|
|
|
|
group('FlutterDriver.connect', () {
|
|
|
|
List<LogRecord> log;
|
|
|
|
StreamSubscription logSub;
|
|
|
|
MockVMServiceClient mockClient;
|
|
|
|
MockVM mockVM;
|
|
|
|
MockIsolate mockIsolate;
|
|
|
|
|
|
|
|
expectLogContains(String message) {
|
|
|
|
expect(log.map((r) => '$r'), anyElement(contains(message)));
|
|
|
|
}
|
|
|
|
|
|
|
|
setUp(() {
|
|
|
|
log = <LogRecord>[];
|
|
|
|
logSub = flutterDriverLog.listen(log.add);
|
|
|
|
mockClient = new MockVMServiceClient();
|
|
|
|
mockVM = new MockVM();
|
|
|
|
mockIsolate = new MockIsolate();
|
|
|
|
when(mockClient.getVM()).thenReturn(mockVM);
|
|
|
|
when(mockVM.isolates).thenReturn([mockIsolate]);
|
2016-03-02 23:58:01 +00:00
|
|
|
when(mockIsolate.loadRunnable()).thenReturn(mockIsolate);
|
2016-02-08 17:30:20 +00:00
|
|
|
when(mockIsolate.invokeExtension(any, any))
|
|
|
|
.thenReturn(new Future.value({'status': 'ok'}));
|
|
|
|
vmServiceConnectFunction = (_) => new Future.value(mockClient);
|
|
|
|
});
|
|
|
|
|
|
|
|
tearDown(() async {
|
|
|
|
await logSub.cancel();
|
2016-02-20 00:31:34 +00:00
|
|
|
restoreVmServiceConnectFunction();
|
2016-02-08 17:30:20 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
test('connects to isolate paused at start', () async {
|
|
|
|
when(mockIsolate.pauseEvent).thenReturn(new MockVMPauseStartEvent());
|
|
|
|
when(mockIsolate.resume()).thenReturn(new Future.value());
|
2016-02-18 02:01:46 +00:00
|
|
|
when(mockIsolate.onExtensionAdded)
|
|
|
|
.thenReturn(new Stream.fromIterable(['ext.flutter_driver']));
|
2016-02-08 17:30:20 +00:00
|
|
|
|
|
|
|
FlutterDriver driver = await FlutterDriver.connect();
|
|
|
|
expect(driver, isNotNull);
|
|
|
|
expectLogContains('Isolate is paused at start');
|
|
|
|
});
|
|
|
|
|
|
|
|
test('connects to isolate paused mid-flight', () async {
|
|
|
|
when(mockIsolate.pauseEvent).thenReturn(new MockVMPauseBreakpointEvent());
|
|
|
|
when(mockIsolate.resume()).thenReturn(new Future.value());
|
|
|
|
|
|
|
|
FlutterDriver driver = await FlutterDriver.connect();
|
|
|
|
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 {
|
|
|
|
when(mockIsolate.pauseEvent).thenReturn(new MockVMPauseBreakpointEvent());
|
|
|
|
when(mockIsolate.resume()).thenAnswer((_) {
|
|
|
|
// This needs to be wrapped in a closure to not be considered uncaught
|
|
|
|
// by package:test
|
|
|
|
return new Future.error(new rpc.RpcException(101, ''));
|
|
|
|
});
|
|
|
|
|
|
|
|
FlutterDriver driver = await FlutterDriver.connect();
|
|
|
|
expect(driver, isNotNull);
|
|
|
|
expectLogContains('Attempted to resume an already resumed isolate');
|
|
|
|
});
|
|
|
|
|
|
|
|
test('connects to unpaused isolate', () async {
|
|
|
|
when(mockIsolate.pauseEvent).thenReturn(new MockVMResumeEvent());
|
|
|
|
FlutterDriver driver = await FlutterDriver.connect();
|
|
|
|
expect(driver, isNotNull);
|
|
|
|
expectLogContains('Isolate is not paused. Assuming application is ready.');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
group('FlutterDriver', () {
|
|
|
|
MockVMServiceClient mockClient;
|
|
|
|
MockIsolate mockIsolate;
|
|
|
|
FlutterDriver driver;
|
|
|
|
|
|
|
|
setUp(() {
|
|
|
|
mockClient = new MockVMServiceClient();
|
|
|
|
mockIsolate = new MockIsolate();
|
|
|
|
driver = new FlutterDriver.connectedTo(mockClient, mockIsolate);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('checks the health of the driver extension', () async {
|
|
|
|
when(mockIsolate.invokeExtension(any, any)).thenReturn(new Future.value({
|
|
|
|
'status': 'ok',
|
|
|
|
}));
|
|
|
|
Health result = await driver.checkHealth();
|
|
|
|
expect(result.status, HealthStatus.ok);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('closes connection', () async {
|
|
|
|
when(mockClient.close()).thenReturn(new Future.value());
|
|
|
|
await driver.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
group('findByValueKey', () {
|
|
|
|
test('restricts value types', () async {
|
|
|
|
expect(driver.findByValueKey(null),
|
|
|
|
throwsA(new isInstanceOf<DriverError>()));
|
|
|
|
});
|
|
|
|
|
|
|
|
test('finds by ValueKey', () async {
|
|
|
|
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
|
|
|
expect(i.positionalArguments[1], {
|
2016-02-29 07:47:30 +00:00
|
|
|
'kind': 'find',
|
|
|
|
'searchSpecType': 'ByValueKey',
|
2016-02-08 17:30:20 +00:00
|
|
|
'keyValueString': 'foo',
|
2016-02-29 07:47:30 +00:00
|
|
|
'keyValueType': 'String'
|
2016-02-08 17:30:20 +00:00
|
|
|
});
|
|
|
|
return new Future.value({
|
|
|
|
'objectReferenceKey': '123',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
ObjectRef result = await driver.findByValueKey('foo');
|
|
|
|
expect(result, isNotNull);
|
|
|
|
expect(result.objectReferenceKey, '123');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
group('tap', () {
|
|
|
|
test('requires a target reference', () async {
|
|
|
|
expect(driver.tap(null), throwsA(new isInstanceOf<DriverError>()));
|
|
|
|
});
|
|
|
|
|
|
|
|
test('requires a valid target reference', () async {
|
|
|
|
expect(driver.tap(new ObjectRef.notFound()),
|
|
|
|
throwsA(new isInstanceOf<DriverError>()));
|
|
|
|
});
|
|
|
|
|
|
|
|
test('sends the tap command', () async {
|
|
|
|
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
|
|
|
expect(i.positionalArguments[1], {
|
|
|
|
'kind': 'tap',
|
|
|
|
'targetRef': '123'
|
|
|
|
});
|
|
|
|
return new Future.value();
|
|
|
|
});
|
|
|
|
await driver.tap(new ObjectRef('123'));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
group('getText', () {
|
|
|
|
test('requires a target reference', () async {
|
|
|
|
expect(driver.getText(null), throwsA(new isInstanceOf<DriverError>()));
|
|
|
|
});
|
|
|
|
|
|
|
|
test('requires a valid target reference', () async {
|
|
|
|
expect(driver.getText(new ObjectRef.notFound()),
|
|
|
|
throwsA(new isInstanceOf<DriverError>()));
|
|
|
|
});
|
|
|
|
|
|
|
|
test('sends the getText command', () async {
|
|
|
|
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
|
|
|
expect(i.positionalArguments[1], {
|
|
|
|
'kind': 'get_text',
|
|
|
|
'targetRef': '123'
|
|
|
|
});
|
|
|
|
return new Future.value({
|
|
|
|
'text': 'hello'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
String result = await driver.getText(new ObjectRef('123'));
|
|
|
|
expect(result, 'hello');
|
|
|
|
});
|
|
|
|
});
|
2016-02-24 01:44:09 +00:00
|
|
|
|
|
|
|
group('waitFor', () {
|
|
|
|
test('waits for a condition', () {
|
|
|
|
expect(
|
|
|
|
driver.waitFor(() {
|
|
|
|
return new Future.delayed(
|
|
|
|
new Duration(milliseconds: 50),
|
|
|
|
() => 123
|
|
|
|
);
|
|
|
|
}, equals(123)),
|
|
|
|
completion(123)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('retries a correct number of times', () {
|
|
|
|
new FakeAsync().run((FakeAsync fakeAsync) {
|
|
|
|
int retryCount = 0;
|
|
|
|
|
|
|
|
expect(
|
|
|
|
driver.waitFor(
|
|
|
|
() {
|
|
|
|
retryCount++;
|
|
|
|
return retryCount;
|
|
|
|
},
|
|
|
|
equals(2),
|
|
|
|
timeout: new Duration(milliseconds: 30),
|
|
|
|
pauseBetweenRetries: new Duration(milliseconds: 10)
|
|
|
|
),
|
|
|
|
completion(2)
|
|
|
|
);
|
|
|
|
|
|
|
|
fakeAsync.elapse(new Duration(milliseconds: 50));
|
|
|
|
|
|
|
|
// Check that we didn't retry more times than necessary
|
|
|
|
expect(retryCount, 2);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
test('times out', () async {
|
|
|
|
bool timedOut = false;
|
|
|
|
await driver.waitFor(
|
|
|
|
() => 1,
|
|
|
|
equals(2),
|
|
|
|
timeout: new Duration(milliseconds: 10),
|
|
|
|
pauseBetweenRetries: new Duration(milliseconds: 2)
|
|
|
|
).catchError((err, stack) {
|
|
|
|
timedOut = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(timedOut, isTrue);
|
|
|
|
});
|
|
|
|
});
|
2016-02-08 17:30:20 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@proxy
|
|
|
|
class MockVMServiceClient extends Mock implements VMServiceClient { }
|
|
|
|
|
|
|
|
@proxy
|
|
|
|
class MockVM extends Mock implements VM { }
|
|
|
|
|
|
|
|
@proxy
|
|
|
|
class MockIsolate extends Mock implements VMRunnableIsolate { }
|
|
|
|
|
|
|
|
@proxy
|
|
|
|
class MockVMPauseStartEvent extends Mock implements VMPauseStartEvent { }
|
|
|
|
|
|
|
|
@proxy
|
|
|
|
class MockVMPauseBreakpointEvent extends Mock implements VMPauseBreakpointEvent { }
|
|
|
|
|
|
|
|
@proxy
|
|
|
|
class MockVMResumeEvent extends Mock implements VMResumeEvent { }
|