driver.dart 32.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
import 'dart:io';
6

7
import 'package:meta/meta.dart';
8
import 'package:vm_service/vm_service.dart' as vms;
9
import 'package:webdriver/async_io.dart' as async_io;
10

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

export 'vmservice_driver.dart';
export 'web_driver.dart';
31

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

43 44 45
  /// The verbose version of compiler.
  compilerVerbose,

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

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

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

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

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

  /// Creates a driver that uses a connection provided by either the combination
92 93
  /// of [webConnection], or the combination of [serviceClient] and [appIsolate]
  /// for the VM.
94 95
  @visibleForTesting
  factory FlutterDriver.connectedTo({
96 97 98
    FlutterWebConnection? webConnection,
    vms.VmService? serviceClient,
    vms.Isolate? appIsolate,
99
  }) {
100 101
    if (webConnection != null) {
      return WebFlutterDriver.connectedTo(webConnection);
102
    }
103
    return VMServiceFlutterDriver.connectedTo(serviceClient!, appIsolate!);
104
  }
105

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

167
  /// Getter of appIsolate.
168
  vms.Isolate get appIsolate => throw UnimplementedError();
169

170
  /// Getter of serviceClient.
171
  vms.VmService get serviceClient => throw UnimplementedError();
172

173 174 175 176
  /// Getter of webDriver.
  async_io.WebDriver get webDriver => throw UnimplementedError();

  /// Enables accessibility feature.
177 178 179 180
  @Deprecated(
    'Call setSemantics(true) instead. '
    'This feature was deprecated after v2.3.0-12.1.pre.'
  )
181
  Future<void> enableAccessibility() async {
182
    await setSemantics(true);
183 184
  }

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

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

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

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

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

214
  /// Waits until [finder] locates the target.
215 216 217 218 219 220 221 222
  ///
  /// The [finder] will wait until there is no pending frame scheduled
  /// in the app under test before executing an action.
  ///
  /// See also:
  ///
  ///  * [FlutterDriver.runUnsynchronized], which will execute an action
  ///    with frame sync disabled even while frames are pending.
223
  Future<void> waitFor(SerializableFinder finder, { Duration? timeout }) async {
224
    await sendCommand(WaitFor(finder, timeout: timeout));
225 226
  }

227
  /// Waits until [finder] can no longer locate the target.
228
  Future<void> waitForAbsent(SerializableFinder finder, { Duration? timeout }) async {
229
    await sendCommand(WaitForAbsent(finder, timeout: timeout));
230 231
  }

232 233 234 235 236
  /// Waits until [finder] is tappable.
  Future<void> waitForTappable(SerializableFinder finder, { Duration? timeout }) async {
    await sendCommand(WaitForTappable(finder, timeout: timeout));
  }

237
  /// Waits until the given [waitCondition] is satisfied.
238
  Future<void> waitForCondition(SerializableWaitCondition waitCondition, {Duration? timeout}) async {
239
    await sendCommand(WaitForCondition(waitCondition, timeout: timeout));
240 241
  }

242 243 244 245
  /// 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].
246
  Future<void> waitUntilNoTransientCallbacks({ Duration? timeout }) async {
247
    await sendCommand(WaitForCondition(const NoTransientCallbacks(), timeout: timeout));
248 249
  }

250 251
  /// Waits until the next [dart:ui.PlatformDispatcher.onReportTimings] is
  /// called.
252 253 254
  ///
  /// Use this method to wait for the first frame to be rasterized during the
  /// app launch.
255 256
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
257
  Future<void> waitUntilFirstFrameRasterized() async {
258
    await sendCommand(const WaitForCondition(FirstFrameRasterized()));
259 260
  }

261
  Future<DriverOffset> _getOffset(SerializableFinder finder, OffsetType type, { Duration? timeout }) async {
262
    final GetOffset command = GetOffset(finder, type, timeout: timeout);
263
    final GetOffsetResult result = GetOffsetResult.fromJson(await sendCommand(command));
264 265 266 267
    return DriverOffset(result.dx, result.dy);
  }

  /// Returns the point at the top left of the widget identified by `finder`.
