flutter_driver_test.dart 13.1 KB
Newer Older
1 2 3 4 5
// 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';
6

7 8 9
import 'package:flutter_driver/src/driver.dart';
import 'package:flutter_driver/src/error.dart';
import 'package:flutter_driver/src/health.dart';
10
import 'package:flutter_driver/src/timeline.dart';
11
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
12
import 'package:mockito/mockito.dart';
13
import 'package:test/test.dart';
14 15
import 'package:vm_service_client/vm_service_client.dart';

16 17 18 19
/// Magical timeout value that's different from the default.
const Duration _kTestTimeout = const Duration(milliseconds: 1234);
const String _kSerializedTestTimeout = '1234';

20
void main() {
21 22
  group('FlutterDriver.connect', () {
    List<LogRecord> log;
Ian Hickson's avatar
Ian Hickson committed
23
    StreamSubscription<LogRecord> logSub;
24 25 26
    MockVMServiceClient mockClient;
    MockVM mockVM;
    MockIsolate mockIsolate;
27
    MockPeer mockPeer;
28

29
    void expectLogContains(String message) {
Ian Hickson's avatar
Ian Hickson committed
30
      expect(log.map((LogRecord r) => '$r'), anyElement(contains(message)));
31 32 33 34 35 36 37 38
    }

    setUp(() {
      log = <LogRecord>[];
      logSub = flutterDriverLog.listen(log.add);
      mockClient = new MockVMServiceClient();
      mockVM = new MockVM();
      mockIsolate = new MockIsolate();
39
      mockPeer = new MockPeer();
40
      when(mockClient.getVM()).thenReturn(mockVM);
pq's avatar
pq committed
41
      when(mockVM.isolates).thenReturn(<VMRunnableIsolate>[mockIsolate]);
42
      when(mockIsolate.loadRunnable()).thenReturn(mockIsolate);
43
      when(mockIsolate.invokeExtension(any, any))
44
          .thenReturn(makeMockResponse(<String, dynamic>{'status': 'ok'}));
45 46
      vmServiceConnectFunction = (String url) {
        return new Future<VMServiceClientConnection>.value(
47
          new VMServiceClientConnection(mockClient, mockPeer)
48 49
        );
      };
50 51 52 53
    });

    tearDown(() async {
      await logSub.cancel();
yjbanov's avatar
yjbanov committed
54
      restoreVmServiceConnectFunction();
55 56 57
    });

    test('connects to isolate paused at start', () async {
58 59 60 61 62
      final List<String> connectionLog = <String>[];
      when(mockPeer.sendRequest('streamListen', any)).thenAnswer((_) {
        connectionLog.add('streamListen');
        return null;
      });
63
      when(mockIsolate.pauseEvent).thenReturn(new MockVMPauseStartEvent());
64 65 66 67 68 69 70 71
      when(mockIsolate.resume()).thenAnswer((_) {
        connectionLog.add('resume');
        return new Future<Null>.value();
      });
      when(mockIsolate.onExtensionAdded).thenAnswer((_) {
        connectionLog.add('onExtensionAdded');
        return new Stream<String>.fromIterable(<String>['ext.flutter.driver']);
      });
72

73
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
74 75
      expect(driver, isNotNull);
      expectLogContains('Isolate is paused at start');
76
      expect(connectionLog, <String>['streamListen', 'onExtensionAdded', 'resume']);
77 78 79 80
    });

    test('connects to isolate paused mid-flight', () async {
      when(mockIsolate.pauseEvent).thenReturn(new MockVMPauseBreakpointEvent());
Ian Hickson's avatar
Ian Hickson committed
81
      when(mockIsolate.resume()).thenReturn(new Future<Null>.value());
82

83
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
84 85 86 87 88 89 90 91 92 93 94 95 96
      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
Ian Hickson's avatar
Ian Hickson committed
97
        return new Future<Null>.error(new rpc.RpcException(101, ''));
98 99
      });

100
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
101 102 103 104 105 106
      expect(driver, isNotNull);
      expectLogContains('Attempted to resume an already resumed isolate');
    });

    test('connects to unpaused isolate', () async {
      when(mockIsolate.pauseEvent).thenReturn(new MockVMResumeEvent());
107
      final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
108 109 110 111 112 113 114
      expect(driver, isNotNull);
      expectLogContains('Isolate is not paused. Assuming application is ready.');
    });
  });

  group('FlutterDriver', () {
    MockVMServiceClient mockClient;
115
    MockPeer mockPeer;
116 117 118 119 120
    MockIsolate mockIsolate;
    FlutterDriver driver;

    setUp(() {
      mockClient = new MockVMServiceClient();
121
      mockPeer = new MockPeer();
122
      mockIsolate = new MockIsolate();
123
      driver = new FlutterDriver.connectedTo(mockClient, mockPeer, mockIsolate);
124 125 126
    });

    test('checks the health of the driver extension', () async {
127 128
      when(mockIsolate.invokeExtension(any, any)).thenReturn(
          makeMockResponse(<String, dynamic>{'status': 'ok'}));
129
      final Health result = await driver.checkHealth();
130 131 132 133
      expect(result.status, HealthStatus.ok);
    });

    test('closes connection', () async {
Ian Hickson's avatar
Ian Hickson committed
134
      when(mockClient.close()).thenReturn(new Future<Null>.value());
135 136 137
      await driver.close();
    });

138
    group('ByValueKey', () {
139
      test('restricts value types', () async {
140
        expect(() => find.byValueKey(null),
141
            throwsA(const isInstanceOf<DriverError>()));
142 143 144 145
      });

      test('finds by ValueKey', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
pq's avatar
pq committed
146
          expect(i.positionalArguments[1], <String, String>{
147
            'command': 'tap',
148
            'timeout': _kSerializedTestTimeout,
149
            'finderType': 'ByValueKey',
150
            'keyValueString': 'foo',
151
            'keyValueType': 'String'
152
          });
153
          return makeMockResponse(<String, dynamic>{});
154
        });
155
        await driver.tap(find.byValueKey('foo'), timeout: _kTestTimeout);
156 157 158 159 160
      });
    });

    group('tap', () {
      test('requires a target reference', () async {
161
        expect(driver.tap(null), throwsA(const isInstanceOf<DriverError>()));
162 163 164 165
      });

      test('sends the tap command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
Ian Hickson's avatar
Ian Hickson committed
166
          expect(i.positionalArguments[1], <String, dynamic>{
167
            'command': 'tap',
168
            'timeout': _kSerializedTestTimeout,
169 170
            'finderType': 'ByText',
            'text': 'foo',
171
          });
172
          return makeMockResponse(<String, dynamic>{});
173
        });
174
        await driver.tap(find.text('foo'), timeout: _kTestTimeout);
175 176 177 178 179
      });
    });

    group('getText', () {
      test('requires a target reference', () async {
180
        expect(driver.getText(null), throwsA(const isInstanceOf<DriverError>()));
181 182 183 184
      });

      test('sends the getText command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
Ian Hickson's avatar
Ian Hickson committed
185
          expect(i.positionalArguments[1], <String, dynamic>{
186
            'command': 'get_text',
187
            'timeout': _kSerializedTestTimeout,
188 189 190
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int'
191
          });
192
          return makeMockResponse(<String, String>{
193 194 195
            'text': 'hello'
          });
        });
196
        final String result = await driver.getText(find.byValueKey(123), timeout: _kTestTimeout);
197 198 199
        expect(result, 'hello');
      });
    });
