driver.dart 33.2 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/text_input_action.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
  /// 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,

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

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

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

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

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

86 87 88 89 90
// Examples can assume:
// import 'package:flutter_driver/flutter_driver.dart';
// import 'package:test/test.dart';
// late FlutterDriver driver;

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

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

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

173
  /// Getter of appIsolate.
174
  vms.Isolate get appIsolate => throw UnimplementedError();
175

176
  /// Getter of serviceClient.
177
  vms.VmService get serviceClient => throw UnimplementedError();
178

179 180 181
  /// Getter of webDriver.
  async_io.WebDriver get webDriver => 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
  /// Returns a dump of the layer tree.
202
  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 213 214 215 216 217 218 219
  ///
  /// 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.
220
  Future<void> waitFor(SerializableFinder finder, { Duration? timeout }) async {
221
    await sendCommand(WaitFor(finder, timeout: timeout));
222 223
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

433 434 435 436
    // 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.
437
    bool isVisible = false;
438 439
    waitFor(item, timeout: timeout).then<void>((_) { isVisible = true; });
    await Future<void>.delayed(const Duration(milliseconds: 500));
440 441
    while (!isVisible) {
      await scroll(scrollable, dxScroll, dyScroll, const Duration(milliseconds: 100));
442
      await Future<void>.delayed(const Duration(milliseconds: 500));
443 444 445 446 447
    }

    return scrollIntoView(item, alignment: alignment);
  }

448
  /// Returns the text in the `Text` widget located by [finder].
449
  Future<String> getText(SerializableFinder finder, { Duration? timeout }) async {
450
    return GetTextResult.fromJson(await sendCommand(GetText(finder, timeout: timeout))).text;
451 452
  }

453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
  /// 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.
  ///
470 471 472
  /// For this method to work, text emulation must be enabled (see
  /// [setTextEntryEmulation]). Text emulation is enabled by default.
  ///
473 474 475 476
  /// Example:
  ///
  /// ```dart
  /// test('enters text in a text field', () async {
477
  ///   final SerializableFinder textField = find.byValueKey('enter-text-field');
478 479 480 481 482
  ///   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
483 484
  /// });
  /// ```
485
  Future<void> enterText(String text, { Duration? timeout }) async {
486
    await sendCommand(EnterText(text, timeout: timeout));
487 488
  }

489 490
  /// Configures text entry emulation.
  ///
491
  /// If `enabled` is true, enables text entry emulation via [enterText]. If
492 493 494 495 496 497
  /// `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.
498
  ///
499 500
  /// When enabled, the operating system's configured keyboard will not be
  /// invoked when the widget is focused, as the [SystemChannels.textInput]
501
  /// channel will be mocked out.
502
  Future<void> setTextEntryEmulation({ required bool enabled, Duration? timeout }) async {
503
    await sendCommand(SetTextEntryEmulation(enabled, timeout: timeout));
504 505
  }

506 507 508 509 510 511 512 513 514 515 516 517 518
  /// Simulate the user posting a text input action.
  ///
  /// The available action types can be found in [TextInputAction]. The [sendTextInputAction]
  /// does not check whether the [TextInputAction] performed is acceptable
  /// based on the client arguments of the text input.
  ///
  /// This can be called even if the [TestTextInput] has not been [TestTextInput.register]ed.
  ///
  /// Example:
  /// {@tool snippet}
  ///
  /// ```dart
  /// test('submit text in a text field', () async {
519
  ///   final SerializableFinder textField = find.byValueKey('enter-text-field');
520 521 522 523 524 525 526 527 528 529 530 531 532
  ///   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.sendTextInputAction(TextInputAction.done);  // submit text
  /// });
  /// ```
  /// {@end-tool}
  ///
  Future<void> sendTextInputAction(TextInputAction action,
      {Duration? timeout}) async {
    await sendCommand(SendTextInputAction(action, timeout: timeout));
  }

533 534
  /// Sends a string and returns a string.
  ///
535 536 537 538
  /// 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.