268 269
  ///
  /// The offset is expressed in logical pixels and can be translated to
270
  /// device pixels via [dart:ui.FlutterView.devicePixelRatio].
271
  Future<DriverOffset> getTopLeft(SerializableFinder finder, { Duration? timeout }) async {
272 273 274 275
    return _getOffset(finder, OffsetType.topLeft, timeout: timeout);
  }

  /// Returns the point at the top right of the widget identified by `finder`.
276 277
  ///
  /// The offset is expressed in logical pixels and can be translated to
278
  /// device pixels via [dart:ui.FlutterView.devicePixelRatio].
279
  Future<DriverOffset> getTopRight(SerializableFinder finder, { Duration? timeout }) async {
280 281 282 283
    return _getOffset(finder, OffsetType.topRight, timeout: timeout);
  }

  /// Returns the point at the bottom left of the widget identified by `finder`.
284 285
  ///
  /// The offset is expressed in logical pixels and can be translated to
286
  /// device pixels via [dart:ui.FlutterView.devicePixelRatio].
287
  Future<DriverOffset> getBottomLeft(SerializableFinder finder, { Duration? timeout }) async {
288 289 290 291
    return _getOffset(finder, OffsetType.bottomLeft, timeout: timeout);
  }

  /// Returns the point at the bottom right of the widget identified by `finder`.
292 293
  ///
  /// The offset is expressed in logical pixels and can be translated to
294
  /// device pixels via [dart:ui.FlutterView.devicePixelRatio].
295
  Future<DriverOffset> getBottomRight(SerializableFinder finder, { Duration? timeout }) async {
296 297 298 299
    return _getOffset(finder, OffsetType.bottomRight, timeout: timeout);
  }

  /// Returns the point at the center of the widget identified by `finder`.
300 301
  ///
  /// The offset is expressed in logical pixels and can be translated to
302
  /// device pixels via [dart:ui.FlutterView.devicePixelRatio].
303
  Future<DriverOffset> getCenter(SerializableFinder finder, { Duration? timeout }) async {
304 305 306
    return _getOffset(finder, OffsetType.center, timeout: timeout);
  }

307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
  /// 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].
328
  Future<Map<String, Object?>> getRenderObjectDiagnostics(
329 330 331
      SerializableFinder finder, {
      int subtreeDepth = 0,
      bool includeProperties = true,
332
      Duration? timeout,
333
  }) async {
334
    return sendCommand(GetDiagnosticsTree(
335 336 337 338 339
      finder,
      DiagnosticsType.renderObject,
      subtreeDepth: subtreeDepth,
      includeProperties: includeProperties,
      timeout: timeout,
340
    ));
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
  }

  /// 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].
361
  Future<Map<String, Object?>> getWidgetDiagnostics(
362 363 364
    SerializableFinder finder, {
    int subtreeDepth = 0,
    bool includeProperties = true,
365
    Duration? timeout,
366
  }) async {
367
    return sendCommand(GetDiagnosticsTree(
368
      finder,
369
      DiagnosticsType.widget,
370 371 372
      subtreeDepth: subtreeDepth,
      includeProperties: includeProperties,
      timeout: timeout,
373
    ));
374 375
  }

376 377 378 379 380 381 382 383 384
  /// 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.
  ///
385
  /// [duration] specifies the length of the action.
386 387 388
  ///
  /// The move events are generated at a given [frequency] in Hz (or events per
  /// second). It defaults to 60Hz.
389
  Future<void> scroll(SerializableFinder finder, double dx, double dy, Duration duration, { int frequency = 60, Duration? timeout }) async {
390
    await sendCommand(Scroll(finder, dx, dy, duration, frequency, timeout: timeout));
391 392
  }

393 394
  /// Scrolls the Scrollable ancestor of the widget located by [finder]
  /// until the widget is completely visible.
395 396 397 398 399
  ///
  /// 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.
