From adbcd7ed47d2d3e2ba0ba16393446b2ede17511f Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 29 May 2026 10:49:00 -0700 Subject: [PATCH 1/5] test: update image baseline for webview (#41053) --- .../mock-binary-response-webkit-webview.png | Bin 10009 -> 9605 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/page/page-request-fulfill.spec.ts-snapshots/mock-binary-response-webkit-webview.png b/tests/page/page-request-fulfill.spec.ts-snapshots/mock-binary-response-webkit-webview.png index 62914006a65f4f05edea29f8381edba43b96e06d..c81b4e3aaf7c0fd3c00011e26b71c010430d7952 100644 GIT binary patch literal 9605 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 R-G5)8l;qT8tEJ7u{vSEYVSoSt literal 10009 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 Date: Fri, 29 May 2026 11:01:52 -0700 Subject: [PATCH 2/5] fix(webview): support site isolation and fix postData (#41041) --- .../webkit/webview/wvInterceptableRequest.ts | 7 +- .../src/server/webkit/webview/wvPage.ts | 10 +- .../mock-svg-webkit-webview.png | Bin 0 -> 472 bytes .../expectations/webkit-webview-page.txt | 129 +++++++++--------- 4 files changed, 75 insertions(+), 71 deletions(-) create mode 100644 tests/page/page-request-fulfill.spec.ts-snapshots/mock-svg-webkit-webview.png diff --git a/packages/playwright-core/src/server/webkit/webview/wvInterceptableRequest.ts b/packages/playwright-core/src/server/webkit/webview/wvInterceptableRequest.ts index 7e646b07cad18..ec0fdb1a71663 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvInterceptableRequest.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvInterceptableRequest.ts @@ -56,8 +56,11 @@ export class WVInterceptableRequest { let postDataBuffer = null; this._timestamp = event.timestamp; this._wallTime = event.walltime * 1000; - if (event.request.postData) - postDataBuffer = Buffer.from(event.request.postData, 'base64'); + if (event.request.postData) { + // Stock WebKit reports Network.Request.postData as a plain string, unlike + // the Playwright-patched WebKit build which base64-encodes it. + postDataBuffer = Buffer.from(event.request.postData, 'utf8'); + } this.request = new network.Request(frame._page.browserContext, frame, null, redirectedFrom?.request || null, documentId, event.request.url, resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers)); } diff --git a/packages/playwright-core/src/server/webkit/webview/wvPage.ts b/packages/playwright-core/src/server/webkit/webview/wvPage.ts index 88d1ace33c3cb..ac5f195c82530 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvPage.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvPage.ts @@ -210,7 +210,15 @@ export class WVPage implements PageDelegate { private async _onTargetCreated(event: Protocol.Target.targetCreatedPayload) { const { targetInfo } = event; - assert(targetInfo.type === 'page', 'Only page targets are expected in WebView, received: ' + targetInfo.type); + if (targetInfo.type !== 'page') { + // Site-isolated WebKit (iOS 26+) reports a separate target per frame. We + // drive the whole page through the top-level page target, so out-of-process + // frame targets are not attached. Resume any paused ones so navigation in + // the owning page is not blocked waiting on them. + if (targetInfo.isPaused) + this._outerSession.sendMayFail('Target.resume', { targetId: targetInfo.targetId }); + return; + } const session = this._createSession(targetInfo.targetId); if (!targetInfo.isProvisional) { let pageOrError: Page | Error; diff --git a/tests/page/page-request-fulfill.spec.ts-snapshots/mock-svg-webkit-webview.png b/tests/page/page-request-fulfill.spec.ts-snapshots/mock-svg-webkit-webview.png new file mode 100644 index 0000000000000000000000000000000000000000..39dbc1e30f3a8d11387f5436e0f0db1845d1def4 GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^wjj*G1|;Q@5?=x-#^NA%Cx&(BWI!C3)CkWsUtb0- zAe)1Mu|1Q41*8OsrGS`$fq4NV12d3j1c@zRg3H=2U`DV(3JoQ>*8`O~dAc};WU#)S zZOGSbAmTiE6?ccWfkB~I-`(U5%FE_3v~9~&RcY&39}${m5Zow{vtLMY>DTk$pRL=< zyFL5q-({gMRtJb@PP8jz{#7+!;+@{D+U?VCWX?LKdwP<^Z~OM+g(b1qWu=T-`x>v8 z+U}7!&0u?3XGy?(rCzfWi_CpfRvIX>golQ{`f|SSd+qyWnWy&IiCD3%du{dm*-oeT zwd=ktzHH2U)m=p_Qz>FX=)|^H%0k^Hf;uNwc{qO+>~x*QA(j%lWZ|!lBTBw3+?%v6 z1=PAEO;l@QjTFsXVW+gwL-=6Rnl6(N`H2xrIujhj7P9WyQPrWh?CFmA&n?}zWiGgI zqUhzb1qaw|FSB~-7kN+A3#x9MJxguV4{w{cwYkxgeb@glm{Ijeeo1G~{Dr!W9=CyP o)k}YPaycKJKA9k0H7l@Ryz02hhkyRpib0|3>FVdQ&MBb@05httd;kCd literal 0 HcmV?d00001 diff --git a/tests/webview/expectations/webkit-webview-page.txt b/tests/webview/expectations/webkit-webview-page.txt index 881c93621fae9..ea66ffa6073dc 100644 --- a/tests/webview/expectations/webkit-webview-page.txt +++ b/tests/webview/expectations/webkit-webview-page.txt @@ -123,76 +123,72 @@ page/elementhandle-screenshot.spec.ts › element screenshot › should not issu page/locator-misc-1.spec.ts › should focus and blur a button [fail] # ============================================================================ -# execution-context-destroyed (65 tests) -# Cross-navigation handle invalidation is racy on stock RDP — execution -# contexts are reported destroyed before our frame manager learns about the -# navigation. Needs proper Runtime.executionContextsCleared wiring in wvPage. -# ============================================================================ -page/elementhandle-convenience.spec.ts › innerHTML should work [fail] -page/elementhandle-convenience.spec.ts › innerText should work [fail] -page/elementhandle-convenience.spec.ts › textContent should work [fail] -page/elementhandle-misc.spec.ts › should fill input when Node is removed [fail] -page/elementhandle-misc.spec.ts › should select single option [fail] -page/elementhandle-press.spec.ts › should not modify selection when focused [fail] -page/elementhandle-query-selector.spec.ts › should query existing element [fail] -page/elementhandle-select-text.spec.ts › should select input [fail] -page/elementhandle-select-text.spec.ts › should select textarea [fail] -page/elementhandle-type.spec.ts › should not modify selection when focused [fail] -page/elementhandle-wait-for-element-state.spec.ts › should wait for stable position [fail] +# service-worker (2 tests) +# Service-worker request interception/reporting is unsupported on the stock +# WebView backend; routing a request through a service worker tears down the +# session ('Target closed'). +# ============================================================================ page/interception.spec.ts › should intercept after a service worker [fail] -page/locator-click.spec.ts › should double click the button [fail] -page/locator-element-handle.spec.ts › xpath should query existing element [fail] -page/locator-list.spec.ts › locator.all should work [fail] -page/locator-misc-2.spec.ts › should select textarea [fail] -page/network-post-data.spec.ts › should return post data for PUT requests [fail] -page/network-post-data.spec.ts › should return post data w/o content-type @smoke [fail] -page/page-add-init-script.spec.ts › should support multiple scripts [fail] -page/page-add-locator-handler.spec.ts › should not work with force:true [fail] -page/page-add-locator-handler.spec.ts › should work › mouseover 1 times [fail] -page/page-add-script-tag.spec.ts › should work with a content and type=module [fail] -page/page-add-style-tag.spec.ts › should include sourceURL when path is provided [fail] -page/page-aria-snapshot.spec.ts › should include pseudo codepoints [fail] +page/page-event-request.spec.ts › should report requests and responses handled by service worker with routing [fail] + +# ============================================================================ +# out-of-process-iframe (1 test) +# iOS 26 site-isolates frames into their own targets, which wvPage does not +# attach — it drives the page through the top-level page target only. Actions +# that must reach inside a cross-origin iframe therefore time out. +# ============================================================================ page/page-click-scroll.spec.ts › should scroll into view element in iframe [fail] -page/page-click.spec.ts › should click on checkbox label and toggle [fail] -page/page-click.spec.ts › should click the button after navigation [fail] -page/page-click.spec.ts › should fail when element detaches after animation [fail] -page/page-click.spec.ts › should not wait with force [fail] -page/page-click.spec.ts › should report nice error when element is detached and force-clicked [fail] -page/page-click.spec.ts › should waitFor display:none to be gone [fail] -page/page-click.spec.ts › should waitFor visibility:hidden to be gone [fail] -page/page-click.spec.ts › should waitFor visible when parent is hidden [fail] + +# ============================================================================ +# native-drag-and-drop (1 test) +# Synthetic mouse events cannot drive WebKit's native HTML5 drag controller, so +# dragstart/dragenter/dragover/drop are never produced for draggable elements. +# ============================================================================ page/page-drag.spec.ts › Drag and drop › should send the right events [fail] -page/page-evaluate.spec.ts › should work with overridden globalThis.Window/Document/Node › () => globalThis.Window = null [fail] -page/page-evaluate.spec.ts › should work with overridden globalThis.Window/Document/Node › () => globalThis.Window = {} [fail] -page/page-event-request.spec.ts › should report requests and responses handled by service worker with routing [fail] -page/page-event-request.spec.ts › should return last requests [fail] -page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - color [fail] -page/page-fill.spec.ts › should fill different input types [fail] -page/page-fill.spec.ts › should retry on disabled element [fail] -page/page-fill.spec.ts › should retry on invisible element [fail] -page/page-fill.spec.ts › should retry on readonly element [fail] -page/page-fill.spec.ts › should throw on unsupported inputs [fail] -page/page-history.spec.ts › page.reload should not resolve with same-document navigation [fail] -page/page-keyboard.spec.ts › insertText should only emit input event [fail] -page/page-keyboard.spec.ts › should have correct Keydown/Keyup order when pressing Escape key [fail] -page/page-keyboard.spec.ts › should report multiple modifiers [fail] -page/page-keyboard.spec.ts › should report shiftKey [fail] -page/page-keyboard.spec.ts › should specify location [fail] -page/page-mouse.spec.ts › should set modifier keys on click [fail] -page/page-network-request.spec.ts › should parse the json post data [fail] -page/page-route.spec.ts › should intercept when postData is more than 1MB [fail] -page/page-route.spec.ts › should pause intercepted fetch request until continue [fail] + +# ============================================================================ +# screenshot-animations (4 tests) +# screenshot({ animations: 'disabled' }) cannot freeze or advance animations +# over the stock RDP (no Playwright screenshot patches), so animation-sensitive +# screenshot comparisons differ. +# ============================================================================ page/page-screenshot.spec.ts › page screenshot animations › should capture screenshots after layoutchanges in transitionend event › make sure transition is actually running [fail] page/page-screenshot.spec.ts › page screenshot animations › should fire transitionend for finite transitions › make sure transition is actually running [fail] page/page-screenshot.spec.ts › page screenshot animations › should not capture css animations in shadow DOM [fail] page/page-screenshot.spec.ts › page screenshot animations › should not capture pseudo element css animation [fail] -page/page-screenshot.spec.ts › page screenshot animations › should not change animation with playbackRate equal to 0 [fail] -page/page-screenshot.spec.ts › page screenshot animations › should resume infinite animations [fail] -page/page-select-option.spec.ts › should wait for multiple options to be present [fail] -page/page-select-option.spec.ts › should wait for option index to be present [fail] -page/page-wait-for-selector-1.spec.ts › should report logs while waiting for hidden [fail] -page/retarget.spec.ts › input value retargeting › "label" in "" input value [fail] -page/selectors-css.spec.ts › should work for open shadow roots [fail] + +# ============================================================================ +# network-request-tracking (1 test) +# Rapidly-issued requests are occasionally dropped from the page's request log +# on the stock RDP, so the full ordered list comes back incomplete. +# ============================================================================ +page/page-event-request.spec.ts › should return last requests [fail] + +# ============================================================================ +# redirect-interception (3 tests) +# Request interception across HTTP redirects is incomplete on the stock WebView +# backend — redirected (and redirected subresource) requests are not surfaced +# to the route handler, and the final navigation response comes back null. +# ============================================================================ +page/page-route.spec.ts › should not work with redirects [fail] +page/page-route.spec.ts › should chain fallback w/ dynamic URL [fail] +page/page-route.spec.ts › should work with redirects for subresources [fail] + +# ============================================================================ +# main-navigation-fulfill-flaky (6 tests) +# Intercepting/fulfilling the MAIN navigation request is flaky on iOS 26: the +# navigation triggers a process swap and the provisional target arrives +# un-paused, so interception races the request and page.goto resolves to null +# (or the real server response). Subresource interception (e.g. mocking an +# ) is unaffected. Pre-existing flakiness, surfaced now that the suite +# runs again. Needs the provisional target to honor Target.setPauseOnStart. +# ============================================================================ +page/page-request-fulfill.spec.ts › should work [fail] +page/page-request-fulfill.spec.ts › should work with buffer as body [fail] +page/page-request-fulfill.spec.ts › should work with status code 422 [fail] +page/page-request-fulfill.spec.ts › should fulfill with fetch response that has multiple set-cookie [fail] +page/page-request-fulfill.spec.ts › should fulfill with fetch result and overrides [fail] +page/page-request-fulfill.spec.ts › should fulfill with gzip and readback [fail] # ============================================================================ # element-not-attached (11 tests) @@ -220,9 +216,11 @@ page/page-screenshot.spec.ts › page screenshot animations › should not captu # deserves individual triage. # ============================================================================ page/elementhandle-bounding-box.spec.ts › should handle scroll offset and click [fail] +page/elementhandle-press.spec.ts › should not modify selection when focused [fail] page/elementhandle-press.spec.ts › should not select existing value [fail] page/elementhandle-press.spec.ts › should reset selection when not focused [fail] page/elementhandle-press.spec.ts › should work [fail] +page/elementhandle-type.spec.ts › should not modify selection when focused [fail] page/elementhandle-type.spec.ts › should not select existing value [fail] page/elementhandle-type.spec.ts › should reset selection when not focused [fail] page/elementhandle-type.spec.ts › should work [fail] @@ -377,7 +375,6 @@ page/page-request-continue.spec.ts › propagate headers same origin redirect [f page/page-request-continue.spec.ts › redirect after continue should be able to delete cookie [fail] page/page-request-continue.spec.ts › should respect set-cookie in redirect response [fail] page/page-request-fallback.spec.ts › should not chain abort [fail] -page/page-request-fulfill.spec.ts › should fulfill with fetch response that has multiple set-cookie [fail] page/page-request-fulfill.spec.ts › should fulfill with har response [fail] page/page-request-fulfill.spec.ts › should fulfill with multiple set-cookie [fail] page/page-request-intercept.spec.ts › should support timeout option in route.fetch [fail] @@ -492,9 +489,6 @@ page/page-request-continue.spec.ts › should not forward Host header on cross-o page/page-request-continue.spec.ts › should work with Cross-Origin-Opener-Policy [fail] page/page-request-fallback.spec.ts › should fall back after exception [fail] page/page-request-fallback.spec.ts › should work [fail] -page/page-request-fulfill.spec.ts › should allow mocking binary responses [fail] -page/page-request-fulfill.spec.ts › should allow mocking svg with charset [fail] -page/page-request-fulfill.spec.ts › should fulfill with fetch result and overrides [fail] page/page-route.spec.ts › should not support ? in glob pattern [fail] page/page-screenshot.spec.ts › page screenshot › should capture canvas changes [fail] page/page-screenshot.spec.ts › page screenshot › should work with odd clip size on Retina displays [fail] @@ -544,7 +538,6 @@ page/page-emulate-media.spec.ts › should emulate contrast [fail] page/page-emulate-media.spec.ts › should emulate forcedColors [fail] page/page-emulate-media.spec.ts › should emulate type @smoke [fail] page/page-emulate-media.spec.ts › should work during navigation [fail] -page/page-request-fulfill.spec.ts › should fulfill with gzip and readback [fail] page/page-request-gc.spec.ts › should work [fail] page/page-request-intercept.spec.ts › should fulfill intercepted response using alias [fail] page/page-request-intercept.spec.ts › should intercept with url override [fail] From ee5a51e730b5fefc98aed383b62bf5e7536047ee Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Sat, 30 May 2026 00:03:20 +0200 Subject: [PATCH 3/5] fix(core): show friendly error for unsupported Node.js versions (#41050) --- packages/playwright-core/index.js | 17 +---------------- packages/playwright-core/src/bootstrap.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/playwright-core/index.js b/packages/playwright-core/index.js index e845cfae9f253..9fc51a4d89475 100644 --- a/packages/playwright-core/index.js +++ b/packages/playwright-core/index.js @@ -13,20 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const minimumMajorNodeVersion = 18; -const currentNodeVersion = process.versions.node; -const semver = currentNodeVersion.split('.'); -const [major] = [+semver[0]]; - -if (major < minimumMajorNodeVersion) { - console.error( - 'You are running Node.js ' + - currentNodeVersion + - '.\n' + - `Playwright requires Node.js ${minimumMajorNodeVersion} or higher. \n` + - 'Please update your version of Node.js.' - ); - process.exit(1); -} - +require('./lib/bootstrap'); module.exports = require('./lib/coreBundle').inprocess.playwright; diff --git a/packages/playwright-core/src/bootstrap.ts b/packages/playwright-core/src/bootstrap.ts index 7ca2fa35c4415..5e611c75c8d1f 100644 --- a/packages/playwright-core/src/bootstrap.ts +++ b/packages/playwright-core/src/bootstrap.ts @@ -14,6 +14,23 @@ * limitations under the License. */ +const minimumMajorNodeVersion = 18; +const currentNodeVersion = process.versions.node; +const major = +currentNodeVersion.split('.')[0]; + +if (major < minimumMajorNodeVersion) { + // eslint-disable-next-line no-console + console.error( + 'You are running Node.js ' + + currentNodeVersion + + '.\n' + + `Playwright requires Node.js ${minimumMajorNodeVersion} or higher. \n` + + 'Please update your version of Node.js.' + ); + // eslint-disable-next-line no-restricted-properties + process.exit(1); +} + if (process.env.PW_INSTRUMENT_MODULES) { const Module = require('module'); const originalLoad = Module._load; From bc2960793c05285244cdcc65d46f2b2b409a1373 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Sat, 30 May 2026 00:04:04 +0200 Subject: [PATCH 4/5] chore: consolidate vite SW+main builds via Environment API (#41047) --- packages/extension/vite.config.mts | 41 ++++++++++++------ packages/extension/vite.sw.config.mts | 31 -------------- packages/trace-viewer/vite.config.ts | 48 +++++++++++++++------ packages/trace-viewer/vite.sw.config.ts | 56 ------------------------- utils/build/build.js | 51 +++++++--------------- 5 files changed, 79 insertions(+), 148 deletions(-) delete mode 100644 packages/extension/vite.sw.config.mts delete mode 100644 packages/trace-viewer/vite.sw.config.ts diff --git a/packages/extension/vite.config.mts b/packages/extension/vite.config.mts index 89ec56c6898a2..01bc27c9c3847 100644 --- a/packages/extension/vite.config.mts +++ b/packages/extension/vite.config.mts @@ -19,7 +19,6 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { viteStaticCopy } from 'vite-plugin-static-copy'; -// https://vitejs.dev/config/ export default defineConfig({ plugins: [ react(), @@ -37,17 +36,35 @@ export default defineConfig({ }) ], root: resolve(__dirname, 'src/ui'), - build: { - outDir: resolve(__dirname, 'dist/'), - emptyOutDir: false, - minify: false, - rollupOptions: { - input: ['src/ui/connect.html', 'src/ui/status.html'], - output: { - manualChunks: undefined, - entryFileNames: 'lib/ui/[name].js', - chunkFileNames: 'lib/ui/[name].js', - assetFileNames: 'lib/ui/[name].[ext]' + builder: {}, + environments: { + client: { + build: { + outDir: resolve(__dirname, 'dist/'), + emptyOutDir: false, + minify: false, + rollupOptions: { + input: ['src/ui/connect.html', 'src/ui/status.html'], + output: { + manualChunks: undefined, + entryFileNames: 'lib/ui/[name].js', + chunkFileNames: 'lib/ui/[name].js', + assetFileNames: 'lib/ui/[name].[ext]' + } + } + } + }, + sw: { + consumer: 'client', + build: { + outDir: resolve(__dirname, 'dist/'), + emptyOutDir: false, + minify: false, + lib: { + entry: resolve(__dirname, 'src/background.ts'), + fileName: 'lib/background', + formats: ['es'] + } } } } diff --git a/packages/extension/vite.sw.config.mts b/packages/extension/vite.sw.config.mts deleted file mode 100644 index a383e4b4a50e9..0000000000000 --- a/packages/extension/vite.sw.config.mts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { resolve } from 'path'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - build: { - lib: { - entry: resolve(__dirname, 'src/background.ts'), - fileName: 'lib/background', - formats: ['es'] - }, - outDir: 'dist', - emptyOutDir: false, - minify: false - } -}); diff --git a/packages/trace-viewer/vite.config.ts b/packages/trace-viewer/vite.config.ts index f88f723d05f7c..3d0f63e65e558 100644 --- a/packages/trace-viewer/vite.config.ts +++ b/packages/trace-viewer/vite.config.ts @@ -42,20 +42,42 @@ export default defineConfig({ '@web': path.resolve(__dirname, '../web/src'), }, }, - build: { - outDir: path.resolve(__dirname, '../playwright-core/lib/vite/traceViewer'), - emptyOutDir: false, - rollupOptions: { - input: { - index: path.resolve(__dirname, 'index.html'), - uiMode: path.resolve(__dirname, 'uiMode.html'), - snapshot: path.resolve(__dirname, 'snapshot.html'), + builder: {}, + environments: { + client: { + build: { + outDir: path.resolve(__dirname, '../playwright-core/lib/vite/traceViewer'), + emptyOutDir: false, + rollupOptions: { + input: { + index: path.resolve(__dirname, 'index.html'), + uiMode: path.resolve(__dirname, 'uiMode.html'), + snapshot: path.resolve(__dirname, 'snapshot.html'), + }, + output: { + entryFileNames: () => '[name].[hash].js', + assetFileNames: () => '[name].[hash][extname]', + manualChunks: undefined, + }, + }, }, - output: { - entryFileNames: () => '[name].[hash].js', - assetFileNames: () => '[name].[hash][extname]', - manualChunks: undefined, + }, + sw: { + consumer: 'client', + build: { + outDir: path.resolve(__dirname, '../playwright-core/lib/vite/traceViewer'), + emptyOutDir: false, + rollupOptions: { + input: { + sw: path.resolve(__dirname, 'src/sw-main.ts'), + }, + output: { + entryFileNames: () => 'sw.bundle.js', + assetFileNames: () => 'sw.[hash][extname]', + manualChunks: undefined, + }, + }, }, }, - } + }, }); diff --git a/packages/trace-viewer/vite.sw.config.ts b/packages/trace-viewer/vite.sw.config.ts deleted file mode 100644 index 1c53ac27db404..0000000000000 --- a/packages/trace-viewer/vite.sw.config.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import path from 'path'; - -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -import { bundle } from './bundle'; - -// https://vitejs.dev/config/ -export default defineConfig({ - base: '', - plugins: [ - react(), - bundle() - ], - resolve: { - alias: { - '@isomorphic': path.resolve(__dirname, '../isomorphic'), - '@protocol': path.resolve(__dirname, '../protocol/src'), - '@testIsomorphic': path.resolve(__dirname, '../playwright/src/isomorphic'), - '@trace': path.resolve(__dirname, '../trace/src'), - '@web': path.resolve(__dirname, '../web/src'), - }, - }, - publicDir: false, - build: { - outDir: path.resolve(__dirname, '../playwright-core/lib/vite/traceViewer'), - // Output dir is shared with vite.config.ts, clearing it here is racy. - emptyOutDir: false, - rollupOptions: { - input: { - sw: path.resolve(__dirname, 'src/sw-main.ts'), - }, - output: { - entryFileNames: info => 'sw.bundle.js', - assetFileNames: () => 'sw.[hash][extname]', - manualChunks: undefined, - }, - }, - } -}); diff --git a/utils/build/build.js b/utils/build/build.js index 964c2a620fdb8..814da980b2b29 100644 --- a/utils/build/build.js +++ b/utils/build/build.js @@ -857,29 +857,12 @@ const pkgSizePlugin = { }, }; -// Build/watch trace viewer service worker. -steps.push(new ProgramStep({ - command: 'npx', - args: [ - 'vite', - '--config', - 'vite.sw.config.ts', - 'build', - ...(watchMode ? ['--watch', '--minify=false'] : []), - ...(withSourceMaps ? ['--sourcemap=inline'] : []), - ], - shell: true, - cwd: path.join(__dirname, '..', '..', 'packages', 'trace-viewer'), - concurrent: true, -})); - // Build/watch web packages. The html-reporter, trace-viewer, and dashboard // also have embedded Vite dev servers used when viewing reports/traces/the // dashboard live, but their bundled output is consumed as a static artifact // in other code paths (e.g. HtmlBuilder.build() reads lib/vite/htmlReport/ // and lib/vite/traceViewer/), so we always keep the static build alongside -// HMR. Recorder is not yet HMR'd. The trace viewer service worker still -// builds via vite.sw.config.ts above — that step is not in this loop. +// HMR. Recorder is not yet HMR'd. const webPackages = ['html-reporter', 'recorder', 'trace-viewer', 'dashboard']; for (const webPackage of webPackages) { steps.push(new ProgramStep({ @@ -897,24 +880,20 @@ for (const webPackage of webPackages) { })); } -// Build/watch extension UI pages and service worker. -for (const config of ['vite.config.mts', 'vite.sw.config.mts']) { - steps.push(new ProgramStep({ - command: 'npx', - args: [ - 'vite', - 'build', - '--config', - config, - ...(watchMode ? ['--watch', '--minify=false'] : []), - ...(withSourceMaps ? ['--sourcemap=inline'] : []), - '--clearScreen=false', - ], - shell: true, - cwd: path.join(__dirname, '..', '..', 'packages', 'extension'), - concurrent: true, - })); -} +// Build/watch extension +steps.push(new ProgramStep({ + command: 'npx', + args: [ + 'vite', + 'build', + ...(watchMode ? ['--watch', '--minify=false'] : []), + ...(withSourceMaps ? ['--sourcemap=inline'] : []), + '--clearScreen=false', + ], + shell: true, + cwd: path.join(__dirname, '..', '..', 'packages', 'extension'), + concurrent: true, +})); // Generate CLI help. onChanges.push({ From 9fe284a594096072d0de3adc419db3933b8ced14 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 29 May 2026 15:35:41 -0700 Subject: [PATCH 5/5] feat(webview): synthesize keyboard and mouse input (#41055) --- packages/injected/src/webview/webViewInput.ts | 329 ++++++++++++++++++ .../src/server/webkit/DEPS.list | 1 + .../src/server/webkit/webview/wvInput.ts | 114 ++---- .../src/server/webkit/webview/wvPage.ts | 13 +- tests/page/page-keyboard.spec.ts | 13 + .../expectations/webkit-webview-page.txt | 35 +- utils/generate_injected.js | 6 + 7 files changed, 388 insertions(+), 123 deletions(-) create mode 100644 packages/injected/src/webview/webViewInput.ts diff --git a/packages/injected/src/webview/webViewInput.ts b/packages/injected/src/webview/webViewInput.ts new file mode 100644 index 0000000000000..4b293c0059997 --- /dev/null +++ b/packages/injected/src/webview/webViewInput.ts @@ -0,0 +1,329 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type Modifiers = { + ctrlKey: boolean; + shiftKey: boolean; + altKey: boolean; + metaKey: boolean; +}; + +export type KeyEventParams = Modifiers & { + code: string; + key: string; + keyCode: number; + location: number; + repeat: boolean; + // Present for printable keys; absent for non-text keys (arrows, modifiers, etc.). + text?: string; +}; + +export type MouseEventParams = Modifiers & { + type: 'mousedown' | 'mouseup' | 'click' | 'auxclick' | 'dblclick' | 'contextmenu'; + x: number; + y: number; + button: number; + buttons: number; + clickCount: number; +}; + +export type MouseMoveParams = Modifiers & { + x: number; + y: number; + button: number; + buttons: number; +}; + +export type WheelParams = Modifiers & { + x: number; + y: number; + deltaX: number; + deltaY: number; +}; + +export type TapParams = Modifiers & { + x: number; + y: number; +}; + +const kTrustedSynthetic = '__pwTrustedSynthetic'; + +function markAndDispatch(node: EventTarget, event: Event): boolean { + Object.defineProperty(event, kTrustedSynthetic, { value: true }); + return node.dispatchEvent(event); +} + +// Legacy WebKit-only KeyboardEvent.keyIdentifier (a DOM Level 3 draft property +// dropped by every other engine). It cannot be supplied via the constructor, so +// compute it from the virtual key code and define it on the event before +// dispatch. Mirrors WebCore's keyIdentifierForWindowsKeyCode. +const kNamedKeyIdentifiers: Record = { + 8: 'U+0008', // Backspace + 9: 'U+0009', // Tab + 13: 'Enter', + 16: 'Shift', + 17: 'Control', + 18: 'Alt', + 27: 'U+001B', // Escape + 33: 'PageUp', + 34: 'PageDown', + 35: 'End', + 36: 'Home', + 37: 'Left', + 38: 'Up', + 39: 'Right', + 40: 'Down', + 45: 'Insert', + 46: 'U+007F', // Delete +}; + +function keyIdentifierFor(keyCode: number, key: string): string { + const named = kNamedKeyIdentifiers[keyCode]; + if (named !== undefined) + return named; + if (keyCode >= 112 && keyCode <= 135) + return 'F' + (keyCode - 111); + if (key.length === 1) + return 'U+' + key.toUpperCase().charCodeAt(0).toString(16).toUpperCase().padStart(4, '0'); + return ''; +} + +function dispatchKeyEvent(node: EventTarget, type: string, init: KeyboardEventInit, keyCode: number, key: string): boolean { + const event = new KeyboardEvent(type, init); + Object.defineProperty(event, 'keyIdentifier', { value: keyIdentifierFor(keyCode, key), configurable: true }); + return markAndDispatch(node, event); +} + +export class WebViewInput { + private _window: Window & typeof globalThis; + private _document: Document; + private _hoverTarget: Element | null = null; + + constructor(window: Window & typeof globalThis, document: Document) { + this._window = window; + this._document = document; + } + + // Descend through open shadow roots so synthetic events land on the actual + // element under the pointer rather than on the shadow host. + private _deepElementFromPoint(x: number, y: number): Element | null { + let el = this._document.elementFromPoint(x, y); + while (el && el.shadowRoot) { + const inner = el.shadowRoot.elementFromPoint(x, y); + if (!inner || inner === el) + break; + el = inner; + } + return el; + } + + // The focused element may live inside one or more shadow roots, where + // document.activeElement only reports the outermost shadow host. + private _deepActiveElement(): Element | null { + let active = this._document.activeElement; + while (active && active.shadowRoot && active.shadowRoot.activeElement) + active = active.shadowRoot.activeElement; + return active; + } + + private _insertText(target: Element | null, text: string) { + if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { + const start = target.selectionStart ?? target.value.length; + const end = target.selectionEnd ?? target.value.length; + target.value = target.value.slice(0, start) + text + target.value.slice(end); + const pos = start + text.length; + try { + target.setSelectionRange(pos, pos); + } catch { + } + target.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: false, data: text, inputType: 'insertText' })); + } else if (target && (target as HTMLElement).isContentEditable) { + this._document.execCommand('insertText', false, text); + } + } + + keydown(params: KeyEventParams) { + const target = this._deepActiveElement() || this._document.body; + if (!target) + return; + const init: KeyboardEventInit = { + bubbles: true, + cancelable: true, + view: this._window, + code: params.code, + key: params.key, + keyCode: params.keyCode, + which: params.keyCode, + location: params.location, + repeat: params.repeat, + ctrlKey: params.ctrlKey, + shiftKey: params.shiftKey, + altKey: params.altKey, + metaKey: params.metaKey, + }; + const notPrevented = dispatchKeyEvent(target, 'keydown', init, params.keyCode, params.key); + if (params.text === undefined) + return; + const charCode = params.text.charCodeAt(0); + const charNotPrevented = markAndDispatch(target, new KeyboardEvent('keypress', { ...init, charCode, keyCode: charCode, which: charCode })); + if (!notPrevented || !charNotPrevented) + return; + // Real WebKit fires a `textInput` (TextEvent) whose default action performs + // the insertion (and the subsequent beforeinput/input). Replicate it; the + // event's default does the insertion, so we do not insert manually. Enter's + // text is '\r' but the inserted/textInput data is a newline. + this._dispatchTextInput(target, params.text === '\r' ? '\n' : params.text); + } + + private _dispatchTextInput(target: EventTarget, text: string) { + // TextEvent has no usable constructor in WebKit — initTextEvent is the only + // way to create one (initTextEvent(type, bubbles, cancelable, view, data)). + const event = this._document.createEvent('TextEvent') as any; + event.initTextEvent('textInput', true, true, this._window, text); + markAndDispatch(target, event); + } + + keyup(params: KeyEventParams) { + const target = this._deepActiveElement() || this._document.body; + if (!target) + return; + dispatchKeyEvent(target, 'keyup', { + bubbles: true, + cancelable: true, + view: this._window, + code: params.code, + key: params.key, + keyCode: params.keyCode, + which: params.keyCode, + location: params.location, + ctrlKey: params.ctrlKey, + shiftKey: params.shiftKey, + altKey: params.altKey, + metaKey: params.metaKey, + }, params.keyCode, params.key); + } + + insertText(text: string) { + this._insertText(this._deepActiveElement(), text); + } + + mouseMove(params: MouseMoveParams) { + const target = this._deepElementFromPoint(params.x, params.y) || this._document.documentElement; + const base: MouseEventInit = { + bubbles: true, + cancelable: true, + view: this._window, + clientX: params.x, + clientY: params.y, + screenX: params.x, + screenY: params.y, + button: params.button, + buttons: params.buttons, + ctrlKey: params.ctrlKey, + shiftKey: params.shiftKey, + altKey: params.altKey, + metaKey: params.metaKey, + }; + const pointer: PointerEventInit = { ...base, pointerId: 1, pointerType: 'mouse', isPrimary: true }; + const prev = this._hoverTarget; + if (prev !== target) { + if (prev && prev.isConnected) { + markAndDispatch(prev, new PointerEvent('pointerout', { ...pointer, relatedTarget: target })); + markAndDispatch(prev, new MouseEvent('mouseout', { ...base, relatedTarget: target })); + markAndDispatch(prev, new PointerEvent('pointerleave', { ...pointer, bubbles: false, cancelable: false, relatedTarget: target })); + markAndDispatch(prev, new MouseEvent('mouseleave', { ...base, bubbles: false, cancelable: false, relatedTarget: target })); + } + markAndDispatch(target, new PointerEvent('pointerover', { ...pointer, relatedTarget: prev })); + markAndDispatch(target, new MouseEvent('mouseover', { ...base, relatedTarget: prev })); + markAndDispatch(target, new PointerEvent('pointerenter', { ...pointer, bubbles: false, cancelable: false, relatedTarget: prev })); + markAndDispatch(target, new MouseEvent('mouseenter', { ...base, bubbles: false, cancelable: false, relatedTarget: prev })); + this._hoverTarget = target; + } + markAndDispatch(target, new PointerEvent('pointermove', pointer)); + markAndDispatch(target, new MouseEvent('mousemove', base)); + } + + mouseEvent(params: MouseEventParams) { + const target = this._deepElementFromPoint(params.x, params.y) || this._document.documentElement; + const event = new MouseEvent(params.type, { + bubbles: true, + cancelable: true, + view: this._window, + clientX: params.x, + clientY: params.y, + screenX: params.x, + screenY: params.y, + button: params.button, + buttons: params.buttons, + detail: params.clickCount, + ctrlKey: params.ctrlKey, + shiftKey: params.shiftKey, + altKey: params.altKey, + metaKey: params.metaKey, + }); + markAndDispatch(target, event); + } + + wheel(params: WheelParams) { + const target = this._deepElementFromPoint(params.x, params.y) || this._document.documentElement; + const event = new WheelEvent('wheel', { + bubbles: true, + cancelable: true, + view: this._window, + clientX: params.x, + clientY: params.y, + screenX: params.x, + screenY: params.y, + deltaX: params.deltaX, + deltaY: params.deltaY, + deltaMode: 0, + ctrlKey: params.ctrlKey, + shiftKey: params.shiftKey, + altKey: params.altKey, + metaKey: params.metaKey, + }); + markAndDispatch(target, event); + this._window.scrollBy(params.deltaX, params.deltaY); + } + + tap(params: TapParams) { + const target = this._deepElementFromPoint(params.x, params.y) || this._document.documentElement; + const init: MouseEventInit = { + bubbles: true, + cancelable: true, + view: this._window, + clientX: params.x, + clientY: params.y, + screenX: params.x, + screenY: params.y, + ctrlKey: params.ctrlKey, + shiftKey: params.shiftKey, + altKey: params.altKey, + metaKey: params.metaKey, + }; + try { + const touch = new Touch({ identifier: 0, target, clientX: params.x, clientY: params.y, screenX: params.x, screenY: params.y, pageX: params.x, pageY: params.y, radiusX: 1, radiusY: 1, rotationAngle: 0, force: 1 }); + markAndDispatch(target, new TouchEvent('touchstart', { ...init, touches: [touch], targetTouches: [touch], changedTouches: [touch] })); + markAndDispatch(target, new TouchEvent('touchend', { ...init, touches: [], targetTouches: [], changedTouches: [touch] })); + } catch { + } + markAndDispatch(target, new MouseEvent('mousedown', { ...init, button: 0, buttons: 1, detail: 1 })); + markAndDispatch(target, new MouseEvent('mouseup', { ...init, button: 0, buttons: 0, detail: 1 })); + markAndDispatch(target, new MouseEvent('click', { ...init, button: 0, buttons: 0, detail: 1 })); + } +} + +export default WebViewInput; diff --git a/packages/playwright-core/src/server/webkit/DEPS.list b/packages/playwright-core/src/server/webkit/DEPS.list index 67e194822dae6..6935c13606896 100644 --- a/packages/playwright-core/src/server/webkit/DEPS.list +++ b/packages/playwright-core/src/server/webkit/DEPS.list @@ -2,6 +2,7 @@ @isomorphic/** @utils/** ../ +../../generated/ ../registry/ node_modules/jpeg-js node_modules/pngjs diff --git a/packages/playwright-core/src/server/webkit/webview/wvInput.ts b/packages/playwright-core/src/server/webkit/webview/wvInput.ts index afce325482fc7..526ba8e297eaa 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvInput.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvInput.ts @@ -30,18 +30,6 @@ function modifierFlags(modifiers: Set) { }; } -// Inlined into the page-side script; descends through open shadow roots so synthetic -// events land on the actual element under the pointer rather than on the shadow host. -const kDeepElementFromPointSrc = `(x, y) => { - let el = document.elementFromPoint(x, y); - while (el && el.shadowRoot) { - const inner = el.shadowRoot.elementFromPoint(x, y); - if (!inner || inner === el) break; - el = inner; - } - return el; -}`; - function buttonToNumber(button: types.MouseButton | 'none'): number { if (button === 'left') return 0; @@ -72,46 +60,22 @@ export class RawKeyboardImpl implements input.RawKeyboard { async keydown(progress: Progress, modifiers: Set, keyName: string, description: input.KeyDescription, autoRepeat: boolean): Promise { const { code, keyCode, key, text, location } = description; - const mods = modifierFlags(modifiers); - const charCode = text ? text.charCodeAt(0) : 0; - const expr = `(() => { - const t = document.activeElement || document.body; - const init = { bubbles: true, cancelable: true, view: window, code: ${JSON.stringify(code)}, key: ${JSON.stringify(key)}, keyCode: ${keyCode}, which: ${keyCode}, location: ${location}, repeat: ${autoRepeat}, ctrlKey: ${mods.ctrlKey}, shiftKey: ${mods.shiftKey}, altKey: ${mods.altKey}, metaKey: ${mods.metaKey} }; - const dispatch = e => { Object.defineProperty(e, '__pwTrustedSynthetic', { value: true }); t.dispatchEvent(e); }; - dispatch(new KeyboardEvent('keydown', init)); - ${text ? `dispatch(new KeyboardEvent('keypress', { ...init, charCode: ${charCode}, keyCode: ${charCode}, which: ${charCode} }));` : ''} - })()`; - await evalInPage(progress, this._session, expr); + const params = { + code, key, keyCode, location, repeat: autoRepeat, + ...modifierFlags(modifiers), + ...(text ? { text } : {}), + }; + await callWebViewInput(progress, this._session, 'keydown', params); } async keyup(progress: Progress, modifiers: Set, keyName: string, description: input.KeyDescription): Promise { const { code, keyCode, key, location } = description; - const mods = modifierFlags(modifiers); - const expr = `(() => { - const t = document.activeElement || document.body; - const event = new KeyboardEvent('keyup', { bubbles: true, cancelable: true, view: window, code: ${JSON.stringify(code)}, key: ${JSON.stringify(key)}, keyCode: ${keyCode}, which: ${keyCode}, location: ${location}, ctrlKey: ${mods.ctrlKey}, shiftKey: ${mods.shiftKey}, altKey: ${mods.altKey}, metaKey: ${mods.metaKey} }); - Object.defineProperty(event, '__pwTrustedSynthetic', { value: true }); - t.dispatchEvent(event); - })()`; - await evalInPage(progress, this._session, expr); + const params = { code, key, keyCode, location, ...modifierFlags(modifiers) }; + await callWebViewInput(progress, this._session, 'keyup', params); } async sendText(progress: Progress, text: string): Promise { - const expr = `(() => { - const text = ${JSON.stringify(text)}; - const t = document.activeElement; - if (t && (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement)) { - const start = t.selectionStart ?? t.value.length; - const end = t.selectionEnd ?? t.value.length; - t.value = t.value.slice(0, start) + text + t.value.slice(end); - const pos = start + text.length; - try { t.setSelectionRange(pos, pos); } catch {} - t.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: false, data: text, inputType: 'insertText' })); - } else if (t && t.isContentEditable) { - document.execCommand('insertText', false, text); - } - })()`; - await evalInPage(progress, this._session, expr); + await callWebViewInput(progress, this._session, 'insertText', text); } } @@ -123,52 +87,40 @@ export class RawMouseImpl implements input.RawMouse { } async move(progress: Progress, x: number, y: number, button: types.MouseButton | 'none', buttons: Set, modifiers: Set, forClick: boolean): Promise { - await this._dispatchMouse(progress, 'mousemove', x, y, buttonToNumber(button), toButtonsMask(buttons), modifiers, 0); + await callWebViewInput(progress, this._session, 'mouseMove', { + x, y, button: buttonToNumber(button), buttons: toButtonsMask(buttons), ...modifierFlags(modifiers), + }); } async down(progress: Progress, x: number, y: number, button: types.MouseButton, buttons: Set, modifiers: Set, clickCount: number): Promise { const buttonCode = buttonToNumber(button); - await this._dispatchMouse(progress, 'mousedown', x, y, buttonCode, toButtonsMask(buttons), modifiers, clickCount); + const buttonsMask = toButtonsMask(buttons); + await this._mouseEvent(progress, 'mousedown', x, y, buttonCode, buttonsMask, modifiers, clickCount); if (button === 'right') - await this._dispatchMouse(progress, 'contextmenu', x, y, buttonCode, toButtonsMask(buttons), modifiers, clickCount); + await this._mouseEvent(progress, 'contextmenu', x, y, buttonCode, buttonsMask, modifiers, clickCount); } async up(progress: Progress, x: number, y: number, button: types.MouseButton, buttons: Set, modifiers: Set, clickCount: number): Promise { const buttonCode = buttonToNumber(button); const buttonsMask = toButtonsMask(buttons); - await this._dispatchMouse(progress, 'mouseup', x, y, buttonCode, buttonsMask, modifiers, clickCount); + await this._mouseEvent(progress, 'mouseup', x, y, buttonCode, buttonsMask, modifiers, clickCount); if (clickCount > 0) { // Non-primary buttons fire 'auxclick'; primary fires 'click'. const clickType = button === 'left' ? 'click' : 'auxclick'; - await this._dispatchMouse(progress, clickType, x, y, buttonCode, buttonsMask, modifiers, clickCount); + await this._mouseEvent(progress, clickType, x, y, buttonCode, buttonsMask, modifiers, clickCount); if (clickCount === 2) - await this._dispatchMouse(progress, 'dblclick', x, y, buttonCode, buttonsMask, modifiers, clickCount); + await this._mouseEvent(progress, 'dblclick', x, y, buttonCode, buttonsMask, modifiers, clickCount); } } async wheel(progress: Progress, x: number, y: number, buttons: Set, modifiers: Set, deltaX: number, deltaY: number): Promise { - const mods = modifierFlags(modifiers); - const expr = `(() => { - const x = ${x}, y = ${y}; - const target = (${kDeepElementFromPointSrc})(x, y) || document.documentElement; - const event = new WheelEvent('wheel', { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, screenX: x, screenY: y, deltaX: ${deltaX}, deltaY: ${deltaY}, deltaMode: 0, ctrlKey: ${mods.ctrlKey}, shiftKey: ${mods.shiftKey}, altKey: ${mods.altKey}, metaKey: ${mods.metaKey} }); - Object.defineProperty(event, '__pwTrustedSynthetic', { value: true }); - target.dispatchEvent(event); - window.scrollBy(${deltaX}, ${deltaY}); - })()`; - await evalInPage(progress, this._session, expr); + await callWebViewInput(progress, this._session, 'wheel', { x, y, deltaX, deltaY, ...modifierFlags(modifiers) }); } - private async _dispatchMouse(progress: Progress, type: string, x: number, y: number, button: number, buttons: number, modifiers: Set, clickCount: number) { - const mods = modifierFlags(modifiers); - const expr = `(() => { - const x = ${x}, y = ${y}; - const target = (${kDeepElementFromPointSrc})(x, y) || document.documentElement; - const event = new MouseEvent(${JSON.stringify(type)}, { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, screenX: x, screenY: y, button: ${button}, buttons: ${buttons}, detail: ${clickCount}, ctrlKey: ${mods.ctrlKey}, shiftKey: ${mods.shiftKey}, altKey: ${mods.altKey}, metaKey: ${mods.metaKey} }); - Object.defineProperty(event, '__pwTrustedSynthetic', { value: true }); - target.dispatchEvent(event); - })()`; - await evalInPage(progress, this._session, expr); + private async _mouseEvent(progress: Progress, type: string, x: number, y: number, button: number, buttons: number, modifiers: Set, clickCount: number) { + await callWebViewInput(progress, this._session, 'mouseEvent', { + type, x, y, button, buttons, clickCount, ...modifierFlags(modifiers), + }); } } @@ -180,27 +132,13 @@ export class RawTouchscreenImpl implements input.RawTouchscreen { } async tap(progress: Progress, x: number, y: number, modifiers: Set) { - const mods = modifierFlags(modifiers); - const expr = `(() => { - const x = ${x}, y = ${y}; - const target = (${kDeepElementFromPointSrc})(x, y) || document.documentElement; - const init = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, screenX: x, screenY: y, ctrlKey: ${mods.ctrlKey}, shiftKey: ${mods.shiftKey}, altKey: ${mods.altKey}, metaKey: ${mods.metaKey} }; - const dispatch = e => { Object.defineProperty(e, '__pwTrustedSynthetic', { value: true }); target.dispatchEvent(e); }; - try { - const touch = new Touch({ identifier: 0, target, clientX: x, clientY: y, screenX: x, screenY: y, pageX: x, pageY: y, radiusX: 1, radiusY: 1, rotationAngle: 0, force: 1 }); - dispatch(new TouchEvent('touchstart', { ...init, touches: [touch], targetTouches: [touch], changedTouches: [touch] })); - dispatch(new TouchEvent('touchend', { ...init, touches: [], targetTouches: [], changedTouches: [touch] })); - } catch {} - dispatch(new MouseEvent('mousedown', { ...init, button: 0, buttons: 1, detail: 1 })); - dispatch(new MouseEvent('mouseup', { ...init, button: 0, buttons: 0, detail: 1 })); - dispatch(new MouseEvent('click', { ...init, button: 0, buttons: 0, detail: 1 })); - })()`; - await evalInPage(progress, this._session, expr); + await callWebViewInput(progress, this._session, 'tap', { x, y, ...modifierFlags(modifiers) }); } } -async function evalInPage(progress: Progress, session: WVSession | undefined, expression: string): Promise { +async function callWebViewInput(progress: Progress, session: WVSession | undefined, method: string, arg: any): Promise { if (!session) throw new Error('Page is not initialized'); + const expression = `window.__pwWebViewInput.${method}(${JSON.stringify(arg)})`; await progress.race(session.send('Runtime.evaluate', { expression, returnByValue: true } as any)); } diff --git a/packages/playwright-core/src/server/webkit/webview/wvPage.ts b/packages/playwright-core/src/server/webkit/webview/wvPage.ts index ac5f195c82530..e043cd4635b0e 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvPage.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvPage.ts @@ -28,6 +28,7 @@ import * as dom from '../../dom'; import { TargetClosedError } from '../../errors'; import { helper } from '../../helper'; import { saveGlobalsSnapshotSource } from '../../javascript'; +import * as rawWebViewInputSource from '../../../generated/webViewInputSource'; import * as network from '../../network'; import { Page, PageBinding } from '../../page'; import { WVSession } from './wvConnection'; @@ -186,8 +187,9 @@ export class WVPage implements PageDelegate { session.sendMayFail('Network.addInterception', { url: '.*', stage: 'request', isRegex: true }), ]); } - // Inject the dialog bridge into the currently-loaded document too — - // bootstrap only applies to future navigations. + // Inject the page-side input dispatcher and dialog bridge into the + // currently-loaded document too — bootstrap only applies to future navigations. + await session.sendMayFail('Runtime.evaluate', { expression: webViewInputBootstrapSource, returnByValue: true } as any); if (this._dialogEndpoint) { await session.sendMayFail('Runtime.evaluate', { expression: dialogBridgeSource(this._dialogEndpoint), @@ -612,6 +614,7 @@ export class WVPage implements PageDelegate { scripts.push('if (!window.GestureEvent) window.GestureEvent = function GestureEvent() {};'); scripts.push(this._publicKeyCredentialScript()); scripts.push(bindingBridgeSource); + scripts.push(webViewInputBootstrapSource); if (this._dialogEndpoint) scripts.push(dialogBridgeSource(this._dialogEndpoint)); scripts.push(...this._page.allInitScripts().map(script => script.source)); @@ -996,6 +999,12 @@ function isLoadedSecurely(url: string, timing: network.ResourceTiming) { } const BINDING_CALL_TAG = '__pw_binding_call__'; +const webViewInputBootstrapSource = `(() => { + const module = {}; + ${rawWebViewInputSource.source} + window.__pwWebViewInput = new (module.exports.default())(window, document); +})()`; + const bindingBridgeSource = ` if (!window['${PageBinding.kBindingName}']) { Object.defineProperty(window, '${PageBinding.kBindingName}', { diff --git a/tests/page/page-keyboard.spec.ts b/tests/page/page-keyboard.spec.ts index 954b590c51bad..56f879afb2fba 100644 --- a/tests/page/page-keyboard.spec.ts +++ b/tests/page/page-keyboard.spec.ts @@ -85,6 +85,19 @@ it('insertText should only emit input event', async ({ page, server }) => { expect(await events.jsonValue()).toEqual(['input']); }); +it('should emit keydown, keypress, textInput and input when typing a character', async ({ page }) => { + await page.setContent(``); + const events = await page.evaluateHandle(() => { + const events: string[] = []; + for (const type of ['keydown', 'keypress', 'textInput', 'input', 'keyup']) + document.querySelector('input').addEventListener(type, () => events.push(type)); + return events; + }); + await page.focus('input'); + await page.keyboard.press('f'); + expect(await events.jsonValue()).toEqual(['keydown', 'keypress', 'textInput', 'input', 'keyup']); +}); + it('should report shiftKey', async ({ page, server, browserName, platform }) => { it.fail(browserName === 'firefox' && platform === 'darwin'); diff --git a/tests/webview/expectations/webkit-webview-page.txt b/tests/webview/expectations/webkit-webview-page.txt index ea66ffa6073dc..352488eb3e263 100644 --- a/tests/webview/expectations/webkit-webview-page.txt +++ b/tests/webview/expectations/webkit-webview-page.txt @@ -106,6 +106,7 @@ page/page-click.spec.ts › should click the button inside an iframe [fail] page/page-click.spec.ts › should not hang when frame is detached [fail] page/page-drag.spec.ts › Drag and drop › should work inside iframe [fail] page/page-evaluate.spec.ts › should throw when frame is detached [fail] +page/page-keyboard.spec.ts › should type emoji into an iframe [fail] page/page-network-request.spec.ts › should work for subframe navigation request [fail] page/page-wait-for-function.spec.ts › should throw when frame is detached [fail] page/page-wait-for-selector-1.spec.ts › page.waitForSelector is shortcut for main frame [fail] @@ -216,14 +217,6 @@ page/page-screenshot.spec.ts › page screenshot animations › should not captu # deserves individual triage. # ============================================================================ page/elementhandle-bounding-box.spec.ts › should handle scroll offset and click [fail] -page/elementhandle-press.spec.ts › should not modify selection when focused [fail] -page/elementhandle-press.spec.ts › should not select existing value [fail] -page/elementhandle-press.spec.ts › should reset selection when not focused [fail] -page/elementhandle-press.spec.ts › should work [fail] -page/elementhandle-type.spec.ts › should not modify selection when focused [fail] -page/elementhandle-type.spec.ts › should not select existing value [fail] -page/elementhandle-type.spec.ts › should reset selection when not focused [fail] -page/elementhandle-type.spec.ts › should work [fail] page/expect-boolean.spec.ts › toBeAttached › with frameLocator [fail] page/expect-boolean.spec.ts › toBeVisible › with frameLocator [fail] page/expect-boolean.spec.ts › toBeVisible › with frameLocator 2 [fail] @@ -247,19 +240,13 @@ page/frame-goto.spec.ts › should continue after client redirect [fail] page/interception.spec.ts › should intercept network activity from worker 2 [fail] page/locator-frame.spec.ts › should click in lazy iframe [fail] page/locator-misc-1.spec.ts › should clear input [fail] -page/locator-misc-2.spec.ts › should press @smoke [fail] page/locator-misc-2.spec.ts › should scroll zero-sized element into view [fail] -page/locator-misc-2.spec.ts › should type [fail] page/locator-query.spec.ts › should allow some, but not all nested frameLocators [fail] page/network-post-data.spec.ts › should return correct postData buffer for utf-8 body [fail] page/network-post-data.spec.ts › should throw on invalid JSON in post data [fail] page/page-add-init-script.spec.ts › init script should run only once in iframe [fail] -page/page-add-locator-handler.spec.ts › should wait for hidden by default 2 [fail] page/page-add-locator-handler.spec.ts › should work when owner frame detaches [fail] page/page-add-locator-handler.spec.ts › should work with locator.hover() [fail] -page/page-add-locator-handler.spec.ts › should work with locator.waitFor [fail] -page/page-add-locator-handler.spec.ts › should work with times: option [fail] -page/page-add-locator-handler.spec.ts › should work › mouseover 1 times [fail] page/page-aria-snapshot-ai.spec.ts › emit generic roles for nodes w/o roles [fail] page/page-aria-snapshot-ai.spec.ts › should include cursor pointer hint [fail] page/page-aria-snapshot-ai.spec.ts › should not generate refs for elements with pointer-events:none [fail] @@ -270,7 +257,6 @@ page/page-autowaiting-basic.spec.ts › should report navigation in the log when page/page-autowaiting-basic.spec.ts › should work with waitForLoadState(load) [fail] page/page-basic.spec.ts › async stacks should work [fail] page/page-basic.spec.ts › has navigator.webdriver set to true [fail] -page/page-basic.spec.ts › page.press should work [fail] page/page-basic.spec.ts › page.url should include hashes [fail] page/page-basic.spec.ts › should have sane user agent [fail] page/page-click-react.spec.ts › should not retarget when element is recycled on hover [fail] @@ -325,28 +311,17 @@ page/page-goto.spec.ts › should send referer of cross-origin URL [fail] page/page-goto.spec.ts › should work with cross-process that fails before committing [fail] page/page-history.spec.ts › page.goBack should work with HistoryAPI [fail] page/page-history.spec.ts › page.reload should work with data url [fail] -page/page-keyboard.spec.ts › locator should pressSequentially with namedKeys [fail] page/page-keyboard.spec.ts › should be able to prevent selectAll [fail] page/page-keyboard.spec.ts › should dispatch a click event on a button when Space gets pressed [fail] page/page-keyboard.spec.ts › should dispatch insertText after context menu was opened [fail] -page/page-keyboard.spec.ts › should expose keyIdentifier in webkit [fail] page/page-keyboard.spec.ts › should move around the selection in a contenteditable [fail] page/page-keyboard.spec.ts › should move to the start of the document [fail] page/page-keyboard.spec.ts › should move with the arrow keys [fail] -page/page-keyboard.spec.ts › should not type canceled events [fail] -page/page-keyboard.spec.ts › should press Enter [fail] -page/page-keyboard.spec.ts › should send a character with ElementHandle.press [fail] +page/page-keyboard.spec.ts › should scroll with PageDown [fail] page/page-keyboard.spec.ts › should support MacOS shortcuts [fail] page/page-keyboard.spec.ts › should support simple copy-pasting [fail] page/page-keyboard.spec.ts › should support simple cut-pasting [fail] page/page-keyboard.spec.ts › should support undo-redo [fail] -page/page-keyboard.spec.ts › should type all kinds of characters [fail] -page/page-keyboard.spec.ts › should type emoji [fail] -page/page-keyboard.spec.ts › should type into a textarea @smoke [fail] -page/page-keyboard.spec.ts › should type repeatedly in contenteditable in shadow dom [fail] -page/page-keyboard.spec.ts › should type repeatedly in contenteditable in shadow dom with nested elements [fail] -page/page-keyboard.spec.ts › should type repeatedly in input in shadow dom [fail] -page/page-keyboard.spec.ts › should type with namedKeys [fail] page/page-localstorage.spec.ts › localStorage.removeItem removes a single item [fail] page/page-mouse.spec.ts › down and up should generate click [fail] page/page-mouse.spec.ts › should click the document @smoke [fail] @@ -408,8 +383,6 @@ page/elementhandle-scroll-into-view.spec.ts › should throw for detached elemen page/elementhandle-scroll-into-view.spec.ts › should wait for display:none to become visible [fail] page/frame-evaluate.spec.ts › should work in iframes that interrupted initial javascript url navigation [fail] page/frame-goto.spec.ts › should reject when frame detaches [fail] -page/locator-misc-2.spec.ts › should pressSequentially [fail] -page/page-add-locator-handler.spec.ts › should throw when handler times out [fail] page/page-aria-snapshot-ai.spec.ts › should snapshot a locator inside an iframe [fail] page/page-click-react.spec.ts › should not retarget when element changes on hover [fail] page/page-click.spec.ts › should not wait with noAutoWaiting 2 [fail] @@ -465,8 +438,6 @@ page/page-history.spec.ts › page.goForward during renderer-initiated navigatio page/page-history.spec.ts › page.reload should work on a page with a hash [fail] page/page-history.spec.ts › page.reload should work with cross-origin redirect [fail] page/page-history.spec.ts › should reload proper page [fail] -page/page-keyboard.spec.ts › should scroll with PageDown [fail] -page/page-keyboard.spec.ts › should work after a cross origin navigation [fail] page/page-leaks.spec.ts › click should not leak [fail] page/page-leaks.spec.ts › expect should not leak [fail] page/page-leaks.spec.ts › fill should not leak [fail] @@ -685,7 +656,6 @@ page/page-goto.spec.ts › should capture cross-process iframe navigation reques page/page-goto.spec.ts › should capture iframe navigation request [fail] page/page-goto.spec.ts › should wait for load when iframe attaches and detaches [fail] page/page-goto.spec.ts › should work with lazy loading iframes [fail] -page/page-keyboard.spec.ts › should type emoji into an iframe [fail] page/page-network-idle.spec.ts › should wait for networkidle from the popup [fail] page/page-network-idle.spec.ts › should wait for networkidle when iframe attaches and detaches [fail] page/page-network-idle.spec.ts › should wait for networkidle when navigating iframe [fail] @@ -770,7 +740,6 @@ page/network-post-data.spec.ts › should get post data for file/blob [fail] page/network-post-data.spec.ts › should get post data for navigator.sendBeacon api calls [fail] page/page-add-init-script.spec.ts › should remove init script after dispose [fail] page/page-add-init-script.spec.ts › should work after a cross origin navigation [fail] -page/page-add-locator-handler.spec.ts › should work [fail] page/page-autowaiting-basic.spec.ts › should await cross-process navigation when clicking anchor [fail] page/page-autowaiting-basic.spec.ts › should await form-get on click [fail] page/page-autowaiting-no-hang.spec.ts › clicking in the middle of navigation that commits [fail] diff --git a/utils/generate_injected.js b/utils/generate_injected.js index e661df9d5e7f0..022e069a83b97 100644 --- a/utils/generate_injected.js +++ b/utils/generate_injected.js @@ -74,6 +74,12 @@ const injectedScripts = [ path.join(ROOT, 'packages', 'playwright-core', 'src', 'generated'), true, ], + [ + path.join(ROOT, 'packages', 'injected', 'src', 'webview', 'webViewInput.ts'), + path.join(ROOT, 'packages', 'injected', 'lib'), + path.join(ROOT, 'packages', 'playwright-core', 'src', 'generated'), + true, + ], [ path.join(ROOT, 'packages', 'playwright-ct-core', 'src', 'injected', 'index.ts'), path.join(ROOT, 'packages', 'playwright-ct-core', 'lib', 'injected', 'packed'),