flutter_driver_test.dart 10.2 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 12
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'package:mockito/mockito.dart';
13
import 'package:test/test.dart';
14 15
import 'package:vm_service_client/vm_service_client.dart';

16
void main() {
17 18
  group('FlutterDriver.connect', () {
    List<LogRecord> log;
Ian Hickson's avatar
Ian Hickson committed
19
    StreamSubscription<LogRecord> logSub;
20 21 22 23
    MockVMServiceClient mockClient;
    MockVM mockVM;
    MockIsolate mockIsolate;

24
    void expectLogContains(String message) {
Ian Hickson's avatar
Ian Hickson committed
25
      expect(log.map((LogRecord r) => '$r'), anyElement(contains(message)));
26 27 28 29 30 31 32 33 34
    }

    setUp(() {
      log = <LogRecord>[];
      logSub = flutterDriverLog.listen(log.add);
      mockClient = new MockVMServiceClient();
      mockVM = new MockVM();
      mockIsolate = new MockIsolate();
      when(mockClient.getVM()).thenReturn(mockVM);
pq's avatar
pq committed
35
      when(mockVM.isolates).thenReturn(<VMRunnableIsolate>[mockIsolate]);
36
      when(mockIsolate.loadRunnable()).thenReturn(mockIsolate);
37
      when(mockIsolate.invokeExtension(any, any))
Ian Hickson's avatar
Ian Hickson committed
38
          .thenReturn(new Future<Map<String, dynamic>>.value(<String, String>{'status': 'ok'}));
39 40 41 42 43
      vmServiceConnectFunction = (String url) {
        return new Future<VMServiceClientConnection>.value(
          new VMServiceClientConnection(mockClient, null)
        );
      };
44 45 46 47
    });

    tearDown(() async {
      await logSub.cancel();
yjbanov's avatar
yjbanov committed
48
      restoreVmServiceConnectFunction();
49 50 51 52
    });

    test('connects to isolate paused at start', () async {
      when(mockIsolate.pauseEvent).thenReturn(new MockVMPauseStartEvent());
Ian Hickson's avatar
Ian Hickson committed
53
      when(mockIsolate.resume()).thenReturn(new Future<Null>.value());
54
      when(mockIsolate.onExtensionAdded).thenReturn(new Stream<String>.fromIterable(<String>['ext.flutter.driver']));
55 56 57 58 59 60 61 62

      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());
Ian Hickson's avatar
Ian Hickson committed
63
      when(mockIsolate.resume()).thenReturn(new Future<Null>.value());
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

      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
Ian Hickson's avatar
Ian Hickson committed
79
        return new Future<Null>.error(new rpc.RpcException(101, ''));
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
      });

      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;
97
    MockPeer mockPeer;
98 99 100 101 102
    MockIsolate mockIsolate;
    FlutterDriver driver;

    setUp(() {
      mockClient = new MockVMServiceClient();
103
      mockPeer = new MockPeer();
104
      mockIsolate = new MockIsolate();
105
      driver = new FlutterDriver.connectedTo(mockClient, mockPeer, mockIsolate);
106 107 108
    });

    test('checks the health of the driver extension', () async {
Ian Hickson's avatar
Ian Hickson committed
109
      when(mockIsolate.invokeExtension(any, any)).thenReturn(new Future<Map<String, dynamic>>.value(<String, dynamic>{
110 111 112 113 114 115 116
        'status': 'ok',
      }));
      Health result = await driver.checkHealth();
      expect(result.status, HealthStatus.ok);
    });

    test('closes connection', () async {
Ian Hickson's avatar
Ian Hickson committed
117
      when(mockClient.close()).thenReturn(new Future<Null>.value());
118 119 120
      await driver.close();
    });

121
    group('ByValueKey', () {
122
      test('restricts value types', () async {
123
        expect(() => find.byValueKey(null),
124 125 126 127 128
            throwsA(new isInstanceOf<DriverError>()));
      });

      test('finds by ValueKey', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
pq's avatar
pq committed
129
          expect(i.positionalArguments[1], <String, String>{
130 131
            'command': 'tap',
            'finderType': 'ByValueKey',
132
            'keyValueString': 'foo',
133
            'keyValueType': 'String'
134
          });
135
          return new Future<Null>.value();
136
        });
137
        await driver.tap(find.byValueKey('foo'));
138 139 140 141 142 143 144 145 146 147
      });
    });

    group('tap', () {
      test('requires a target reference', () async {
        expect(driver.tap(null), throwsA(new isInstanceOf<DriverError>()));
      });

      test('sends the tap command', () async {
        when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
Ian Hickson's avatar
Ian Hickson committed
148
          expect(i.positionalArguments[1], <String, dynamic>{
149
            'command': 'tap',
150 151
            'finderType': 'ByText',
            'text': 'foo',
152
          });
Ian Hickson's avatar
Ian Hickson committed
153
          return new Future<Map<String, dynamic>>.value();
154
        });
155
        await driver.tap(find.text('foo'));
156 157 158 159 160 161 162 163 164 165
      });
    });

    group('getText', () {
      test('requires a target reference', () async {
        expect(driver.getText(null), throwsA(new isInstanceOf<DriverError>()));
      });

      test('sends the getText 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': 'get_text',
168 169 170
            'finderType': 'ByValueKey',
            'keyValueString': '123',
            'keyValueType': 'int'
171
          });
pq's avatar
pq committed
172
          return new Future<Map<String, dynamic>>.value(<String, String>{
173 174 175
            'text': 'hello'
          });
        });