400
  Future<void> scrollIntoView(SerializableFinder finder, { double alignment = 0.0, Duration? timeout }) async {
401
    await sendCommand(ScrollIntoView(finder, alignment: alignment, timeout: timeout));
402 403
  }

404 405 406 407 408
  /// 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].
409
  /// Typically `find.byType('ListView')` or `find.byType('CustomScrollView')`.
410
  ///
411
  /// At least one of [dxScroll] and [dyScroll] must be non-zero.
412 413 414 415
  ///
  /// 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
416
  /// if [item] is above, then specify a positive value for [dyScroll].
417
  ///
418
  /// If [item] is to the right of the currently visible items, then
419 420
  /// specify a negative value for [dxScroll] that's a small enough increment to
  /// expose [item] without potentially scrolling it up and completely out of
421
  /// view. Similarly if [item] is to the left, then specify a positive value
422 423 424
  /// for [dyScroll].
  ///
  /// The [timeout] value should be long enough to accommodate as many scrolls
425
  /// as needed to bring an item into view. The default is to not time out.
426 427 428
  Future<void> scrollUntilVisible(
    SerializableFinder scrollable,
    SerializableFinder item, {
429 430 431
    double alignment = 0.0,
    double dxScroll = 0.0,
    double dyScroll = 0.0,
432
    Duration? timeout,
433 434 435 436 437 438 439 440
  }) async {
    assert(scrollable != null);
    assert(item != null);
    assert(alignment != null);
    assert(dxScroll != null);
    assert(dyScroll != null);
    assert(dxScroll != 0.0 || dyScroll != 0.0);

441 442 443 444
    // 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.
445
    bool isVisible = false;
446 447
    waitFor(item, timeout: timeout).then<void>((_) { isVisible = true; });
    await Future<void>.delayed(const Duration(milliseconds: 500));
448 449
    while (!isVisible) {
      await scroll(scrollable, dxScroll, dyScroll, const Duration(milliseconds: 100));
450
      await Future<void>.delayed(const Duration(milliseconds: 500));
451 452 453 454 455
    }

    return scrollIntoView(item, alignment: alignment);
  }

456
  /// Returns the text in the `Text` widget located by [finder].
457
  Future<String> getText(SerializableFinder finder, { Duration? timeout }) async {
458
    return GetTextResult.fromJson(await sendCommand(GetText(finder, timeout: timeout))).text;
459 460
  }

461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
  /// 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.
  ///
478 479 480
  /// For this method to work, text emulation must be enabled (see
  /// [setTextEntryEmulation]). Text emulation is enabled by default.
  ///
481 482 483 484
  /// Example:
  ///
  /// ```dart
  /// test('enters text in a text field', () async {
485 486 487 488 489 490
  ///   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
491 492
  /// });
  /// ```
493
  Future<void> enterText(String text, { Duration? timeout }) async {
494
    await sendCommand(EnterText(text, timeout: timeout));
495 496
  }

497 498
  /// Configures text entry emulation.
  ///
499
  /// If `enabled` is true, enables text entry emulation via [enterText]. If
500 501 502 503 504 505
  /// `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.
506
  ///
507 508
  /// When enabled, the operating system's configured keyboard will not be
  /// invoked when the widget is focused, as the [SystemChannels.textInput]
509
  /// channel will be mocked out.
510
  Future<void> setTextEntryEmulation({ required bool enabled, Duration? timeout }) async {
511
    assert(enabled != null);
512
    await sendCommand(SetTextEntryEmulation(enabled, timeout: timeout));
513 514
  }

515 516
  /// Sends a string and returns a string.
  ///
517 518 519 520
  /// 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.
521
  Future<String> requestData(String? message, { Duration? timeout }) async {
522
    return RequestDataResult.fromJson(await sendCommand(RequestData(message, timeout: timeout))).message;
523 524
  }

525 526
  /// Turns semantics on or off in the Flutter app under test.
  ///
527
  /// Returns true when the call actually changed the state from on to off or
