driver.dart 30.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6
import 'dart:io';
7

8
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
9
import 'package:meta/meta.dart';
10
import 'package:vm_service_client/vm_service_client.dart';
11

12
import '../common/diagnostics_tree.dart';
13 14 15
import '../common/error.dart';
import '../common/find.dart';
import '../common/frame_sync.dart';
16
import '../common/geometry.dart';
17 18
import '../common/gesture.dart';
import '../common/health.dart';
19
import '../common/layer_tree.dart';
20 21 22 23
import '../common/message.dart';
import '../common/render_tree.dart';
import '../common/request_data.dart';
import '../common/semantics.dart';
24
import '../common/text.dart';
25
import '../common/wait.dart';
26
import 'timeline.dart';
27 28 29 30 31
import 'vmservice_driver.dart';
import 'web_driver.dart';

export 'vmservice_driver.dart';
export 'web_driver.dart';
32

33
/// Timeline stream identifier.
34
enum TimelineStream {
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
  /// A meta-identifier that instructs the Dart VM to record all streams.
  all,

  /// Marks events related to calls made via Dart's C API.
  api,

  /// Marks events from the Dart VM's JIT compiler.
  compiler,

  /// Marks events emitted using the `dart:developer` API.
  dart,

  /// Marks events from the Dart VM debugger.
  debugger,

  /// Marks events emitted using the `dart_tools_api.h` C API.
  embedder,

  /// Marks events from the garbage collector.
  gc,

  /// Marks events related to message passing between Dart isolates.
  isolate,

  /// Marks internal VM events.
  vm,
61 62
}

63 64
/// How long to wait before showing a message saying that
/// things seem to be taking a long time.
65 66
@visibleForTesting
const Duration kUnusuallyLongTimeout = Duration(seconds: 5);
67

68 69 70 71
/// A convenient accessor to frequently used finders.
///
/// Examples:
///
72
///     driver.tap(find.text('Save'));
73
///     driver.scroll(find.byValueKey(42));
74
const CommonFinders find = CommonFinders._();
75

76 77 78 79 80
/// Computes a value.
///
/// If computation is asynchronous, the function may return a [Future].
///
/// See also [FlutterDriver.waitFor].
81
typedef EvaluatorFunction = dynamic Function();
82

83
/// Drives a Flutter Application running in another process.
84 85
abstract class FlutterDriver {
  /// Default constructor.
86
  @visibleForTesting
87 88 89
  FlutterDriver();

