driver.dart 33.4 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 182
  /// Getter of webDriver.
  async_io.WebDriver get webDriver => throw UnimplementedError();

  /// Enables accessibility feature.
183 184 185 186
  @Deprecated(
    'Call setSemantics(true) instead. '
    'This feature was deprecated after v2.3.0-12.1.pre.'
  )
187
  Future<void> enableAccessibility() async {
188
    await setSemantics(true);
189 190
  }

191 192
  /// Sends [command] to the Flutter Driver extensions.
  /// This must be implemented by subclass.
193
  ///
194 195 196 197
  /// See also:
  ///
  ///  * [VMServiceFlutterDriver], which uses vmservice to implement.
  ///  * [WebFlutterDriver], which uses webdriver to implement.
198
  Future<Map<String, dynamic>> sendCommand(Command command) async => throw UnimplementedError();
199

200
  /// Checks the status of the Flutter Driver extension.
201
  Future<Health> checkHealth({ Duration? timeout }) async {
202
    return Health.fromJson(await sendCommand(GetHealth(timeout: timeout)));
203 204
  }

205
  /// Returns a dump of the render tree.
206
  Future<RenderTree> getRenderTree({ Duration? timeout }) async {
207
    return RenderTree.fromJson(await sendCommand(GetRenderTree(timeout: timeout)));
208 209
  }

210
  /// Returns a dump of the layer tree.
211
  Future<LayerTree> getLayerTree({ Duration? timeout }) async {
212
    return LayerTree.fromJson(await sendCommand(GetLayerTree(timeout: timeout)));
213 214
  }

215
  /// Taps at the center of the widget located by [finder].
216
  Future<void> tap(SerializableFinder finder, { Duration? timeout }) async {
217
    await sendCommand(Tap(finder, timeout: timeout));
218
  }
219

220
  /// Waits until [finder] locates the target.
221 222 223 224 225 226 227 228
  ///
  /// 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.
229
  Future<void> waitFor(SerializableFinder finder, { Duration? timeout }) async {
230
    await sendCommand(WaitFor(finder, timeout: timeout));
231 232
  }

233
  /// Waits until [finder] can no longer locate the target.
234
  Future<void> waitForAbsent(SerializableFinder finder, { Duration? timeout }) async {
235
    await sendCommand(WaitForAbsent(finder, timeout: timeout));
236 237
  }

238 239 240 241 242
  /// Waits until [finder] is tappable.
  Future<void> waitForTappable(SerializableFinder finder, { Duration? timeout }) async {
    await sendCommand(WaitForTappable(finder, timeout: timeout));
  }

243
  /// Waits until the given [waitCondition] is satisfied.
244
  Future<void> waitForCondition(SerializableWaitCondition waitCondition, {Duration? timeout}) async {
245
    await sendCommand(WaitForCondition(waitCondition, timeout: timeout));
246 247
  }

248 249 250 251
  /// 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].
252
  Future<void> waitUntilNoTransientCallbacks({ Duration? timeout }) async {
253
    await sendCommand(WaitForCondition(const NoTransientCallbacks(), timeout: timeout));
254 255
  }

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

267
  Future<DriverOffset> _getOffset(SerializableFinder finder, OffsetType type, { Duration? timeout }) async {
268
    final GetOffset command = GetOffset(finder, type, timeout: timeout);
269
    final GetOffsetResult result = GetOffsetResult.fromJson(await sendCommand(command));
270 271 272 273
    return DriverOffset(result.dx, result.dy);
  }

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

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

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

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

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

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

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

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

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

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

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

    return scrollIntoView(item, alignment: alignment);
  }

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

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

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

515 516 517 518 519 520 521 522 523 524 525 526 527
  /// 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 {
528
  ///   final SerializableFinder textField = find.byValueKey('enter-text-field');
529 530 531 532 533 534 535 536 537 538 539 540 541
  ///   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));
  }

542 543
  /// Sends a string and returns a string.
  ///
544 545 546 547
  /// 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.
548
  Future<String> requestData(String? message, { Duration? timeout }) async {
549
    return RequestDataResult.fromJson(await sendCommand(RequestData(message, timeout: timeout))).message;
550 551
  }

552 553
  /// Turns semantics on or off in the Flutter app under test.
  ///