528
  /// vice versa.
529 530 531 532 533 534 535
  ///
  /// Does not enable or disable the assistive technology installed on the
  /// device. For example, this does not enable VoiceOver on iOS, TalkBack on
  /// Android, or NVDA on Windows.
  ///
  /// Enabling semantics on the web causes the engine to render ARIA-annotated
  /// HTML.
536
  Future<bool> setSemantics(bool enabled, { Duration? timeout }) async {
537
    final SetSemanticsResult result = SetSemanticsResult.fromJson(await sendCommand(SetSemantics(enabled, timeout: timeout)));
538 539 540
    return result.changedState;
  }

541 542
  /// Retrieves the semantics node id for the object returned by `finder`, or
  /// the nearest ancestor with a semantics node.
543
  ///
544 545
  /// Throws an error if `finder` returns multiple elements or a semantics
  /// node is not found.
546
  ///
547 548
  /// Semantics must be enabled to use this method, either using a platform
  /// specific shell command or [setSemantics].
549
  Future<int> getSemanticsId(SerializableFinder finder, { Duration? timeout }) async {
550
    final Map<String, dynamic> jsonResponse = await sendCommand(GetSemanticsId(finder, timeout: timeout));
551 552 553 554
    final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson(jsonResponse);
    return result.id;
  }

555 556 557
  /// Take a screenshot.
  ///
  /// The image will be returned as a PNG.
558 559 560
  ///
  ///  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
561 562 563 564
  ///        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:
565 566 567 568 569
  ///
  ///        -------------------------------------------------------------------
  ///        Without this delay:
  ///        -------------------------------------------------------------------
  ///        UI    : <-- build -->
570
  ///        Raster:               <-- rasterize -->
571 572 573 574 575 576 577 578
  ///        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
579
  ///        be, the screenshot is taken before the raster thread is done
580 581 582 583 584 585 586
  ///        rasterizing the frame, so the screenshot of the previous frame is
  ///        taken, which is wrong.
  ///
  ///        -------------------------------------------------------------------
  ///        With this delay, if we're lucky:
  ///        -------------------------------------------------------------------
  ///        UI    : <-- build -->
587
  ///        Raster:               <-- rasterize -->
588 589 590
  ///        Gap   :              |    2 seconds or more   |
  ///        Driver:                                        <-- screenshot -->
  ///
591
  ///        The two-second gap should be long enough for the raster thread to
592 593 594 595 596 597 598
  ///        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 -->
599
  ///        Raster:               <-- rasterize randomly slow today -->
600 601 602 603 604 605
  ///        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.
606
  Future<List<int>> screenshot() async {
607 608
    throw UnimplementedError();
  }
609

610 611
  /// Returns the Flags set in the Dart VM as JSON.
  ///
612 613
  /// See the complete documentation for [the `getFlagList` Dart VM service
  /// method][getFlagList].
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
  ///
  /// 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
629 630
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
631
  Future<List<Map<String, dynamic>>> getVmFlags() async {
632 633
    throw UnimplementedError();
  }
634
  /// Starts recording performance traces.
635 636 637 638
  ///
  /// 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.
639 640
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
641
  Future<void> startTracing({
642
    List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
643
    Duration timeout = kUnusuallyLongTimeout,
644
  }) async {
645 646
    throw UnimplementedError();
  }
647

648
  /// Stops recording performance traces and downloads the timeline.
649 650 651 652
  ///
  /// 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.
653 654
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
655
  Future<Timeline> stopTracingAndDownloadTimeline({
656
    Duration timeout = kUnusuallyLongTimeout,
657
  }) async {
658 659
    throw UnimplementedError();
  }
660 661 662 663 664 665
  /// 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
666
  /// [stopTracingAndDownloadTimeline].
667 668 669
  ///
  /// [streams] limits the recorded timeline event streams to only the ones
  /// listed. By default, all streams are recorded.
670 671 672 673
  ///
  /// 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.