  /// Creates a driver that uses a connection provided by either the combination
90
  /// of [webConnection], or the combination of [serviceClient],
91 92 93 94 95 96 97 98
  /// [peer] and [appIsolate]
  @visibleForTesting
  factory FlutterDriver.connectedTo({
    FlutterWebConnection webConnection,
    VMServiceClient serviceClient,
    rpc.Peer peer,
    VMIsolate appIsolate,
  }) {
99 100
    if (webConnection != null) {
      return WebFlutterDriver.connectedTo(webConnection);
101 102 103
    }
    return VMServiceFlutterDriver.connectedTo(serviceClient, peer, appIsolate);
  }
104

105 106 107 108
  /// Connects to a Flutter application.
  ///
  /// Resumes the application if it is currently paused (e.g. at a breakpoint).
  ///
109
  /// `dartVmServiceUrl` is the URL to Dart observatory (a.k.a. VM service). If
110
  /// not specified, the URL specified by the `VM_SERVICE_URL` environment
111
  /// variable is used. One or the other must be specified.
112
  ///
113
  /// `printCommunication` determines whether the command communication between
114 115
  /// the test and the app should be printed to stdout.
  ///
116
  /// `logCommunicationToFile` determines whether the command communication
117
  /// between the test and the app should be logged to `flutter_driver_commands.log`.
118
  ///
119
  /// `isolateNumber` determines the specific isolate to connect to.
120
  /// If this is left as `null`, will connect to the first isolate found
121 122 123 124 125 126 127 128 129
  /// running on `dartVmServiceUrl`.
  ///
  /// `fuchsiaModuleTarget` specifies the pattern for determining which mod to
  /// control. When running on a Fuchsia device, either this or the environment
  /// variable `FUCHSIA_MODULE_TARGET` must be set (the environment variable is
  /// treated as a substring pattern). This field will be ignored if
  /// `isolateNumber` is set, as this is already enough information to connect
  /// to an isolate.
  ///
130 131 132 133
  /// `browser` specifies which FlutterDriver implementation to use. If not
  /// speicifed or set to false, [VMServiceFlutterDriver] implementation
  /// will be used. Otherwise, [WebFlutterDriver] implementation will be used.
  ///
134 135 136
  /// The return value is a future. This method never times out, though it may
  /// fail (completing with an error). A timeout can be applied by the caller
  /// using [Future.timeout] if necessary.
137 138
  static Future<FlutterDriver> connect({
    String dartVmServiceUrl,
139 140
    bool printCommunication = false,
    bool logCommunicationToFile = true,
141
    int isolateNumber,
142
    Pattern fuchsiaModuleTarget,
143
    Duration timeout,
144
  }) async {
145
    if (Platform.environment['FLUTTER_WEB_TEST'] != null) {
146
      return WebFlutterDriver.connectWeb(hostUrl: dartVmServiceUrl, timeout: timeout);
147
    }
148 149 150 151 152 153
    return VMServiceFlutterDriver.connect(
              dartVmServiceUrl: dartVmServiceUrl,
              printCommunication: printCommunication,
              logCommunicationToFile: logCommunicationToFile,
              isolateNumber: isolateNumber,
              fuchsiaModuleTarget: fuchsiaModuleTarget,
154
    );
155 156
  }

157
  /// Getter of appIsolate
158
  VMIsolate get appIsolate => throw UnimplementedError();
159

160
  /// Getter of serviceClient
161
  VMServiceClient get serviceClient => throw UnimplementedError();
162

163 164
  /// Sends [command] to the Flutter Driver extensions.
  /// This must be implemented by subclass.
165
  ///
166 167 168 169
  /// See also:
  ///
  ///  * [VMServiceFlutterDriver], which uses vmservice to implement.
  ///  * [WebFlutterDriver], which uses webdriver to implement.
170
  Future<Map<String, dynamic>> sendCommand(Command command) => throw UnimplementedError();
171

172
  /// Checks the status of the Flutter Driver extension.
173
  Future<Health> checkHealth({ Duration timeout }) async {
174
    return Health.fromJson(await sendCommand(GetHealth(timeout: timeout)));
175 176
  }

177
  /// Returns a dump of the render tree.
178
  Future<RenderTree> getRenderTree({ Duration timeout }) async {
179
    return RenderTree.fromJson(await sendCommand(GetRenderTree(timeout: timeout)));
180 181
  }

182 183
  /// Returns a dump of the layer tree.
  Future<LayerTree> getLayerTree({ Duration timeout }) async {
184
    return LayerTree.fromJson(await sendCommand(GetLayerTree(timeout: timeout)));
185 186
  }

187
  /// Taps at the center of the widget located by [finder].
188
  Future<void> tap(SerializableFinder finder, { Duration timeout }) async {
189
    await sendCommand(Tap(finder, timeout: timeout));
190
  }
191

192
  /// Waits until [finder] locates the target.
193
  Future<void> waitFor(SerializableFinder finder, { Duration timeout }) async {
194
    await sendCommand(WaitFor(finder, timeout: timeout));
195 196
  }

197
  /// Waits until [finder] can no longer locate the target.
198
  Future<void> waitForAbsent(SerializableFinder finder, { Duration timeout }) async {
199
    await sendCommand(WaitForAbsent(finder, timeout: timeout));
200 201
  }

202 203
  /// Waits until the given [waitCondition] is satisfied.
  Future<void> waitForCondition(SerializableWaitCondition waitCondition, {Duration timeout}) async {
204
    await sendCommand(WaitForCondition(waitCondition, timeout: timeout));
205 206
  }

207 208 209 210
  /// Waits until there are no more transient callbacks in the queue.
  ///
  /// Use this method when you need to wait for the moment when the application
  /// becomes "stable", for example, prior to taking a [screenshot].
211
  Future<void> waitUntilNoTransientCallbacks({ Duration timeout }) async {
212
    await sendCommand(WaitForCondition(const NoTransientCallbacks(), timeout: timeout));
213 214
  }

215 216 217 218
  /// Waits until the next [Window.onReportTimings] is called.
  ///
  /// Use this method to wait for the first frame to be rasterized during the
  /// app launch.
219 220
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
221
  Future<void> waitUntilFirstFrameRasterized() async {
222
    await sendCommand(const WaitForCondition(FirstFrameRasterized()));
223 224
  }

225 226
  Future<DriverOffset> _getOffset(SerializableFinder finder, OffsetType type, { Duration timeout }) async {
    final GetOffset command = GetOffset(finder, type, timeout: timeout);
227
    final GetOffsetResult result = GetOffsetResult.fromJson(await sendCommand(command));
228 229 230 231
    return DriverOffset(result.dx, result.dy);
  }