554
  /// Returns true when the call actually changed the state from on to off or
555
  /// vice versa.
556 557 558 559 560 561 562
  ///
  /// 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.
563
  Future<bool> setSemantics(bool enabled, { Duration? timeout }) async {
564
    final SetSemanticsResult result = SetSemanticsResult.fromJson(await sendCommand(SetSemantics(enabled, timeout: timeout)));
565 566 567
    return result.changedState;
  }

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

582 583 584
  /// Take a screenshot.
  ///
  /// The image will be returned as a PNG.
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 626 627 628 629 630 631 632 633 634
  /// **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.
635
  Future<List<int>> screenshot() async {
636 637
    throw UnimplementedError();
  }
638

639 640
  /// Returns the Flags set in the Dart VM as JSON.
  ///
641 642
  /// See the complete documentation for [the `getFlagList` Dart VM service
  /// method][getFlagList].
643 644 645 646 647 648 649 650 651 652 653 654 655 656
  ///
  /// 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"
  ///       },
  ///       ...
  ///     ]
  ///
657
  /// [getFlagList]: https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#getflaglist
658 659
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
660
  Future<List<Map<String, dynamic>>> getVmFlags() async {
661 662
    throw UnimplementedError();
  }
663
  /// Starts recording performance traces.
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> startTracing({
671
    List<TimelineStream> streams = const <TimelineStream>[TimelineStream.all],
672
    Duration timeout = kUnusuallyLongTimeout,
673
  }) async {
674 675
    throw UnimplementedError();
  }
676

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

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

755
  /// Force a garbage collection run in the VM.
756 757
  ///
  /// Throws [UnimplementedError] on [WebFlutterDriver] instances.
758
  Future<void> forceGC() async {
759 760
    throw UnimplementedError();
  }
761

762 763 764
  /// Closes the underlying connection to the VM service.
  ///
  /// Returns a [Future] that fires once the connection has been closed.
765
  Future<void> close() async {
766 767
    throw UnimplementedError();
  }
yjbanov's avatar
yjbanov committed
768
}
769 770 771 772 773

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

Janice Collins's avatar
Janice Collins committed
774 775
  /// Finds [widgets.Text] and [widgets.EditableText] widgets containing string
  /// equal to [text].
776
  SerializableFinder text(String text) => ByText(text);
777

778
  /// Finds widgets by [key]. Only [String] and [int] values can be used.
779
  SerializableFinder byValueKey(dynamic key) => ByValueKey(key);
780 781

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

784 785 786
  /// Finds widgets with the given semantics [label].
  SerializableFinder bySemanticsLabel(Pattern label) => BySemanticsLabel(label);

787
  /// Finds widgets whose class name matches the given string.
788
  SerializableFinder byType(String type) => ByType(type);
789 790

  /// Finds the back button on a Material or Cupertino page's scaffold.
791
  SerializableFinder pageBack() => const PageBack();
792 793 794 795 796 797

  /// 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.
798 799 800
  ///
  /// If `firstMatchOnly` is true then only the first ancestor matching
  /// `matching` will be returned. Defaults to false.
801
  SerializableFinder ancestor({
802 803
    required SerializableFinder of,
    required SerializableFinder matching,
804
    bool matchRoot = false,
805 806
    bool firstMatchOnly = false,
  }) => Ancestor(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
807 808 809 810 811 812

  /// 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.
813 814 815
  ///
  /// If `firstMatchOnly` is true then only the first descendant matching
  /// `matching` will be returned. Defaults to false.
816
  SerializableFinder descendant({
817 818
    required SerializableFinder of,
    required SerializableFinder matching,
819
    bool matchRoot = false,
820 821
    bool firstMatchOnly = false,
  }) => Descendant(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
822
}
823 824

/// An immutable 2D floating-point offset used by Flutter Driver.
825
@immutable
826 827 828 829 830 831 832 833 834 835 836
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
837
  String toString() => '$runtimeType($dx, $dy)'; // ignore: no_runtimetype_tostring, can't access package:flutter here to use objectRuntimeType
838 839

  @override
840
  bool operator ==(Object other) {
841 842 843
    return other is DriverOffset
        && other.dx == dx
        && other.dy == dy;
844 845 846
  }

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