Skip to content

fix(android): include the from point in the swipe path - #42349

Open
Lazizbek Ergashev (lazerg) wants to merge 5 commits into
microsoft:mainfrom
lazerg:fix-42348
Open

fix(android): include the from point in the swipe path#42349
Lazizbek Ergashev (lazerg) wants to merge 5 commits into
microsoft:mainfrom
lazerg:fix-42348

Conversation

@lazerg

@lazerg Lazizbek Ergashev (lazerg) commented Aug 21, 2026

Copy link
Copy Markdown

Summary

  • AndroidInput.swipe() accepts a from point and then drops it: only segments reaches the protocol, so the gesture actually starts at segments[0]. from is the first point of the path, so it is prepended to segments on the client. No protocol or driver change is needed, since the driver passes the array straight to UiDevice.swipe, which starts at its first point.
  • The minimal documented call swipe(from, [to], steps) used to send a one-element array, which the driver turns into a touch down and up on the same point, so it did not move at all. Anyone who worked around this by repeating from inside segments now gets a zero-length leading segment: same path, one segment worth of extra time.
  • No test: tests/android has no coverage of AndroidInput and no channel-level harness to assert the payload against, so this would need new test infrastructure.

Fixes #42348

@lazerg Lazizbek Ergashev (lazerg) changed the title fix(android): include the 'from' point in the swipe path fix(android): include the from point in the swipe path Aug 21, 2026

@yury-s Yury Semikhatsky (yury-s) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change looks good. Any chance you can add a test for it?

@lazerg

Copy link
Copy Markdown
Author

Added a test in c658786 (tests/android/input.spec.ts). It scrolls a long page with input.swipe(from, [to], steps) and checks scrollY moved. The old code dropped from, so the swipe never moved and this test fails on it.

It goes through the real androidDevice fixtures like the rest of tests/android, so it only runs under npm run atest against the ADB emulator, not locally here. CI's Android job triggers on push or the CQ1 label, not automatically on external PRs, so it hasn't run there yet.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@yury-s

Copy link
Copy Markdown
Member

Added a test in c658786 (tests/android/input.spec.ts). It scrolls a long page with input.swipe(from, [to], steps) and checks scrollY moved. The old code dropped from, so the swipe never moved and this test fails on it.

It goes through the real androidDevice fixtures like the rest of tests/android, so it only runs under npm run atest against the ADB emulator, not locally here. CI's Android job triggers on push or the CQ1 label, not automatically on external PRs, so it hasn't run there yet.

The new test failed on CI: https://github.com/microsoft/playwright/actions/runs/32510988871/job/96862912472?pr=42349

CI showed the swipe assertion timing out with scrollY stuck at 0 on
every retry, even though the swipe call itself completed without
error. androidDevice.input.swipe() injects a raw touch event through
the OS input pipeline, independent of the CDP channel used by
setContent(), so the gesture can land before the new content is
actually composited and presented on the device screen. Wait for two
animation frames after setContent() so the swipe reliably hits the
rendered page.
@lazerg

Copy link
Copy Markdown
Author

Checked the job log. The swipe call did not throw. But window.scrollY stayed at 0 through every retry, so the gesture never scrolled the page. This looks like a race in the test, not a problem with the fix itself.

androidDevice.input.swipe() sends a raw touch event through ADB. That event does not go through the CDP channel that setContent() uses. On a real device the touch can land before the new page content is actually painted on screen, so the swipe likely hit the old blank frame.

Added a short wait for two animation frames after setContent(), before the swipe runs, in commit 9b39365, pushed to fix-42348. Still only runs under npm run atest / CQ1, so it will need another labeled run to confirm.

@Mukller

Copy link
Copy Markdown

Confirmed the root cause independently: \AndroidDispatcher.inputSwipe\ forwards the params verbatim to the on-device agent (\packages/playwright-core/src/server/dispatchers/androidDispatcher.ts), so before this change \ rom\ never reached the driver and every gesture started at \segments[0]. The fix is correct and minimal — thanks.

On the flaky test: the double-
equestAnimationFrame\ wait guarantees the new content frame is composited, but raw touch injection goes through the system input pipeline and can be silently dropped if the browser surface isn't the focused window at that moment. If failures persist after c658786, consider retrying the gesture itself inside the assertion, e.g.:

\\ s
await expect.poll(async () => {
await androidDevice.input.swipe({ x: 250, y: 1500 }, [{ x: 250, y: 500 }], 30);
return page.evaluate(() => window.scrollY);
}, { timeout: 15_000 }).toBeGreaterThan(0);
\\

so a single dropped first gesture doesn't fail the run.

Comment thread tests/android/input.spec.ts Outdated
// pipeline, independent of the CDP connection used by setContent() above. Wait for
// the new content to actually be composited and presented on the device screen,
// otherwise the swipe can land on the previous (blank) frame and never scroll anything.
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use rafraf from ../config/utils. I doubt this will help though, if the test is still flaky, let's revert it and keep just the fix.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to rafraf in 6de3e90.

Worth noting the wait has never actually run on the emulator. The only Android job on this branch is run 32510988871, which built c658786, before the wait existed. tests 2 starts only on the labeled event, so the pushes after the CQ1 label did not re-trigger it. So there is no evidence yet either way.

If you re-apply CQ1 and it still fails, I will drop the test and leave only the fix.

