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

5 6
// @dart = 2.8

7
import 'dart:io';
8

9
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
10
import 'package:meta/meta.dart';
11
import 'package:vm_service_client/vm_service_client.dart';
12
import 'package:webdriver/async_io.dart' as async_io;
13

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

export 'vmservice_driver.dart';
export 'web_driver.dart';
34

35
/// Timeline stream identifier.
36
enum TimelineStream {
37 38 39 40 41 42 43 44 45
  /// 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,

46 47 48
  /// The verbose version of compiler.
  compilerVerbose,

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
  /// 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,
66 67
}

68 69
/// How long to wait before showing a message saying that
/// things seem to be taking a long time.
70 71
@visibleForTesting
const Duration kUnusuallyLongTimeout = Duration(seconds: 5);
72

73 74 75 76
/// A convenient accessor to frequently used finders.
///
/// Examples:
///
77
///     driver.tap(find.text('Save'));
78
///     driver.scroll(find.byValueKey(42));
79
const CommonFinders find = CommonFinders._();
80

81 82 83 84 85
/// Computes a value.
///
/// If computation is asynchronous, the function may return a [Future].
///
/// See also [FlutterDriver.waitFor].
86
typedef EvaluatorFunction = dynamic Function();
87

88
/// Drives a Flutter Application running in another process.
89 90
abstract class FlutterDriver {
  /// Default constructor.
91
  @visibleForTesting
92 93 94
  FlutterDriver();

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

110 111 112 113
  /// Connects to a Flutter application.
  ///
  /// Resumes the application if it is currently paused (e.g. at a breakpoint).
  ///
114
  /// `dartVmServiceUrl` is the URL to Dart observatory (a.k.a. VM service). If
115
  /// not specified, the URL specified by the `VM_SERVICE_URL` environment
116
  /// variable is used. One or the other must be specified.
117
  ///
118
  /// `printCommunication` determines whether the command communication between
119 120
  /// the test and the app should be printed to stdout.
  ///
121
  /// `logCommunicationToFile` determines whether the command communication
122
  /// between the test and the app should be logged to `flutter_driver_commands.log`.
123
  ///
124
  /// `isolateNumber` determines the specific isolate to connect to.
125
  /// If this is left as `null`, will connect to the first isolate found
126 127 128 129 130 131 132 133 134
  /// 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.
  ///
135 136 137 138
  /// `headers` optionally specifies HTTP headers to be included in the
  /// [WebSocket] connection. This is only used for [VMServiceFlutterDriver]
  /// connections.
  ///
139 140 141 142
  /// `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.
  ///
143 144 145
  /// 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.
146 147
  static Future<FlutterDriver> connect({
    String dartVmServiceUrl,
148 149
    bool printCommunication = false,
    bool logCommunicationToFile = true,
150
    int isolateNumber,
151
    Pattern fuchsiaModuleTarget,
152
    Duration timeout,
153
    Map<String, dynamic> headers,
154
  }) async {
155
    if (Platform.environment['FLUTTER_WEB_TEST'] != null) {
156
      return WebFlutterDriver.connectWeb(hostUrl: dartVmServiceUrl, timeout: timeout);
157
    }
158 159 160 161 162 163
    return VMServiceFlutterDriver.connect(
              dartVmServiceUrl: dartVmServiceUrl,
              printCommunication: printCommunication,
              logCommunicationToFile: logCommunicationToFile,
              isolateNumber: isolateNumber,
              fuchsiaModuleTarget: fuchsiaModuleTarget,
164
              headers: headers,
165
    );
166 167
  }

168
  /// Getter of appIsolate.
169
  VMIsolate get appIsolate => throw UnimplementedError();
170

171
  /// Getter of serviceClient.
172
  VMServiceClient get serviceClient => throw UnimplementedError();
173

174 175 176 177 178 179 180 181
  /// Getter of webDriver.
  async_io.WebDriver get webDriver => throw UnimplementedError();