176
        String result = await driver.getText(find.byValueKey(123));
177 178 179
        expect(result, 'hello');
      });
    });
180 181

    group('waitFor', () {
182 183
      test('requires a target reference', () async {
        expect(driver.waitFor(null), throwsA(new isInstanceOf<DriverError>()));
184 185
      });

186 187 188 189 190 191 192 193
      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',
            'timeout': '1000',
          });
pq's avatar
pq committed
194
          return new Future<Map<String, dynamic>>.value(<String, dynamic>{});
195
        });
196
        await driver.waitFor(find.byTooltip('foo'), timeout: new Duration(seconds: 1));
197 198
      });
    });
199 200 201 202 203 204 205

    group('traceAction', () {
      test('traces action', () async {
        bool actionCalled = false;
        bool startTracingCalled = false;
        bool stopTracingCalled = false;

pq's avatar
pq committed
206
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[all]'}))))
207 208 209 210 211
          .thenAnswer((_) async {
            startTracingCalled = true;
            return null;
          });

pq's avatar
pq committed
212
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
213 214 215 216 217 218 219
          .thenAnswer((_) async {
            stopTracingCalled = true;
            return null;
          });

        when(mockPeer.sendRequest('_getVMTimeline')).thenAnswer((_) async {
          return <String, dynamic> {
pq's avatar
pq committed
220 221
            'traceEvents': <dynamic>[
              <String, String>{
222 223 224
                'name': 'test event'
              }
            ],
225 226 227
          };
        });

228
        Timeline timeline = await driver.traceAction(() {
229 230 231 232 233 234
          actionCalled = true;
        });

        expect(actionCalled, isTrue);
        expect(startTracingCalled, isTrue);
        expect(stopTracingCalled, isTrue);
235
        expect(timeline.events.single.name, 'test event');
236 237
      });
    });
238

239 240
    group('traceAction with timeline streams', () {
      test('specify non-default timeline streams', () async {
241 242 243 244
        bool actionCalled = false;
        bool startTracingCalled = false;
        bool stopTracingCalled = false;

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

pq's avatar
pq committed
251
        when(mockPeer.sendRequest('_setVMTimelineFlags', argThat(equals(<String, dynamic>{'recordedStreams': '[]'}))))
252 253 254 255 256 257 258
          .thenAnswer((_) async {
            stopTracingCalled = true;
            return null;
          });

        when(mockPeer.sendRequest('_getVMTimeline')).thenAnswer((_) async {
          return <String, dynamic> {
pq's avatar
pq committed
259 260
            'traceEvents': <dynamic>[
              <String, String>{
261 262 263 264 265 266 267 268 269
                'name': 'test event'
              }
            ],
          };
        });

        Timeline timeline = await driver.traceAction(() {
          actionCalled = true;
        },
270 271 272 273
        streams: const <TimelineStream>[
          TimelineStream.dart,
          TimelineStream.gc,
          TimelineStream.compiler
274 275 276 277 278 279 280 281
        ]);

        expect(actionCalled, isTrue);
        expect(startTracingCalled, isTrue);
        expect(stopTracingCalled, isTrue);
        expect(timeline.events.single.name, 'test event');
      });
    });
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
  });
}

@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 { }
302 303 304

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