  /// Returns the point at the top left of the widget identified by `finder`.
232 233 234
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
235 236 237 238 239
  Future<DriverOffset> getTopLeft(SerializableFinder finder, { Duration timeout }) async {
    return _getOffset(finder, OffsetType.topLeft, timeout: timeout);
  }

  /// Returns the point at the top right of the widget identified by `finder`.
240 241 242
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
243 244 245 246 247
  Future<DriverOffset> getTopRight(SerializableFinder finder, { Duration timeout }) async {
    return _getOffset(finder, OffsetType.topRight, timeout: timeout);
  }

  /// Returns the point at the bottom left of the widget identified by `finder`.
248 249 250
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
251 252 253 254 255
  Future<DriverOffset> getBottomLeft(SerializableFinder finder, { Duration timeout }) async {
    return _getOffset(finder, OffsetType.bottomLeft, timeout: timeout);
  }

  /// Returns the point at the bottom right of the widget identified by `finder`.
256 257 258
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
259 260 261 262 263
  Future<DriverOffset> getBottomRight(SerializableFinder finder, { Duration timeout }) async {
    return _getOffset(finder, OffsetType.bottomRight, timeout: timeout);
  }

  /// Returns the point at the center of the widget identified by `finder`.
264 265 266
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
267 268 269 270
  Future<DriverOffset> getCenter(SerializableFinder finder, { Duration timeout }) async {
    return _getOffset(finder, OffsetType.center, timeout: timeout);
  }

271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
  /// Returns a JSON map of the [DiagnosticsNode] that is associated with the
  /// [RenderObject] identified by `finder`.
  ///
  /// The `subtreeDepth` argument controls how many layers of children will be
  /// included in the result. It defaults to zero, which means that no children
  /// of the [RenderObject] identified by `finder` will be part of the result.
  ///
  /// The `includeProperties` argument controls whether properties of the
  /// [DiagnosticsNode]s will be included in the result. It defaults to true.
  ///
  /// [RenderObject]s are responsible for positioning, layout, and painting on
  /// the screen, based on the configuration from a [Widget]. Callers that need
  /// information about size or position should use this method.
  ///
  /// A widget may indirectly create multiple [RenderObject]s, which each
  /// implement some aspect of the widget configuration. A 1:1 relationship
  /// should not be assumed.
  ///
  /// See also:
  ///
  ///  * [getWidgetDiagnostics], which gets the [DiagnosticsNode] of a [Widget].
  Future<Map<String, Object>> getRenderObjectDiagnostics(
      SerializableFinder finder, {
      int subtreeDepth = 0,
      bool includeProperties = true,
      Duration timeout,
  }) async {
298
    return sendCommand(GetDiagnosticsTree(
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
      finder,
      DiagnosticsType.renderObject,
      subtreeDepth: subtreeDepth,
      includeProperties: includeProperties,
      timeout: timeout,
    ));
  }