Comment thread tests/android/input.spec.ts Outdated
@@ -0,0 +1,31 @@
/**
* Copyright 2020 Microsoft Corporation. All rights reserved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not 2020 anymore, look at other files.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, copied it from the older files in this folder. Changed to the Copyright (c) Microsoft Corporation. header in 6de3e90.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Test results for "MCP"

1 failed
❌ [msedge] › mcp/annotate.spec.ts:386 › should cancel browser_annotate when the MCP request is aborted @mcp-windows-latest-msedge

8141 passed, 1361 skipped


Merge workflow run.

@github-actions

Copy link
Copy Markdown
Contributor

Test results for "tests 1"

3 flaky ⚠️ [chromium-library] › library/video.spec.ts:736 › screencast › should work with video+trace `@chromium-ubuntu-22.04-node24`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:436 › should produce network events, routing, and annotations for Service Worker (advanced) `@chromium-ubuntu-22.04-node22`
⚠️ [chromium-library] › library/tracing.spec.ts:494 › should produce screencast frames fit `@chromium-ubuntu-22.04-node22`

51371 passed, 1238 skipped


Merge workflow run.

@dgozman

Copy link
Copy Markdown
Collaborator

The new test still fails. I'd recommend to run it locally fist, before sending updates to the PR.

@github-actions

Copy link
Copy Markdown
Contributor

Test results for "tests 2"

1 failed
❌ [android-native] › android/input.spec.ts:20 › androidInput.swipe should start from the given point

24 flaky ⚠️ [chromium-library] › library/chromium/chromium.spec.ts:373 › should produce network events, routing, and annotations for Service Worker `@tracing-chromium`
⚠️ [chromium-library] › library/chromium/connect-over-cdp.spec.ts:523 › emulate media should not be affected by second connectOverCDP with noDefaults `@msedge-dev-windows-latest`
⚠️ [chromium-library] › library/beforeunload.spec.ts:130 › should support dismissing the dialog multiple times `@chrome-ubuntu-22.04`
⚠️ [chromium-library] › library/video.spec.ts:736 › screencast › should work with video+trace `@chromium-windows-latest`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:179 › serviceWorker(), and fromServiceWorker() work `@chromium-macos-15-xlarge`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:301 › should report intercepted service worker requests in HAR `@chrome-beta-ubuntu-22.04`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:179 › serviceWorker(), and fromServiceWorker() work `@chrome-macos-latest`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:213 › should intercept service worker requests (main and within) `@chrome-macos-latest`
⚠️ [chromium-library] › library/selector-generator.spec.ts:437 › selector generator › should prioritize attributes correctly › type `@chrome-macos-latest`
⚠️ [chromium-library] › library/selector-generator.spec.ts:546 › selector generator › should ignore empty aria-label for candidate consideration `@chrome-macos-latest`
⚠️ [chromium-library] › library/selector-generator.spec.ts:736 › selector generator › should not use icon fonts aria name `@chrome-macos-latest`
⚠️ [chromium-library] › library/selectors-register.spec.ts:55 › should work when registered on global `@chrome-macos-latest`
⚠️ [firefox-library] › library/proxy.spec.ts:203 › should work with authenticate followed by redirect `@tracing-firefox`
⚠️ [firefox-page] › page/page-emulate-media.spec.ts:144 › should keep reduced motion and color emulation after reload `@tracing-firefox`
⚠️ [firefox-page] › page/page-goto.spec.ts:90 › should work with Cross-Origin-Opener-Policy `@tracing-firefox`
⚠️ [firefox-page] › page/page-request-continue.spec.ts:288 › should work with Cross-Origin-Opener-Policy `@tracing-firefox`
⚠️ [firefox-library] › library/browsercontext-reuse.spec.ts:200 › reuse connect › should not cache resources `@firefox-windows-latest`
⚠️ [firefox-library] › library/trace-viewer-scrub.spec.ts:98 › should support keyboard navigation on scrubber `@firefox-macos-15-xlarge`
⚠️ [webkit-library] › library/trace-viewer-scrub.spec.ts:157 › should update scrubber aria-valuenow `@webkit-macos-15-xlarge`
⚠️ [webkit-library] › library/trace-viewer.spec.ts:1893 › should not leak recorders `@webkit-macos-15-xlarge`
⚠️ [webkit-library] › library/trace-viewer.spec.ts:1965 › canvas disabled title `@webkit-macos-15-xlarge`
⚠️ [webkit-library] › library/proxy.spec.ts:404 › should use http proxy for websocket requests `@webkit-macos-15-large`
⚠️ [android-page] › page/page-goto.spec.ts:460 › should disable timeout when its set to 0
⚠️ [android-page] › page/page-request-fulfill.spec.ts:81 › should fulfill with unuassigned status codes

110794 passed, 4724 skipped


Merge workflow run.

@lazerg

Copy link
Copy Markdown
Author

Ran it on a local emulator this time, an arm64 AVD with the same API 35 Nexus 5X profile utils/avd_recreate.sh builds.

The gesture never reaches the page. I attached touchstart/touchmove/touchend/click listeners and injected input four ways: androidDevice.input.swipe, androidDevice.input.tap, adb shell input swipe and adb shell input tap. All four recorded zero events in the page and left scrollY at 0, while Chrome held focus and the coordinates sat inside the web contents area (devicePixelRatio 2.625, innerHeight 628 of a 1080x1920 screen). I repeated the run with -gpu swiftshader_indirect and got the same numbers.

So the render race I guessed at earlier was wrong, and no amount of waiting or retrying gets past it. Nothing else in the repo covers AndroidInput, and this looks like the reason.

I removed the test and kept the one line fix, which is what Yury suggested if it stayed flaky.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: AndroidInput.swipe() silently ignores its documented 'from' point

4 participants