200 201

    group('waitFor', () {
202
      test('requires a target reference', () async {
203
        expect(driver.waitFor(null), throwsA(const isInstanceOf<DriverError>()));
204 205
      });

206 207 208 209 210 211
      test('sends the waitFor command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitFor',
            'finderType': 'ByTooltipMessage',
            'text': 'foo',
212
            'timeout': _kSerializedTestTimeout,
213
          });
214
          return makeMockResponse(<String, dynamic>{});
215
        });
216
        await driver.waitFor(find.byTooltip('foo'), timeout: _kTestTimeout);
217 218
      });
    });
219

220 221 222 223 224
    group('waitUntilNoTransientCallbacks', () {
      test('sends the waitUntilNoTransientCallbacks command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          expect(i.positionalArguments[1], <String, dynamic>{
            'command': 'waitUntilNoTransientCallbacks',
225
            'timeout': _kSerializedTestTimeout,
226 227 228
          });
          return makeMockResponse(<String, dynamic>{});
        });
229
        await driver.waitUntilNoTransientCallbacks(timeout: _kTestTimeout);
230 231 232
      });
    });

233 234 235 236 237 238
    group('traceAction', () {
      test('traces action', () async {
        bool actionCalled = false;
        bool startTracingCalled = false;
        bool stopTracingCalled = false;

pq's avatar
pq committed
239
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[all]'}))))
240 241 242 243 244
          .thenAnswer((_) async {
            startTracingCalled = true;
            return null;
          });

pq's avatar
pq committed
245
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
246 247 248 249 250 251 252
          .thenAnswer((_) async {
            stopTracingCalled = true;
            return null;
          });

        when(mockPeer.sendRequest('_getVMTimeline')).thenAnswer((_) async {
          return <String, dynamic> {
pq's avatar
pq committed
253 254
            'traceEvents': <dynamic>[
              <String, String>{
255 256 257
                'name': 'test event'
              }
            ],
258 259 260
          };
        });

261
        final Timeline timeline = await driver.traceAction(() {
262 263 264 265 266 267
          actionCalled = true;
        });

        expect(actionCalled, isTrue);
        expect(startTracingCalled, isTrue);
        expect(stopTracingCalled, isTrue);
268
        expect(timeline.events.single.name, 'test event');
269 270
      });
    });
