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
/// Drives a Flutter Application running in another process.
87 88
abstract class FlutterDriver {
  /// Default constructor.
89
  @visibleForTesting
90 91 92
  FlutterDriver();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
  /// 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 {
  ///   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.sendTextInputAction(TextInputAction.done);  // submit text
  /// });
  /// ```
  /// {@end-tool}
  ///
  Future<void> sendTextInputAction(TextInputAction action,
      {Duration? timeout}) async {
    assert(action != null);
    await sendCommand(SendTextInputAction(action, timeout: timeout));
  }

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

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

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

584 585 586
  /// Take a screenshot.
  ///
  /// The image will be returned as a PNG.
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 635 636
  /// **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.
637
  Future<List<int>> screenshot() async {
638 639
    throw UnimplementedError();
  }
640

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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