539
  Future<String> requestData(String? message, { Duration? timeout }) async {
540
    return RequestDataResult.fromJson(await sendCommand(RequestData(message, timeout: timeout))).message;
541 542
  }

543 544
  /// Turns semantics on or off in the Flutter app under test.
  ///
545
  /// Returns true when the call actually changed the state from on to off or
546
  /// vice versa.
547 548 549 550 551 552 553
  ///
  /// 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.
554
  Future<bool> setSemantics(bool enabled, { Duration? timeout }) async {
555
    final SetSemanticsResult result = SetSemanticsResult.fromJson(await sendCommand(SetSemantics(enabled, timeout: timeout)));
556 557 558
    return result.changedState;
  }

559 560
  /// Retrieves the semantics node id for the object returned by `finder`, or
  /// the nearest ancestor with a semantics node.
561
  ///
562 563
  /// Throws an error if `finder` returns multiple elements or a semantics
  /// node is not found.
564
  ///
565 566
  /// Semantics must be enabled to use this method, either using a platform
  /// specific shell command or [setSemantics].
567
  Future<int> getSemanticsId(SerializableFinder finder, { Duration? timeout }) async {
568
    final Map<String, dynamic> jsonResponse = await sendCommand(GetSemanticsId(finder, timeout: timeout));
569 570 571 572
    final GetSemanticsIdResult result = GetSemanticsIdResult.fromJson(jsonResponse);
    return result.id;
  }

573 574 575
  /// Take a screenshot.
  ///
  /// The image will be returned as a PNG.
576
  ///
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
  /// **Warning:** This is not reliable.
  ///
  /// There is a two-second artificial delay before screenshotting. The delay
  /// here is to deal with a race between the driver script and the raster
  /// thread (formerly known as the GPU thread). The issue is that the 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:
  ///
  ///     ---------------------------------------------------------------
  ///     Without this delay:
  ///     ---------------------------------------------------------------
  ///     UI    : <-- build -->
  ///     Raster:               <-- 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 communication
  /// channel between the driver process and the application. If this gap is too
  /// short, which it typically will be, the screenshot is taken before the
  /// raster thread is done rasterizing the frame, so the screenshot of the
  /// previous frame is taken, which is not what is intended.
  ///
  ///     ---------------------------------------------------------------
  ///     With this delay, if we're lucky:
  ///     ---------------------------------------------------------------
  ///     UI    : <-- build -->
  ///     Raster:               <-- rasterize -->
  ///     Gap   :              |    2 seconds or more   |
  ///     Driver:                                        <-- screenshot -->
  ///
  /// The two-second gap should be long enough for the raster 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 -->
  ///     Raster:               <-- 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.
626
  Future<List<int>> screenshot() async {
627 628
    throw UnimplementedError();
  }
629

630 631
  /// Returns the Flags set in the Dart VM as JSON.
  ///
632 633
  /// See the complete documentation for [the `getFlagList` Dart VM service
  /// method][getFlagList].
634 635 636 637 638 639 640 641 642 643 644 645 646 647
  ///
  /// 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"
  ///       },
  ///       ...
  ///     ]
  ///
648
  /// [getFlagList]: https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#getflaglist
649 650
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
651
  Future<List<Map<String, dynamic>>> getVmFlags() async {
652 653
    throw UnimplementedError();
  }
654
  /// Starts recording performance traces.
655 656 657 658
  ///
  /// 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.
659 660
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
661
  Future<void> startTracing({
662
    List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
663
    Duration timeout = kUnusuallyLongTimeout,
664
  }) async {
665 666
    throw UnimplementedError();
  }
667

668
  /// Stops recording performance traces and downloads the timeline.
669 670 671 672
  ///
  /// 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.
673 674
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
675
  Future<Timeline> stopTracingAndDownloadTimeline({
676
    Duration timeout = kUnusuallyLongTimeout,
677
  }) async {
678 679
    throw UnimplementedError();
  }
680 681 682 683 684 685
  /// 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
686
  /// [stopTracingAndDownloadTimeline].
687 688 689
  ///
  /// [streams] limits the recorded timeline event streams to only the ones
  /// listed. By default, all streams are recorded.