271

272 273
    group('traceAction with timeline streams', () {
      test('specify non-default timeline streams', () async {
274 275 276 277
        bool actionCalled = false;
        bool startTracingCalled = false;
        bool stopTracingCalled = false;

pq's avatar
pq committed
278
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[Dart, GC, Compiler]'}))))
279 280 281 282 283
          .thenAnswer((_) async {
            startTracingCalled = true;
            return null;
          });

pq's avatar
pq committed
284
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
285 286 287 288 289 290 291
          .thenAnswer((_) async {
            stopTracingCalled = true;
            return null;
          });

        when(mockPeer.sendRequest('_getVMTimeline')).thenAnswer((_) async {
          return <String, dynamic> {
pq's avatar
pq committed
292 293
            'traceEvents': <dynamic>[
              <String, String>{
294 295 296 297 298 299
                'name': 'test event'
              }
            ],
          };
        });

300
        final Timeline timeline = await driver.traceAction(() {
301 302
          actionCalled = true;
        },
303 304 305 306
        streams: const <TimelineStream>[
          TimelineStream.dart,
          TimelineStream.gc,
          TimelineStream.compiler
307 308 309 310 311 312 313 314
        ]);

        expect(actionCalled, isTrue);
        expect(startTracingCalled, isTrue);
        expect(stopTracingCalled, isTrue);
        expect(timeline.events.single.name, 'test event');
      });
    });
315 316 317 318 319 320 321 322

    group('sendCommand error conditions', () {
      test('local timeout', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          // completer never competed to trigger timeout
          return new Completer<Map<String, dynamic>>().future;
        });
        try {
323
          await driver.waitFor(find.byTooltip('foo'), timeout: const Duration(milliseconds: 100));
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
          fail('expected an exception');
        } catch(error) {
          expect(error is DriverError, isTrue);
          expect(error.message, 'Failed to fulfill WaitFor: Flutter application not responding');
        }
      });

      test('remote error', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
          return makeMockResponse(<String, dynamic>{
            'message': 'This is a failure'
          }, isError: true);
        });
        try {
          await driver.waitFor(find.byTooltip('foo'));
          fail('expected an exception');
        } catch(error) {
          expect(error is DriverError, isTrue);
          expect(error.message, 'Error in Flutter application: {message: This is a failure}');
        }
      });
    });
  });
}

Future<Map<String, dynamic>> makeMockResponse(
    Map<String, dynamic> response, {bool isError: false}) {
  return new Future<Map<String, dynamic>>.value(<String, dynamic>{
    'isError': isError,
    'response': response
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
  });
}

@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 { }
374 375 376

@proxy
class MockPeer extends Mock implements rpc.Peer { }