  /// Enables accessibility feature.
  Future<void> enableAccessibility() async {
    throw UnimplementedError();
  }

182 183
  /// Sends [command] to the Flutter Driver extensions.
  /// This must be implemented by subclass.
184
  ///
185 186 187 188
  /// See also:
  ///
  ///  * [VMServiceFlutterDriver], which uses vmservice to implement.
  ///  * [WebFlutterDriver], which uses webdriver to implement.
189
  Future<Map<String, dynamic>> sendCommand(Command command) async => throw UnimplementedError();
190

191
  /// Checks the status of the Flutter Driver extension.
192
  Future<Health> checkHealth({ Duration timeout }) async {
193
    return Health.fromJson(await sendCommand(GetHealth(timeout: timeout)));
194 195
  }

196
  /// Returns a dump of the render tree.
197
  Future<RenderTree> getRenderTree({ Duration timeout }) async {
198
    return RenderTree.fromJson(await sendCommand(GetRenderTree(timeout: timeout)));
199 200
  }

201 202
  /// Returns a dump of the layer tree.
  Future<LayerTree> getLayerTree({ Duration timeout }) async {
203
    return LayerTree.fromJson(await sendCommand(GetLayerTree(timeout: timeout)));
204 205
  }

206
  /// Taps at the center of the widget located by [finder].
207
  Future<void> tap(SerializableFinder finder, { Duration timeout }) async {
208
    await sendCommand(Tap(finder, timeout: timeout));
209
  }
210

211
  /// Waits until [finder] locates the target.
212
  Future<void> waitFor(SerializableFinder finder, { Duration timeout }) async {
213
    await sendCommand(WaitFor(finder, timeout: timeout));
214 215
  }

216
  /// Waits until [finder] can no longer locate the target.
217
  Future<void> waitForAbsent(SerializableFinder finder, { Duration timeout }) async {
218
    await sendCommand(WaitForAbsent(finder, timeout: timeout));
219 220
  }

221 222
  /// Waits until the given [waitCondition] is satisfied.
  Future<void> waitForCondition(SerializableWaitCondition waitCondition, {Duration timeout}) async {
223
    await sendCommand(WaitForCondition(waitCondition, timeout: timeout));
224 225
  }

226 227 228 229
  /// 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].
230
  Future<void> waitUntilNoTransientCallbacks({ Duration timeout }) async {
231
    await sendCommand(WaitForCondition(const NoTransientCallbacks(), timeout: timeout));
232 233
  }

234 235 236 237
  /// Waits until the next [Window.onReportTimings] is called.
  ///
  /// Use this method to wait for the first frame to be rasterized during the
  /// app launch.
238 239
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
240
  Future<void> waitUntilFirstFrameRasterized() async {
241
    await sendCommand(const WaitForCondition(FirstFrameRasterized()));
242 243
  }

244 245
  Future<DriverOffset> _getOffset(SerializableFinder finder, OffsetType type, { Duration timeout }) async {
    final GetOffset command = GetOffset(finder, type, timeout: timeout);
246
    final GetOffsetResult result = GetOffsetResult.fromJson(await sendCommand(command));
247 248 249 250
    return DriverOffset(result.dx, result.dy);
  }

