Repository navigation
Add real WebDriver pointer actions to package:integration_test on the web - #195207
Merged
auto-submit[bot] merged 23 commits intoOct 29, 2026
Merged
auto-submit[bot] merged 23 commits into
auto-submit[bot] merged 23 commits into
Conversation
- Rewrite the integrationDriver click tests around a fake app and a fake mouse whose behavior is stated in the test, so each test reads as the clicks the app asks for and where the mouse clicked. - Fold the view-to-viewport origin lookup into the existing web/io callback split instead of separate view_utils libraries. - Explain why the first button click in the e2e test reaches neither the button nor the iframe. - Drop comments that only made sense with knowledge of earlier designs.
harryterkelsen
marked this pull request as ready for review
October 24, 2026 02:05
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces the webDriverClick API to integration_test on the web, enabling real browser pointer clicks via WebDriver. It adds the click command to WebDriverCommandType, implements the click dispatching logic in the web callback manager and integration driver, and includes comprehensive integration and unit tests. The review feedback suggests replacing deprecated usages of PlatformDispatcher.implicitView with non-deprecated alternatives in both the test driver and the core integration test package.
mdebbar
reviewed
Oct 26, 2026
…icks-as-a-first-class-package
…able testWidgets block
mdebbar
reviewed
Oct 28, 2026
Each kind of pointer action is now its own final subclass (WebDriverPointerMove, WebDriverPointerDown, WebDriverPointerUp, WebDriverPointerPause) with non-nullable fields, replacing the WebDriverPointerActionType enum and nullable fields. The WebDriverPointerAction.move/down/up/pause constructors redirect to the subclasses, so call sites are unchanged, and consumers can switch exhaustively on the action.
mdebbar
previously approved these changes
Oct 28, 2026
Drop the exhaustive switch example from the WebDriverPointerAction doc comment, along with the snippet preamble import that only it needed.
Add a direct dependency on package:meta to integration_test and annotate the sealed WebDriverPointerAction with @immutable, replacing the per-member avoid_equals_and_hash_code_on_mutable_classes ignores on its subclasses. Also make the base generative constructor private and drop redundant "See [X.new]" lines from the redirecting factory constructor docs.
This was referenced Oct 28, 2026
Drop the package:meta import and dependency so common.dart remains completely standalone and compatible with zero-dependency target environments.
mdebbar
approved these changes
Oct 28, 2026
This was referenced Oct 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a first-class way for Flutter web integration tests to drive the real browser mouse through WebDriver: clicks, hovers, drags, and arbitrary pointer action sequences with any mouse button.
WidgetTester.tap,WidgetTester.dragandbinding.handlePointerEventinject synthetic events straight into the framework. They skip the browser's own DOM hit testing and event dispatch, which is what decides whether a pointer event lands on Flutter content or on a platform view (<iframe>, map, video) underneath it. So features that depend on browser hit testing, such as pointer handling over platform views, couldn't be checked end to end. Until now the only workaround was to misusebinding.takeScreenshotas an RPC and drive the mouse from inside the host'sonScreenshotcallback.New API
On
IntegrationTestWidgetsFlutterBinding:Future<void> webDriverPointerActions(List<WebDriverPointerAction> actions, {FlutterView? view})performsactionsin order as one real browser pointer sequence and completes once the browser has dispatched every event in it.Future<void> webDriverClick(Offset position, {FlutterView? view})is a convenience wrapper that moves topositionand presses and releases the primary button.In
package:integration_test/common.dart(whichintegration_test.dartdoes not re-export, so tests import it directly):sealed class WebDriverPointerAction, created withWebDriverPointerAction.move(x, y, {Duration duration}),.down({WebDriverMouseButton button}),.up({WebDriverMouseButton button})and.pause(Duration duration). The concrete classes areWebDriverPointerMove,WebDriverPointerDown,WebDriverPointerUpandWebDriverPointerPause, withtoJson/fromJson, value equality and readabletoString.enum WebDriverMouseButton { primary, middle, secondary }, whosew3cButtonis the W3C button index (0,1,2), not a FlutterkPrimaryButton-style bit flag.WebDriverCommandType.pointerActionsandWebDriverCommand.pointerActions(actions)in the web command protocol.CallbackManager.pointerActions(List<WebDriverPointerAction> actions), which completes with anUnsupportedErrorby default.For example, a drag split across two calls so the test can assert on the intermediate state:
How it works
tester.getCenter).webDriverPointerActionsconverts them to viewport CSS pixels by adding the view host element'sgetBoundingClientRect()origin, so full-page, embedded and multi-view apps all work. The command travels over the same handshake asscreenshot.integrationDriver(inintegration_test_driver_extended.dart) sends the whole sequence as a single W3C perform-actions request for one mouse input source and replies only after WebDriver has performed it. Tests can assert right after theawaitwith no fixed sleeps, and noonScreenshotcallback is needed.pointerMoveas a singlemousemoveat the end. To make the browser see intermediate events during drags and hovers, the driver splits each timed move into straight-line steps of about 16 ms, starting from the previous move's position (remembered across calls within oneintegrationDriverrun) and capped at one step per pixel. Positions are rounded to whole CSS pixels.up.ArgumentErrorbefore anything is sent, and calling without aviewwhen there is no implicit view throwsStateError. Off the web, both methods throwUnsupportedError.WebFlutterDriver, the host fails the in-browser call and throws aStateError. If WebDriver throws (for example, coordinates out of bounds), the host releases pressed buttons, fails the in-browser call, and rethrows the original error, so the test fails at once instead of hanging.binding.shouldPropagateDevicePointerEvents. Real browser events reach the test binding as device events, so a test that asserts on Flutter widget gestures has to opt in withbinding.shouldPropagateDevicePointerEvents = true. Platform view DOM listeners get the native events either way.Tests
webdriver_pointer_actions_integration, registered inrun_web_tests.dartfor CanvasKit and--wasmin profile mode. It checks that a real click reaches a same-origin<iframe>platform view (whichtester.tapcan't do), that a click reaches a Flutter button only once device pointer events are opted in, that position conversion still works when the view's host element is offset in the page, that a timed drag produces intermediate pointer moves, that buttons stay pressed across calls for a split drag, and that moving without a button hovers.packages/integration_test/test/webdriver_command_test.dartcover action serialization, equality andfromJsonvalidation, thepointerActionscommand, theCallbackManagerdefault, input validation, and the off-webUnsupportedError.packages/integration_test/test/integration_driver_pointer_actions_test.dartcover the W3C requestintegrationDriverbuilds (rounding, timed-move subdivision across requests, pauses and buttons) and its error handling for non-web drivers, WebDriver failures and malformed action JSON.Compatibility
CallbackManager.pointerActionshas a default body, so classes thatextendCallbackManagerkeep compiling. A class thatimplementsit would need to addpointerActions. The binding'scallbackManagercan't be swapped out, though, so outside code shouldn't have a real implementation.common.dartis not re-exported fromintegration_test.dart, so no new names enter the scope of existing tests.Part of #179806
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.