690 691 692 693
  ///
  /// 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.
694 695 696
  ///
  /// If this is run in debug mode, a warning message will be printed to suggest
  /// running the benchmark in profile mode instead.
697 698
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
699
  Future<Timeline> traceAction(
700
    Future<dynamic> Function() action, {
701
    List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
702
    bool retainPriorEvents = false,
703
  }) async {
704 705
    throw UnimplementedError();
  }
706

707
  /// Clears all timeline events recorded up until now.
708 709 710 711
  ///
  /// 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.
712 713
  ///
  /// For [WebFlutterDriver], this is only supported for Chrome.
714
  Future<void> clearTimeline({
715
    Duration timeout = kUnusuallyLongTimeout,
716
  }) async {
717 718
    throw UnimplementedError();
  }
719 720 721 722 723 724 725 726 727 728 729 730 731
  /// [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.
  ///
732
  /// With frame sync disabled, it's the responsibility of the test author to
733 734
  /// ensure that no action is performed while the app is undergoing a
  /// transition to avoid flakiness.
735
  Future<T> runUnsynchronized<T>(Future<T> Function() action, { Duration? timeout }) async {
736
    await sendCommand(SetFrameSync(false, timeout: timeout));
737
    T result;
738 739 740
    try {
      result = await action();
    } finally {
741
      await sendCommand(SetFrameSync(true, timeout: timeout));
742 743 744 745
    }
    return result;
  }

746
  /// Force a garbage collection run in the VM.
747 748
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
749
  Future<void> forceGC() async {
750 751
    throw UnimplementedError();
  }
752

753 754 755
  /// Closes the underlying connection to the VM service.
  ///
  /// Returns a [Future] that fires once the connection has been closed.
756
  Future<void> close() async {
757 758
    throw UnimplementedError();
  }
yjbanov's avatar
yjbanov committed
759
}
760 761 762 763 764

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

Janice Collins's avatar
Janice Collins committed
765 766
  /// Finds [widgets.Text] and [widgets.EditableText] widgets containing string
  /// equal to [text].
767
  SerializableFinder text(String text) => ByText(text);
768

769
  /// Finds widgets by [key]. Only [String] and [int] values can be used.
770
  SerializableFinder byValueKey(dynamic key) => ByValueKey(key);
771 772

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

775 776 777
  /// Finds widgets with the given semantics [label].
  SerializableFinder bySemanticsLabel(Pattern label) => BySemanticsLabel(label);

778
  /// Finds widgets whose class name matches the given string.
779
  SerializableFinder byType(String type) => ByType(type);
780 781

  /// Finds the back button on a Material or Cupertino page's scaffold.
782
  SerializableFinder pageBack() => const PageBack();
783 784 785 786 787 788

  /// 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.
789 790 791
  ///
  /// If `firstMatchOnly` is true then only the first ancestor matching
  /// `matching` will be returned. Defaults to false.
792
  SerializableFinder ancestor({
793 794
    required SerializableFinder of,
    required SerializableFinder matching,
795
    bool matchRoot = false,
796 797
    bool firstMatchOnly = false,
  }) => Ancestor(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
798 799 800 801 802 803

  /// 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.
804 805 806
  ///
  /// If `firstMatchOnly` is true then only the first descendant matching
  /// `matching` will be returned. Defaults to false.
807
  SerializableFinder descendant({
808 809
    required SerializableFinder of,
    required SerializableFinder matching,
810
    bool matchRoot = false,
811 812
    bool firstMatchOnly = false,
  }) => Descendant(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
813
}
814 815

/// An immutable 2D floating-point offset used by Flutter Driver.
816
@immutable
817 818 819 820 821 822 823 824 825 826 827
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
828
  String toString() => '$runtimeType($dx, $dy)'; // ignore: no_runtimetype_tostring, can't access package:flutter here to use objectRuntimeType
829 830

  @override
831
  bool operator ==(Object other) {
832 833 834
    return other is DriverOffset
        && other.dx == dx
        && other.dy == dy;
835 836 837
  }

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