  /// Returns the point at the top left of the widget identified by `finder`.
251 252 253
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
254 255 256 257 258
  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`.
259 260 261
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
262 263 264 265 266
  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`.
267 268 269
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
270 271 272 273 274
  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`.
275 276 277
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
278 279 280 281 282
  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`.
283 284 285
  ///
  /// The offset is expressed in logical pixels and can be translated to
  /// device pixels via [Window.devicePixelRatio].
286 287 288 289
  Future<DriverOffset> getCenter(SerializableFinder finder, { Duration timeout }) async {
    return _getOffset(finder, OffsetType.center, timeout: timeout);
  }

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
  /// 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 {
317
    return sendCommand(GetDiagnosticsTree(
318 319 320 321 322 323 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
      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 {
350
    return sendCommand(GetDiagnosticsTree(
351
      finder,
352
      DiagnosticsType.widget,
353 354 355 356 357 358
      subtreeDepth: subtreeDepth,
      includeProperties: includeProperties,
      timeout: timeout,
    ));
  }

359 360 361 362 363 364 365 366 367
  /// 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.
  ///
368
  /// [duration] specifies the length of the action.
369 370 371
  ///
  /// The move events are generated at a given [frequency] in Hz (or events per
  /// second). It defaults to 60Hz.
372
  Future<void> scroll(SerializableFinder finder, double dx, double dy, Duration duration, { int frequency = 60, Duration timeout }) async {
373
    await sendCommand(Scroll(finder, dx, dy, duration, frequency, timeout: timeout));
374 375
  }

376 377
  /// Scrolls the Scrollable ancestor of the widget located by [finder]
  /// until the widget is completely visible.
378 379 380 381 382
  ///
  /// 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.
383
  Future<void> scrollIntoView(SerializableFinder finder, { double alignment = 0.0, Duration timeout }) async {
384
    await sendCommand(ScrollIntoView(finder, alignment: alignment, timeout: timeout));
385 386
  }

387 388 389 390 391
  /// 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].
392
  /// Typically `find.byType('ListView')` or `find.byType('CustomScrollView')`.
393
  ///
394
  /// At least one of [dxScroll] and [dyScroll] must be non-zero.
395 396 397 398
  ///
  /// 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
399
  /// if [item] is above, then specify a positive value for [dyScroll].
400
  ///
401
  /// If [item] is to the right of the currently visible items, then
402 403
  /// specify a negative value for [dxScroll] that's a small enough increment to
  /// expose [item] without potentially scrolling it up and completely out of
404
  /// view. Similarly if [item] is to the left, then specify a positive value
405 406 407
  /// for [dyScroll].
  ///
  /// The [timeout] value should be long enough to accommodate as many scrolls
408
  /// as needed to bring an item into view. The default is to not time out.
409 410 411
  Future<void> scrollUntilVisible(
    SerializableFinder scrollable,
    SerializableFinder item, {
412 413 414
    double alignment = 0.0,
    double dxScroll = 0.0,
    double dyScroll = 0.0,
415
    Duration timeout,
416 417 418 419 420 421 422 423
  }) async {
    assert(scrollable != null);
    assert(item != null);
    assert(alignment != null);
    assert(dxScroll != null);
    assert(dyScroll != null);
    assert(dxScroll != 0.0 || dyScroll != 0.0);

424 425 426 427
    // 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.
428
    bool isVisible = false;
429 430
    waitFor(item, timeout: timeout).then<void>((_) { isVisible = true; });
    await Future<void>.delayed(const Duration(milliseconds: 500));
431 432
    while (!isVisible) {
      await scroll(scrollable, dxScroll, dyScroll, const Duration(milliseconds: 100));
433
      await Future<void>.delayed(const Duration(milliseconds: 500));
434 435 436 437 438
    }

    return scrollIntoView(item, alignment: alignment);
  }

439
  /// Returns the text in the `Text` widget located by [finder].
440
  Future<String> getText(SerializableFinder finder, { Duration timeout }) async {
441
    return GetTextResult.fromJson(await sendCommand(GetText(finder, timeout: timeout))).text;
442 443
  }

444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
  /// 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.
  ///
461 462 463
  /// For this method to work, text emulation must be enabled (see
  /// [setTextEntryEmulation]). Text emulation is enabled by default.
  ///
464 465 466 467
  /// Example:
  ///
  /// ```dart
  /// test('enters text in a text field', () async {
468 469 470 471 472 473
  ///   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
474 475
  /// });
  /// ```
476
  Future<void> enterText(String text, { Duration timeout }) async {
477
    await sendCommand(EnterText(text, timeout: timeout));
478 479
  }

480 481
  /// Configures text entry emulation.
  ///
482
  /// If `enabled` is true, enables text entry emulation via [enterText]. If
483 484 485 486 487 488
  /// `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.
489
  ///
490 491
  /// When enabled, the operating system's configured keyboard will not be
  /// invoked when the widget is focused, as the [SystemChannels.textInput]
492
  /// channel will be mocked out.
493
  Future<void> setTextEntryEmulation({ @required bool enabled, Duration timeout }) async {
494
    assert(enabled != null);
495
    await sendCommand(SetTextEntryEmulation(enabled, timeout: timeout));
496 497
  }

498 499
  /// Sends a string and returns a string.
  ///
500 501 502 503
  /// 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.
504
  Future<String> requestData(String message, { Duration timeout }) async {
505
    return RequestDataResult.fromJson(await sendCommand(RequestData(message, timeout: timeout))).message;
506 507
  }

508 509
  /// Turns semantics on or off in the Flutter app under test.
  ///
510
  /// Returns true when the call actually changed the state from on to off or
511
  /// vice versa.
512
  Future<bool> setSemantics(bool enabled, { Duration timeout }) async {
513
    final SetSemanticsResult result = SetSemanticsResult.fromJson(await sendCommand(SetSemantics(enabled, timeout: timeout)));
514 515 516
    return result.changedState;
  }

517 518
  /// Retrieves the semantics node id for the object returned by `finder`, or
  /// the nearest ancestor with a semantics node.
519
  ///
520 521
  /// Throws an error if `finder` returns multiple elements or a semantics
  /// node is not found.
522
  ///
523 524
  /// Semantics must be enabled to use this method, either using a platform
  /// specific shell command or [setSemantics].
525
  Future<int> getSemanticsId(SerializableFinder finder, { Duration timeout }) async {
526
    final Map<String, dynamic> jsonResponse = await sendCommand(GetSemanticsId(finder, timeout: timeout));
527 528 529 530
    final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson(jsonResponse);
    return result.id;
  }

531 532 533
  /// Take a screenshot.
  ///
  /// The image will be returned as a PNG.
534 535 536
  ///
  ///  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
537 538 539 540
  ///        the raster thread (formerly known as the GPU thread). The issue is
  ///        that driver API synchronizes with the framework based on transient
  ///        callbacks, which are out of sync with the raster thread.
  ///        Here's the timeline of events in ASCII art:
541 542 543 544 545
  ///
  ///        -------------------------------------------------------------------
  ///        Without this delay:
  ///        -------------------------------------------------------------------
  ///        UI    : <-- build -->
546
  ///        Raster:               <-- rasterize -->
547 548 549 550 551 552 553 554
  ///        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
555
  ///        be, the screenshot is taken before the raster thread is done
556 557 558 559 560 561 562
  ///        rasterizing the frame, so the screenshot of the previous frame is
  ///        taken, which is wrong.
  ///
  ///        -------------------------------------------------------------------
  ///        With this delay, if we're lucky:
  ///        -------------------------------------------------------------------
  ///        UI    : <-- build -->
563
  ///        Raster:               <-- rasterize -->
564 565 566
  ///        Gap   :              |    2 seconds or more   |
  ///        Driver:                                        <-- screenshot -->
  ///
567
  ///        The two-second gap should be long enough for the raster thread to
568 569 570 571 572 573 574
  ///        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 -->
575
  ///        Raster:               <-- rasterize randomly slow today -->
576 577 578 579 580 581
  ///        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.
582
  Future<List<int>> screenshot() async {
583 584
    throw UnimplementedError();
  }
585

586 587
  /// Returns the Flags set in the Dart VM as JSON.
  ///
588 589
  /// See the complete documentation for [the `getFlagList` Dart VM service
  /// method][getFlagList].
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
  ///
  /// 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
605 606
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
607
  Future<List<Map<String, dynamic>>> getVmFlags() async {
608 609
    throw UnimplementedError();
  }
610
  /// Starts recording performance traces.
611 612 613 614
  ///
  /// 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.
615 616
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
617
  Future<void> startTracing({
618
    List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
619
    Duration timeout = kUnusuallyLongTimeout,
620
  }) async {
621 622
    throw UnimplementedError();
  }
623

624
  /// Stops recording performance traces and downloads the timeline.
625 626 627 628
  ///
  /// 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.
629 630
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
631
  Future<Timeline> stopTracingAndDownloadTimeline({
632
    Duration timeout = kUnusuallyLongTimeout,
633
  }) async {
634 635
    throw UnimplementedError();
  }
636 637 638 639 640 641
  /// 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
642
  /// [stopTracingAndDownloadTimeline].
643 644 645
  ///
  /// [streams] limits the recorded timeline event streams to only the ones
  /// listed. By default, all streams are recorded.
646 647 648 649
  ///
  /// 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.
650 651 652
  ///
  /// If this is run in debug mode, a warning message will be printed to suggest
  /// running the benchmark in profile mode instead.
653 654
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
655 656
  Future<Timeline> traceAction(
    Future<dynamic> action(), {
657
    List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
658
    bool retainPriorEvents = false,
659
  }) async {
660 661
    throw UnimplementedError();
  }
662

663
  /// Clears all timeline events recorded up until now.
664 665 666 667
  ///
  /// 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.
668 669
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
670
  Future<void> clearTimeline({
671
    Duration timeout = kUnusuallyLongTimeout,
672
  }) async {
673 674
    throw UnimplementedError();
  }
675 676 677 678 679 680 681 682 683 684 685 686 687
  /// [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.
  ///
688
  /// With frame sync disabled, it's the responsibility of the test author to
689 690
  /// ensure that no action is performed while the app is undergoing a
  /// transition to avoid flakiness.
691
  Future<T> runUnsynchronized<T>(Future<T> action(), { Duration timeout }) async {
692
    await sendCommand(SetFrameSync(false, timeout: timeout));
693
    T result;
694 695 696
    try {
      result = await action();
    } finally {
697
      await sendCommand(SetFrameSync(true, timeout: timeout));
698 699 700 701
    }
    return result;
  }

702
  /// Force a garbage collection run in the VM.
703 704
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
705
  Future<void> forceGC() async {
706 707
    throw UnimplementedError();
  }
708

709 710 711
  /// Closes the underlying connection to the VM service.
  ///
  /// Returns a [Future] that fires once the connection has been closed.
712
  Future<void> close() async {
713 714
    throw UnimplementedError();
  }
yjbanov's avatar
yjbanov committed
715
}
716 717 718 719 720

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

721
  /// Finds [Text] and [EditableText] widgets containing string equal to [text].
722
  SerializableFinder text(String text) => ByText(text);
723

724
  /// Finds widgets by [key]. Only [String] and [int] values can be used.
725
  SerializableFinder byValueKey(dynamic key) => ByValueKey(key);
726 727

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

730 731 732
  /// Finds widgets with the given semantics [label].
  SerializableFinder bySemanticsLabel(Pattern label) => BySemanticsLabel(label);

733
  /// Finds widgets whose class name matches the given string.
734
  SerializableFinder byType(String type) => ByType(type);
735 736

  /// Finds the back button on a Material or Cupertino page's scaffold.
737
  SerializableFinder pageBack() => const PageBack();
738 739 740 741 742 743

  /// 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.
744 745 746
  ///
  /// If `firstMatchOnly` is true then only the first ancestor matching
  /// `matching` will be returned. Defaults to false.
747 748 749 750
  SerializableFinder ancestor({
    @required SerializableFinder of,
    @required SerializableFinder matching,
    bool matchRoot = false,
751 752
    bool firstMatchOnly = false,
  }) => Ancestor(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
753 754 755 756 757 758

  /// 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.
759 760 761
  ///
  /// If `firstMatchOnly` is true then only the first descendant matching
  /// `matching` will be returned. Defaults to false.
762 763 764 765
  SerializableFinder descendant({
    @required SerializableFinder of,
    @required SerializableFinder matching,
    bool matchRoot = false,
766 767
    bool firstMatchOnly = false,
  }) => Descendant(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
768
}
769 770

/// An immutable 2D floating-point offset used by Flutter Driver.
771
@immutable
772 773 774 775 776 777 778 779 780 781 782 783 784 785
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
786
  bool operator ==(Object other) {
787 788 789
    return other is DriverOffset
        && other.dx == dx
        && other.dy == dy;
790 791 792
  }

  @override
793
  int get hashCode => dx.hashCode ^ dy.hashCode;
794
}