  /// Returns a JSON map of the [DiagnosticsNode] that is associated with the
  /// [Widget] identified by `finder`.
  ///
  /// The `subtreeDepth` argument controls how many layers of children will be
  /// included in the result. It defaults to zero, which means that no children
  /// of the [Widget] identified by `finder` will be part of the result.
  ///
  /// The `includeProperties` argument controls whether properties of the
  /// [DiagnosticsNode]s will be included in the result. It defaults to true.
  ///
  /// [Widget]s describe configuration for the rendering tree. Individual
  /// widgets may create multiple [RenderObject]s to actually layout and paint
  /// the desired configuration.
  ///
  /// See also:
  ///
  ///  * [getRenderObjectDiagnostics], which gets the [DiagnosticsNode] of a
  ///    [RenderObject].
  Future<Map<String, Object>> getWidgetDiagnostics(
    SerializableFinder finder, {
    int subtreeDepth = 0,
    bool includeProperties = true,
    Duration timeout,
  }) async {
331
    return sendCommand(GetDiagnosticsTree(
332
      finder,
333
      DiagnosticsType.widget,
334 335 336 337 338 339
      subtreeDepth: subtreeDepth,
      includeProperties: includeProperties,
      timeout: timeout,
    ));
  }

340 341 342 343 344 345 346 347 348
  /// Tell the driver to perform a scrolling action.
  ///
  /// A scrolling action begins with a "pointer down" event, which commonly maps
  /// to finger press on the touch screen or mouse button press. A series of
  /// "pointer move" events follow. The action is completed by a "pointer up"
  /// event.
  ///
  /// [dx] and [dy] specify the total offset for the entire scrolling action.
  ///
349
  /// [duration] specifies the length of the action.
350 351 352
  ///
  /// The move events are generated at a given [frequency] in Hz (or events per
  /// second). It defaults to 60Hz.
353
  Future<void> scroll(SerializableFinder finder, double dx, double dy, Duration duration, { int frequency = 60, Duration timeout }) async {
354
    await sendCommand(Scroll(finder, dx, dy, duration, frequency, timeout: timeout));
355 356
  }

357 358
  /// Scrolls the Scrollable ancestor of the widget located by [finder]
  /// until the widget is completely visible.
359 360 361 362 363
  ///
  /// If the widget located by [finder] is contained by a scrolling widget
  /// that lazily creates its children, like [ListView] or [CustomScrollView],
  /// then this method may fail because [finder] doesn't actually exist.
  /// The [scrollUntilVisible] method can be used in this case.
364
  Future<void> scrollIntoView(SerializableFinder finder, { double alignment = 0.0, Duration timeout }) async {
365
    await sendCommand(ScrollIntoView(finder, alignment: alignment, timeout: timeout));
366 367
  }

368 369 370 371 372
  /// Repeatedly [scroll] the widget located by [scrollable] by [dxScroll] and
  /// [dyScroll] until [item] is visible, and then use [scrollIntoView] to
  /// ensure the item's final position matches [alignment].
  ///
  /// The [scrollable] must locate the scrolling widget that contains [item].
373
  /// Typically `find.byType('ListView')` or `find.byType('CustomScrollView')`.
374
  ///
375
  /// At least one of [dxScroll] and [dyScroll] must be non-zero.
376 377 378 379
  ///
  /// If [item] is below the currently visible items, then specify a negative
  /// value for [dyScroll] that's a small enough increment to expose [item]
  /// without potentially scrolling it up and completely out of view. Similarly
380
  /// if [item] is above, then specify a positive value for [dyScroll].
381
  ///
382
  /// If [item] is to the right of the currently visible items, then
383 384
  /// specify a negative value for [dxScroll] that's a small enough increment to
  /// expose [item] without potentially scrolling it up and completely out of
385
  /// view. Similarly if [item] is to the left, then specify a positive value
386 387 388
  /// for [dyScroll].
  ///
  /// The [timeout] value should be long enough to accommodate as many scrolls
389
  /// as needed to bring an item into view. The default is to not time out.
390 391 392
  Future<void> scrollUntilVisible(
    SerializableFinder scrollable,
    SerializableFinder item, {
393 394 395
    double alignment = 0.0,
    double dxScroll = 0.0,
    double dyScroll = 0.0,
396
    Duration timeout,
397 398 399 400 401 402 403 404
  }) async {
    assert(scrollable != null);
    assert(item != null);
    assert(alignment != null);
    assert(dxScroll != null);
    assert(dyScroll != null);
    assert(dxScroll != 0.0 || dyScroll != 0.0);

405 406 407 408
    // Kick off an (unawaited) waitFor that will complete when the item we're
    // looking for finally scrolls onscreen. We add an initial pause to give it
    // the chance to complete if the item is already onscreen; if not, scroll
    // repeatedly until we either find the item or time out.
409
    bool isVisible = false;
410 411
    waitFor(item, timeout: timeout).then<void>((_) { isVisible = true; });
    await Future<void>.delayed(const Duration(milliseconds: 500));
412 413
    while (!isVisible) {
      await scroll(scrollable, dxScroll, dyScroll, const Duration(milliseconds: 100));
414
      await Future<void>.delayed(const Duration(milliseconds: 500));
415 416 417 418 419
    }

    return scrollIntoView(item, alignment: alignment);
  }

420
  /// Returns the text in the `Text` widget located by [finder].
421
  Future<String> getText(SerializableFinder finder, { Duration timeout }) async {
422
    return GetTextResult.fromJson(await sendCommand(GetText(finder, timeout: timeout))).text;
423 424
  }

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
  /// Enters `text` into the currently focused text input, such as the
  /// [EditableText] widget.
  ///
  /// This method does not use the operating system keyboard to enter text.
  /// Instead it emulates text entry by sending events identical to those sent
  /// by the operating system keyboard (the "TextInputClient.updateEditingState"
  /// method channel call).
  ///
  /// Generally the behavior is dependent on the implementation of the widget
  /// receiving the input. Usually, editable widgets, such as [EditableText] and
  /// those built on top of it would replace the currently entered text with the
  /// provided `text`.
  ///
  /// It is assumed that the widget receiving text input is focused prior to
  /// calling this method. Typically, a test would activate a widget, e.g. using
  /// [tap], then call this method.
  ///
442 443 444
  /// For this method to work, text emulation must be enabled (see
  /// [setTextEntryEmulation]). Text emulation is enabled by default.
  ///
445 446 447 448
  /// Example:
  ///
  /// ```dart
  /// test('enters text in a text field', () async {
449 450 451 452 453 454
  ///   var textField = find.byValueKey('enter-text-field');
  ///   await driver.tap(textField);  // acquire focus
  ///   await driver.enterText('Hello!');  // enter text
  ///   await driver.waitFor(find.text('Hello!'));  // verify text appears on UI
  ///   await driver.enterText('World!');  // enter another piece of text
  ///   await driver.waitFor(find.text('World!'));  // verify new text appears
455 456
  /// });
  /// ```
457
  Future<void> enterText(String text, { Duration timeout }) async {
458
    await sendCommand(EnterText(text, timeout: timeout));
459 460
  }

461 462
  /// Configures text entry emulation.
  ///
463
  /// If `enabled` is true, enables text entry emulation via [enterText]. If
464 465 466 467 468 469
  /// `enabled` is false, disables it. By default text entry emulation is
  /// enabled.
  ///
  /// When disabled, [enterText] will fail with a [DriverError]. When an
  /// [EditableText] is focused, the operating system's configured keyboard
  /// method is invoked, such as an on-screen keyboard on a phone or a tablet.
470
  ///
471 472
  /// When enabled, the operating system's configured keyboard will not be
  /// invoked when the widget is focused, as the [SystemChannels.textInput]
473
  /// channel will be mocked out.
474
  Future<void> setTextEntryEmulation({ @required bool enabled, Duration timeout }) async {
475
    assert(enabled != null);
476
    await sendCommand(SetTextEntryEmulation(enabled, timeout: timeout));
477 478
  }

479 480
  /// Sends a string and returns a string.
  ///
481 482 483 484
  /// This enables generic communication between the driver and the application.
  /// It's expected that the application has registered a [DataHandler]
  /// callback in [enableFlutterDriverExtension] that can successfully handle
  /// these requests.
485
  Future<String> requestData(String message, { Duration timeout }) async {
486
    return RequestDataResult.fromJson(await sendCommand(RequestData(message, timeout: timeout))).message;
487 488
  }

489 490
  /// Turns semantics on or off in the Flutter app under test.
  ///
491
  /// Returns true when the call actually changed the state from on to off or
492
  /// vice versa.
493
  Future<bool> setSemantics(bool enabled, { Duration timeout }) async {
494
    final SetSemanticsResult result = SetSemanticsResult.fromJson(await sendCommand(SetSemantics(enabled, timeout: timeout)));
495 496 497
    return result.changedState;
  }

498 499
  /// Retrieves the semantics node id for the object returned by `finder`, or
  /// the nearest ancestor with a semantics node.
500
  ///
501 502
  /// Throws an error if `finder` returns multiple elements or a semantics
  /// node is not found.
503
  ///
504 505
  /// Semantics must be enabled to use this method, either using a platform
  /// specific shell command or [setSemantics].
506
  Future<int> getSemanticsId(SerializableFinder finder, { Duration timeout }) async {
507
    final Map<String, dynamic> jsonResponse = await sendCommand(GetSemanticsId(finder, timeout: timeout));
508 509 510 511
    final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson(jsonResponse);
    return result.id;
  }

512 513 514
  /// Take a screenshot.
  ///
  /// The image will be returned as a PNG.
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
  ///
  ///  HACK: There will be a 2-second artificial delay before screenshotting,
  ///        the delay here is to deal with a race between the driver script and
  ///        the GPU thread. The issue is that driver API synchronizes with the
  ///        framework based on transient callbacks, which are out of sync with
  ///        the GPU thread. Here's the timeline of events in ASCII art:
  ///
  ///        -------------------------------------------------------------------
  ///        Without this delay:
  ///        -------------------------------------------------------------------
  ///        UI    : <-- build -->
  ///        GPU   :               <-- rasterize -->
  ///        Gap   :              | random |
  ///        Driver:                        <-- screenshot -->
  ///
  ///        In the diagram above, the gap is the time between the last driver
  ///        action taken, such as a `tap()`, and the subsequent call to
  ///        `screenshot()`. The gap is random because it is determined by the
  ///        unpredictable network communication between the driver process and
  ///        the application. If this gap is too short, which it typically will
  ///        be, the screenshot is taken before the GPU thread is done
  ///        rasterizing the frame, so the screenshot of the previous frame is
  ///        taken, which is wrong.
  ///
  ///        -------------------------------------------------------------------
  ///        With this delay, if we're lucky:
  ///        -------------------------------------------------------------------
  ///        UI    : <-- build -->
  ///        GPU   :               <-- rasterize -->
  ///        Gap   :              |    2 seconds or more   |
  ///        Driver:                                        <-- screenshot -->
  ///
  ///        The two-second gap should be long enough for the GPU thread to
  ///        finish rasterizing the frame, but not longer than necessary to keep
  ///        driver tests as fast a possible.
  ///
  ///        -------------------------------------------------------------------
  ///        With this delay, if we're not lucky:
  ///        -------------------------------------------------------------------
  ///        UI    : <-- build -->
  ///        GPU   :               <-- rasterize randomly slow today -->
  ///        Gap   :              |    2 seconds or more   |
  ///        Driver:                                        <-- screenshot -->
  ///
  ///        In practice, sometimes the device gets really busy for a while and
  ///        even two seconds isn't enough, which means that this is still racy
  ///        and a source of flakes.
562 563 564
  Future<List<int>> screenshot() {
    throw UnimplementedError();
  }
565

566 567
  /// Returns the Flags set in the Dart VM as JSON.
  ///
568 569
  /// See the complete documentation for [the `getFlagList` Dart VM service
  /// method][getFlagList].
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
  ///
  /// Example return value:
  ///
  ///     [
  ///       {
  ///         "name": "timeline_recorder",
  ///         "comment": "Select the timeline recorder used. Valid values: ring, endless, startup, and systrace.",
  ///         "modified": false,
  ///         "_flagType": "String",
  ///         "valueAsString": "ring"
  ///       },
  ///       ...
  ///     ]
  ///
  /// [getFlagList]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#getflaglist
585 586
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
587 588 589
  Future<List<Map<String, dynamic>>> getVmFlags() {
    throw UnimplementedError();
  }
590
  /// Starts recording performance traces.
591 592 593 594
  ///
  /// The `timeout` argument causes a warning to be displayed to the user if the
  /// operation exceeds the specified timeout; it does not actually cancel the
  /// operation.
595 596
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
597
  Future<void> startTracing({
598
    List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
599
    Duration timeout = kUnusuallyLongTimeout,
600 601 602
  }) {
    throw UnimplementedError();
  }
603

604
  /// Stops recording performance traces and downloads the timeline.
605 606 607 608
  ///
  /// The `timeout` argument causes a warning to be displayed to the user if the
  /// operation exceeds the specified timeout; it does not actually cancel the
  /// operation.
609 610
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
611
  Future<Timeline> stopTracingAndDownloadTimeline({
612
    Duration timeout = kUnusuallyLongTimeout,
613 614 615
  }) {
    throw UnimplementedError();
  }
616 617 618 619 620 621
  /// Runs [action] and outputs a performance trace for it.
  ///
  /// Waits for the `Future` returned by [action] to complete prior to stopping
  /// the trace.
  ///
  /// This is merely a convenience wrapper on top of [startTracing] and
622
  /// [stopTracingAndDownloadTimeline].
623 624 625
  ///
  /// [streams] limits the recorded timeline event streams to only the ones
  /// listed. By default, all streams are recorded.
626 627 628 629
  ///
  /// If [retainPriorEvents] is true, retains events recorded prior to calling
  /// [action]. Otherwise, prior events are cleared before calling [action]. By
  /// default, prior events are cleared.
630 631 632
  ///
  /// If this is run in debug mode, a warning message will be printed to suggest
  /// running the benchmark in profile mode instead.
633 634
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
635 636
  Future<Timeline> traceAction(
    Future<dynamic> action(), {
637
    List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
638
    bool retainPriorEvents = false,
639 640 641
  }) {
    throw UnimplementedError();
  }
642

643
  /// Clears all timeline events recorded up until now.
644 645 646 647
  ///
  /// The `timeout` argument causes a warning to be displayed to the user if the
  /// operation exceeds the specified timeout; it does not actually cancel the
  /// operation.
648 649
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
650
  Future<void> clearTimeline({
651
    Duration timeout = kUnusuallyLongTimeout,
652 653 654
  }) {
    throw UnimplementedError();
  }
655 656 657 658 659 660 661 662 663 664 665 666 667
  /// [action] will be executed with the frame sync mechanism disabled.
  ///
  /// By default, Flutter Driver waits until there is no pending frame scheduled
  /// in the app under test before executing an action. This mechanism is called
  /// "frame sync". It greatly reduces flakiness because Flutter Driver will not
  /// execute an action while the app under test is undergoing a transition.
  ///
  /// Having said that, sometimes it is necessary to disable the frame sync
  /// mechanism (e.g. if there is an ongoing animation in the app, it will
  /// never reach a state where there are no pending frames scheduled and the
  /// action will time out). For these cases, the sync mechanism can be disabled
  /// by wrapping the actions to be performed by this [runUnsynchronized] method.
  ///
668
  /// With frame sync disabled, it's the responsibility of the test author to
669 670
  /// ensure that no action is performed while the app is undergoing a
  /// transition to avoid flakiness.
671
  Future<T> runUnsynchronized<T>(Future<T> action(), { Duration timeout }) async {
672
    await sendCommand(SetFrameSync(false, timeout: timeout));
673
    T result;
674 675 676
    try {
      result = await action();
    } finally {
677
      await sendCommand(SetFrameSync(true, timeout: timeout));
678 679 680 681
    }
    return result;
  }

682
  /// Force a garbage collection run in the VM.
683 684
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
685 686 687
  Future<void> forceGC() {
    throw UnimplementedError();
  }
688

689 690 691
  /// Closes the underlying connection to the VM service.
  ///
  /// Returns a [Future] that fires once the connection has been closed.
692 693 694
  Future<void> close() {
    throw UnimplementedError();
  }
yjbanov's avatar
yjbanov committed
695
}
696 697 698 699 700