674 675 676
  ///
  /// If this is run in debug mode, a warning message will be printed to suggest
  /// running the benchmark in profile mode instead.
677 678
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
679
  Future<Timeline> traceAction(
680
    Future<dynamic> Function() action, {
681
    List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
682
    bool retainPriorEvents = false,
683
  }) async {
684 685
    throw UnimplementedError();
  }
686

687
  /// Clears all timeline events recorded up until now.
688 689 690 691
  ///
  /// 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.
692 693
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
694
  Future<void> clearTimeline({
695
    Duration timeout = kUnusuallyLongTimeout,
696
  }) async {
697 698
    throw UnimplementedError();
  }
699 700 701 702 703 704 705 706 707 708 709 710 711
  /// [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.
  ///
712
  /// With frame sync disabled, it's the responsibility of the test author to
713 714
  /// ensure that no action is performed while the app is undergoing a
  /// transition to avoid flakiness.
715
  Future<T> runUnsynchronized<T>(Future<T> Function() action, { Duration? timeout }) async {
716
    await sendCommand(SetFrameSync(false, timeout: timeout));
717
    T result;
718 719 720
    try {
      result = await action();
    } finally {
721
      await sendCommand(SetFrameSync(true, timeout: timeout));
722 723 724 725
    }
    return result;
  }

726
  /// Force a garbage collection run in the VM.
727 728
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
729
  Future<void> forceGC() async {
730 731
    throw UnimplementedError();
  }
732

733 734 735
  /// Closes the underlying connection to the VM service.
  ///
  /// Returns a [Future] that fires once the connection has been closed.
736
  Future<void> close() async {
737 738
    throw UnimplementedError();
  }
yjbanov's avatar
yjbanov committed
739
}
740 741 742 743 744

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

Janice Collins's avatar
Janice Collins committed
745 746
  /// Finds [widgets.Text] and [widgets.EditableText] widgets containing string
  /// equal to [text].
747
  SerializableFinder text(String text) => ByText(text);
748

749
  /// Finds widgets by [key]. Only [String] and [int] values can be used.
750
  SerializableFinder byValueKey(dynamic key) => ByValueKey(key);
751 752

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

755 756 757
  /// Finds widgets with the given semantics [label].
  SerializableFinder bySemanticsLabel(Pattern label) => BySemanticsLabel(label);

758
  /// Finds widgets whose class name matches the given string.
759
  SerializableFinder byType(String type) => ByType(type);
760 761

  /// Finds the back button on a Material or Cupertino page's scaffold.
762
  SerializableFinder pageBack() => const PageBack();
763 764 765 766 767 768

  /// 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.
769 770 771
  ///
  /// If `firstMatchOnly` is true then only the first ancestor matching
  /// `matching` will be returned. Defaults to false.
772
  SerializableFinder ancestor({
773 774
    required SerializableFinder of,
    required SerializableFinder matching,
775
    bool matchRoot = false,
776 777
    bool firstMatchOnly = false,
  }) => Ancestor(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
778 779 780 781 782 783

  /// 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.
784 785 786
  ///
  /// If `firstMatchOnly` is true then only the first descendant matching
  /// `matching` will be returned. Defaults to false.
787
  SerializableFinder descendant({
788 789
    required SerializableFinder of,
    required SerializableFinder matching,
790
    bool matchRoot = false,
791 792
    bool firstMatchOnly = false,
  }) => Descendant(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
793
}
794 795

/// An immutable 2D floating-point offset used by Flutter Driver.
796
@immutable
797 798 799 800 801 802 803 804 805 806 807
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
Ian Hickson's avatar
Ian Hickson committed
808
  String toString() => '$runtimeType($dx, $dy)'; // ignore: no_runtimetype_tostring, can't access package:flutter here to use objectRuntimeType
809 810

  @override
811
  bool operator ==(Object other) {
812 813 814
    return other is DriverOffset
        && other.dx == dx
        && other.dy == dy;
815 816 817
  }

  @override
818
  int get hashCode => Object.hash(dx, dy);
819
}