/// Provides convenient accessors to frequently used finders.
class CommonFinders {
  const CommonFinders._();

701
  /// Finds [Text] and [EditableText] widgets containing string equal to [text].
702
  SerializableFinder text(String text) => ByText(text);
703

704
  /// Finds widgets by [key]. Only [String] and [int] values can be used.
705
  SerializableFinder byValueKey(dynamic key) => ByValueKey(key);
706 707

  /// Finds widgets with a tooltip with the given [message].
708
  SerializableFinder byTooltip(String message) => ByTooltipMessage(message);
709

710 711 712
  /// Finds widgets with the given semantics [label].
  SerializableFinder bySemanticsLabel(Pattern label) => BySemanticsLabel(label);

713
  /// Finds widgets whose class name matches the given string.
714
  SerializableFinder byType(String type) => ByType(type);
715 716

  /// Finds the back button on a Material or Cupertino page's scaffold.
717
  SerializableFinder pageBack() => const PageBack();
718 719 720 721 722 723

  /// Finds the widget that is an ancestor of the `of` parameter and that
  /// matches the `matching` parameter.
  ///
  /// If the `matchRoot` argument is true then the widget specified by `of` will
  /// be considered for a match. The argument defaults to false.
724 725 726
  ///
  /// If `firstMatchOnly` is true then only the first ancestor matching
  /// `matching` will be returned. Defaults to false.
727 728 729 730
  SerializableFinder ancestor({
    @required SerializableFinder of,
    @required SerializableFinder matching,
    bool matchRoot = false,
731 732
    bool firstMatchOnly = false,
  }) => Ancestor(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
733 734 735 736 737 738

  /// Finds the widget that is an descendant of the `of` parameter and that
  /// matches the `matching` parameter.
  ///
  /// If the `matchRoot` argument is true then the widget specified by `of` will
  /// be considered for a match. The argument defaults to false.
739 740 741
  ///
  /// If `firstMatchOnly` is true then only the first descendant matching
  /// `matching` will be returned. Defaults to false.
742 743 744 745
  SerializableFinder descendant({
    @required SerializableFinder of,
    @required SerializableFinder matching,
    bool matchRoot = false,
746 747
    bool firstMatchOnly = false,
  }) => Descendant(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
748
}
749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764

/// An immutable 2D floating-point offset used by Flutter Driver.
class DriverOffset {
  /// Creates an offset.
  const DriverOffset(this.dx, this.dy);

  /// The x component of the offset.
  final double dx;

  /// The y component of the offset.
  final double dy;

  @override
  String toString() => '$runtimeType($dx, $dy)';

  @override
765
  bool operator ==(Object other) {
766 767 768
    return other is DriverOffset
        && other.dx == dx
        && other.dy == dy;
769 770 771
  }

  @override
772
  int get hashCode => dx.hashCode ^ dy.hashCode;
773
}