diff --git a/.github/workflows/infra.yml b/.github/workflows/infra.yml index 40f281c7d680c..1767072f8c6a5 100644 --- a/.github/workflows/infra.yml +++ b/.github/workflows/infra.yml @@ -5,10 +5,10 @@ on: branches: - main - release-* + # Disabled while iterating on tests_webview_gtk.yml; re-enable by reverting. pull_request: - branches: - - main - - release-* + paths: + - '__webview_gtk_pr_disabled__' env: ELECTRON_SKIP_BINARY_DOWNLOAD: 1 diff --git a/.github/workflows/tests_components.yml b/.github/workflows/tests_components.yml index 585796c280070..0c7c7fd3b5883 100644 --- a/.github/workflows/tests_components.yml +++ b/.github/workflows/tests_components.yml @@ -5,19 +5,10 @@ on: branches: - main - release-* + # Disabled while iterating on tests_webview_gtk.yml; re-enable by reverting. pull_request: - paths-ignore: - - 'browser_patches/**' - - 'docs/**' - - 'packages/extension/**' - - 'packages/playwright-core/src/server/bidi/**' - - 'packages/playwright-core/src/tools/**' - - 'tests/bidi/**' - - 'tests/extension/**' - - 'tests/mcp/**' - branches: - - main - - release-* + paths: + - '__webview_gtk_pr_disabled__' env: FORCE_COLOR: 1 diff --git a/.github/workflows/tests_mcp.yml b/.github/workflows/tests_mcp.yml index 3e1c7e7cbb5cb..dba401392485c 100644 --- a/.github/workflows/tests_mcp.yml +++ b/.github/workflows/tests_mcp.yml @@ -5,17 +5,10 @@ on: branches: - main - release-* + # Disabled while iterating on tests_webview_gtk.yml; re-enable by reverting. pull_request: - paths-ignore: - - 'browser_patches/**' - - 'docs/**' - - 'packages/extension/**' - - 'packages/playwright-core/src/server/bidi/**' - - 'tests/bidi/**' - - 'tests/extension/**' - branches: - - main - - release-* + paths: + - '__webview_gtk_pr_disabled__' concurrency: # For pull requests, cancel all currently-running jobs for this workflow diff --git a/.github/workflows/tests_primary.yml b/.github/workflows/tests_primary.yml index 142cab8343aff..0604590f0ed7c 100644 --- a/.github/workflows/tests_primary.yml +++ b/.github/workflows/tests_primary.yml @@ -5,19 +5,10 @@ on: branches: - main - release-* + # Disabled while iterating on tests_webview_gtk.yml; re-enable by reverting. pull_request: - paths-ignore: - - 'browser_patches/**' - - 'docs/**' - - 'packages/extension/**' - - 'packages/playwright-core/src/server/bidi/**' - - 'packages/playwright-core/src/tools/**' - - 'tests/bidi/**' - - 'tests/extension/**' - - 'tests/mcp/**' - branches: - - main - - release-* + paths: + - '__webview_gtk_pr_disabled__' concurrency: # For pull requests, cancel all currently-running jobs for this workflow diff --git a/.github/workflows/tests_webview_gtk.yml b/.github/workflows/tests_webview_gtk.yml new file mode 100644 index 0000000000000..f6d9d7ac82cb6 --- /dev/null +++ b/.github/workflows/tests_webview_gtk.yml @@ -0,0 +1,76 @@ +name: "tests WebView (WebKitGTK)" + +on: + workflow_dispatch: + pull_request: + paths: + - 'tests/webview/**' + - 'packages/playwright-core/src/server/webkit/webview/**' + - '.github/workflows/tests_webview_gtk.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + FORCE_COLOR: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + # The webview fixture launches this binary (provided by libwebkitgtk-6.0-4). + PW_WEBVIEW_PROXY_BASE: http://127.0.0.1:9222 + +jobs: + test_webview_gtk: + name: "WebView on WebKitGTK (${{ matrix.shard }}/4)" + runs-on: ubuntu-24.04 + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3, 4] + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install WebKitGTK MiniBrowser + run: | + echo "::group::apt-get install libwebkitgtk-6.0-4" + sudo apt-get update + # libwebkitgtk-6.0-4 ships /usr/lib/x86_64-linux-gnu/webkitgtk-6.0/MiniBrowser + # and the engine; xvfb gives the GTK app a display, dbus-x11 a session bus. + sudo apt-get install -y libwebkitgtk-6.0-4 xvfb dbus-x11 + ls -l /usr/lib/x86_64-linux-gnu/webkitgtk-6.0/MiniBrowser + echo "::endgroup::" + + - name: npm ci + run: | + echo "::group::npm ci" + npm ci + echo "::endgroup::" + + - name: npm run build + run: | + echo "::group::npm run build" + npm run build + echo "::endgroup::" + + - name: Run WebView tests + run: | + echo "::group::Test run (shard ${{ matrix.shard }}/4)" + # GTK needs a display (xvfb) and a session bus (dbus-run-session). The + # fixture launches MiniBrowser itself and disables the WebKit sandbox. + # Scoped to a curated subset for now; widen to the full config later. + xvfb-run -a dbus-run-session -- \ + npx playwright test page-check page-click page-goto page-keyboard \ + --config tests/webview/playwright.config.ts --shard=${{ matrix.shard }}/4 + echo "::endgroup::" + + - name: Upload artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: webview-gtk-logs-${{ matrix.shard }} + path: | + ${{ github.workspace }}/test-results/** + if-no-files-found: ignore diff --git a/.github/workflows/tests_webview_simulator.yml b/.github/workflows/tests_webview_simulator.yml index 607e5ddd0a5a7..763f695936ad5 100644 --- a/.github/workflows/tests_webview_simulator.yml +++ b/.github/workflows/tests_webview_simulator.yml @@ -2,11 +2,10 @@ name: "tests WebView (iOS Simulator)" on: workflow_dispatch: + # Disabled while iterating on tests_webview_gtk.yml; re-enable by reverting. pull_request: paths: - - 'tests/webview/**' - - 'packages/playwright-core/src/server/webkit/webview/**' - - '.github/workflows/tests_webview_simulator.yml' + - '__webview_gtk_pr_disabled__' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} diff --git a/packages/playwright-core/src/server/webkit/webview/wvBrowser.ts b/packages/playwright-core/src/server/webkit/webview/wvBrowser.ts index 509890ce30637..c0e4dfaf59375 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvBrowser.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvBrowser.ts @@ -56,16 +56,65 @@ function deriveProxyBase(endpointURL: string): string { } function pageIdFromWsUrl(wsUrl: string): string { - const m = /\/devtools\/page\/([^/]+)/.exec(wsUrl); - return m ? m[1] : wsUrl; + // ios_webkit_debug_proxy exposes tabs as /devtools/page/. + const cdp = /\/devtools\/page\/([^/]+)/.exec(wsUrl); + if (cdp) + return cdp[1]; + // WebKitGTK/WPE remote inspector HTTP server exposes targets as + // /socket///. + const gtk = /\/socket\/(\d+)\/(\d+)\//.exec(wsUrl); + if (gtk) + return `${gtk[1]}/${gtk[2]}`; + return wsUrl; +} + +function decodeHtmlEntities(text: string): string { + return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') + .replace(/"/g, '"').replace(/'/g, '\''); +} + +// WebKitGTK and WPE expose the remote inspector through an HTTP server (enabled +// with WEBKIT_INSPECTOR_HTTP_SERVER=host:port). Unlike ios_webkit_debug_proxy +// there is no /json endpoint: the target list is an HTML page at `/`, with each +// inspectable target carrying a `/socket///` path +// that upgrades to a WebSocket speaking the same protocol as the iOS tabs. +function parseGtkTargetListPage(html: string, proxyBase: string): ProxyTab[] { + const wsProto = new URL(proxyBase).protocol === 'https:' ? 'wss:' : 'ws:'; + const host = new URL(proxyBase).host; + const tabs: ProxyTab[] = []; + const rowRegex = /
([\s\S]*?)<\/div>
([\s\S]*?)<\/div>[\s\S]*?\/socket\/(\d+)\/(\d+)\/(\w+)/g; + let match: RegExpExecArray | null; + while ((match = rowRegex.exec(html))) { + const [, name, url, connectionID, targetID, type] = match; + // Only page targets are driveable; skip ServiceWorker/JavaScript targets. + if (type !== 'WebPage') + continue; + tabs.push({ + title: decodeHtmlEntities(name), + url: decodeHtmlEntities(url), + webSocketDebuggerUrl: `${wsProto}//${host}/socket/${connectionID}/${targetID}/${type}`, + }); + } + return tabs; } async function listTabs(proxyBase: string, headers: { [key: string]: string }): Promise { - const res = await fetch(`${proxyBase}/json`, { headers }); - if (!res.ok) - throw new Error(`ios_webkit_debug_proxy ${proxyBase}/json returned ${res.status}`); - const data = await res.json() as ProxyTab[]; - return data.filter(t => !!t.webSocketDebuggerUrl); + // ios_webkit_debug_proxy exposes a CDP-style JSON listing. + const jsonRes = await fetch(`${proxyBase}/json`, { headers }); + if (jsonRes.ok) { + const text = await jsonRes.text(); + try { + const data = JSON.parse(text) as ProxyTab[]; + if (Array.isArray(data)) + return data.filter(t => !!t.webSocketDebuggerUrl); + } catch { + // Not a JSON listing — fall through to the WebKitGTK/WPE HTML listing. + } + } + const htmlRes = await fetch(`${proxyBase}/`, { headers }); + if (!htmlRes.ok) + throw new Error(`Remote inspector at ${proxyBase} returned ${jsonRes.status} for /json and ${htmlRes.status} for /`); + return parseGtkTargetListPage(await htmlRes.text(), proxyBase); } // Local WebSocket-backed transport: defers opening the socket until `open()` @@ -204,7 +253,7 @@ export class WVBrowser extends Browser { } else { await this._syncTabs(); if (!this._tabs.size) - throw new Error(`No Mobile Safari tabs found at ${this._proxyBase}/json — open Safari first.`); + throw new Error(`No inspectable tabs found at ${this._proxyBase} — open a page in the browser first.`); } this._page = this._firstTab().page; } diff --git a/tests/webview/expectations/webkit-webview-gtk-page.txt b/tests/webview/expectations/webkit-webview-gtk-page.txt new file mode 100644 index 0000000000000..4683937d22af1 --- /dev/null +++ b/tests/webview/expectations/webkit-webview-gtk-page.txt @@ -0,0 +1,838 @@ +# WebView (WebKitGTK / WPE) expected failures. +# +# Tests in this file are skipped against the webkit-webview-gtk-page project. Each +# section explains the root cause; once the cause is addressed, tests in that +# section should be re-run and removed from the file. +# +# Seeded from the iOS Simulator baseline (webkit-webview-page.txt); the two share +# the same WebKit remote inspector limitations. Refine from a WebKitGTK CI run: +# drop entries that pass and add GTK-specific failures. + +# ============================================================================ +# viewport-emulation (56 tests) +# Viewport emulation is not available over the stock iOS Web Inspector +# protocol. Mobile Safari's viewport is bound to the simulator window and +# neither Emulation.setDeviceMetricsOverride nor Page.setScreenSizeOverride +# are exposed on Cocoa builds — see wvPage.updateEmulatedViewportSize. +# ============================================================================ +page/elementhandle-bounding-box.spec.ts › should force a layout [fail] +page/elementhandle-bounding-box.spec.ts › should get frame box [fail] +page/elementhandle-bounding-box.spec.ts › should handle nested frames [fail] +page/elementhandle-bounding-box.spec.ts › should work [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › path option should create subdirectories [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should capture full element when larger than viewport [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should capture full element when larger than viewport in parallel [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should prefer type over extension [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should scroll 15000px into view [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should scroll element into view [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should take into account padding and border [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should take screenshot of disabled button [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should wait for element to stop moving [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should wait for visible [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should work [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should work when main world busts JSON.stringify [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should work with a rotated element [fail] +page/expect-matcher-result.spec.ts › toHaveScreenshot should populate matcherResult [fail] +page/locator-misc-2.spec.ts › should return bounding box [fail] +page/locator-misc-2.spec.ts › should take screenshot [fail] +page/page-add-locator-handler.spec.ts › should work with toHaveScreenshot [fail] +page/page-click-timeout-4.spec.ts › should fail to click the button behind a large header after scrolling around [fail] +page/page-click.spec.ts › should click a button that is overlaid by a permission popup [fail] +page/page-click.spec.ts › should click the button behind position:absolute header [fail] +page/page-click.spec.ts › should click the button behind sticky header [fail] +page/page-click.spec.ts › should click the button with fixed position inside an iframe [fail] +page/page-screenshot.spec.ts › page screenshot animations › should wait for fonts to load [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should hide elements based on attr [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should mask in parallel [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should mask multiple elements [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should remove elements based on attr [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should remove mask after screenshot [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should work [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should work when mask color is not pink #F0F [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should work with elementhandle [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should work with locator [fail] +page/page-screenshot.spec.ts › page screenshot › path option should create subdirectories [fail] +page/page-screenshot.spec.ts › page screenshot › path option should detect jpeg [fail] +page/page-screenshot.spec.ts › page screenshot › path option should work [fail] +page/page-screenshot.spec.ts › page screenshot › should allow transparency [fail] +page/page-screenshot.spec.ts › page screenshot › should clip elements to the viewport [fail] +page/page-screenshot.spec.ts › page screenshot › should clip rect [fail] +page/page-screenshot.spec.ts › page screenshot › should clip rect with fullPage [fail] +page/page-screenshot.spec.ts › page screenshot › should render white background on jpeg file [fail] +page/page-screenshot.spec.ts › page screenshot › should restore viewport after fullPage screenshot [fail] +page/page-screenshot.spec.ts › page screenshot › should run in parallel [fail] +page/page-screenshot.spec.ts › page screenshot › should take fullPage screenshots [fail] +page/page-screenshot.spec.ts › page screenshot › should take fullPage screenshots and mask elements outside of it [fail] +page/page-screenshot.spec.ts › page screenshot › should take fullPage screenshots during navigation [fail] +page/page-screenshot.spec.ts › page screenshot › should throw on clip outside the viewport [fail] +page/page-screenshot.spec.ts › page screenshot › should work @smoke [fail] +page/page-screenshot.spec.ts › page screenshot › should work for canvas [fail] +page/page-screenshot.spec.ts › page screenshot › should work for translateZ [fail] +page/page-screenshot.spec.ts › page screenshot › should work for webgl [fail] +page/page-screenshot.spec.ts › page screenshot › should work while navigating [fail] +page/page-screenshot.spec.ts › page screenshot › should work with Array deleted [fail] + +# ============================================================================ +# frame-element-and-ownership (38 tests) +# elementHandle.contentFrame / elementHandle.ownerFrame / frame.frameElement +# are not implemented in wvPage — see the throwing stubs in wvPage.ts. +# ============================================================================ +page/elementhandle-content-frame.spec.ts › should return null for document.documentElement [fail] +page/elementhandle-content-frame.spec.ts › should return null for non-iframes [fail] +page/elementhandle-content-frame.spec.ts › should work [fail] +page/elementhandle-content-frame.spec.ts › should work for cross-frame evaluations [fail] +page/elementhandle-content-frame.spec.ts › should work for cross-process iframes [fail] +page/elementhandle-owner-frame.spec.ts › should work [fail] +page/elementhandle-owner-frame.spec.ts › should work for cross-frame evaluations [fail] +page/elementhandle-owner-frame.spec.ts › should work for cross-process iframes [fail] +page/elementhandle-owner-frame.spec.ts › should work for detached elements [fail] +page/elementhandle-owner-frame.spec.ts › should work for document [fail] +page/elementhandle-owner-frame.spec.ts › should work for iframe elements [fail] +page/frame-evaluate.spec.ts › should be isolated between frames [fail] +page/frame-evaluate.spec.ts › should have different execution contexts [fail] +page/frame-evaluate.spec.ts › should not allow cross-frame element handles when frames do not script each other [fail] +page/frame-evaluate.spec.ts › should throw for detached frames [fail] +page/frame-frame-element.spec.ts › should throw when detached [fail] +page/frame-frame-element.spec.ts › should work @smoke [fail] +page/frame-frame-element.spec.ts › should work inside closed shadow root [fail] +page/frame-frame-element.spec.ts › should work inside declarative shadow root [fail] +page/frame-frame-element.spec.ts › should work with contentFrame [fail] +page/frame-frame-element.spec.ts › should work with frameset [fail] +page/frame-goto.spec.ts › should return matching responses [fail] +page/frame-hierarchy.spec.ts › should report different frame instance when frame re-attaches [fail] +page/frame-hierarchy.spec.ts › should report frame.name() [fail] +page/frame-hierarchy.spec.ts › should report frame.parent() [fail] +page/frame-hierarchy.spec.ts › should send events when frames are manipulated dynamically [fail] +page/page-click.spec.ts › should click button inside frameset [fail] +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] +page/page-wait-for-selector-1.spec.ts › should run in specified frame [fail] +page/page-wait-for-selector-1.spec.ts › should throw when frame is detached [fail] +page/page-wait-for-selector-2.spec.ts › should run in specified frame xpath [fail] +page/page-wait-for-selector-2.spec.ts › should throw when frame is detached xpath [fail] + +# ============================================================================ +# expose-function (2 tests) +# page.exposeFunction / exposeBinding are not implemented — the bootstrap +# script path needs to be extended with a binding bridge over Runtime.evaluate. +# ============================================================================ +page/elementhandle-screenshot.spec.ts › element screenshot › should not issue resize event [fail] +page/locator-misc-1.spec.ts › should focus and blur a button [fail] + +# ============================================================================ +# service-worker (3 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'). Passes in isolation but fails in the full run +# because service-worker registration persists across tests. +# ============================================================================ +page/interception.spec.ts › should intercept after a service worker [fail] +page/page-event-request.spec.ts › should report requests and responses handled by service worker with routing [fail] +page/page-goto.spec.ts › should be able to navigate to a page controlled by service worker [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] + +# ============================================================================ +# 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] + +# ============================================================================ +# 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] + +# ============================================================================ +# 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) +# Element-not-attached errors during action retries: getContentQuads / +# scrollRectIntoViewIfNeeded evaluate in main world without the utility-world +# shielding, so DOM mutations between checks can detach the handle. Needs the +# same retry-with-fresh-handle pattern that wkPage uses. +# ============================================================================ +page/elementhandle-click.spec.ts › should double click the button [fail] +page/elementhandle-click.spec.ts › should work with Node removed [fail] +page/elementhandle-misc.spec.ts › should fill input [fail] +page/elementhandle-misc.spec.ts › should hover [fail] +page/elementhandle-misc.spec.ts › should hover when Node is removed [fail] +page/elementhandle-scroll-into-view.spec.ts › should work @smoke [fail] +page/elementhandle-select-text.spec.ts › should wait for visible [fail] +page/locator-misc-2.spec.ts › should scroll into view [fail] +page/page-keyboard.spec.ts › should handle selectAll [fail] +page/page-screenshot.spec.ts › page screenshot animations › should not capture infinite css animation [fail] +page/page-screenshot.spec.ts › page screenshot animations › should not capture infinite web animations [fail] + +# ============================================================================ +# assertion-mismatch (179 tests) +# Real behavior differences between Mobile Safari and Playwright's expectations +# (user-agent strings, layout metrics, screenshot expectations, etc.). Each +# deserves individual triage. +# ============================================================================ +page/elementhandle-bounding-box.spec.ts › should handle scroll offset and click [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] +page/expect-misc.spec.ts › toBeInViewport › should respect ratio option [fail] +page/expect-misc.spec.ts › toBeInViewport › should work [fail] +page/expect-misc.spec.ts › toHaveURL › fail string [fail] +page/expect-misc.spec.ts › toHaveURL › fail with negative predicate [fail] +page/expect-misc.spec.ts › toHaveURL › fail with positive predicate [fail] +page/expect-misc.spec.ts › toHaveURL › pass [fail] +page/expect-misc.spec.ts › toHaveURL › resolve predicate after retries [fail] +page/expect-misc.spec.ts › toHaveURL › resolve predicate on initial call [fail] +page/expect-misc.spec.ts › toHaveURL › support ignoreCase [fail] +page/expect-timeout.spec.ts › should timeout during first locator handler check [fail] +page/expect-to-have-accessible.spec.ts › toHaveAccessibleDescription [fail] +page/expect-to-have-text.spec.ts › toHaveText with array › fail [fail] +page/frame-evaluate.spec.ts › should dispose context on cross-origin navigation [fail] +page/frame-evaluate.spec.ts › should dispose context on navigation [fail] +page/frame-evaluate.spec.ts › should have correct execution contexts @smoke [fail] +page/frame-evaluate.spec.ts › should work in iframes that failed initial navigation [fail] +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 scroll zero-sized element into view [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 work when owner frame detaches [fail] +page/page-add-locator-handler.spec.ts › should work with locator.hover() [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] +page/page-aria-snapshot-ai.spec.ts › should not nest cursor pointer hints [fail] +page/page-autowaiting-basic.spec.ts › should await form-post on click [fail] +page/page-autowaiting-basic.spec.ts › should await navigation when clicking anchor [fail] +page/page-autowaiting-basic.spec.ts › should report navigation in the log when clicking anchor [fail] +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.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] +page/page-click-scroll.spec.ts › should scroll into view span element [fail] +page/page-click-timeout-2.spec.ts › should timeout waiting for display:none to be gone [fail] +page/page-click-timeout-2.spec.ts › should timeout waiting for visibility:hidden to be gone [fail] +page/page-click-timeout-3.spec.ts › should report wrong hit target subtree [fail] +page/page-click-timeout-3.spec.ts › should timeout waiting for hit target [fail] +page/page-click-timeout-4.spec.ts › should timeout waiting for stable position [fail] +page/page-click.spec.ts › ensure events are dispatched in the individual tasks [fail] +page/page-click.spec.ts › should click a button in scrolling container with offset [fail] +page/page-click.spec.ts › should click on checkbox input and toggle [fail] +page/page-click.spec.ts › should click the button with em border with offset [fail] +page/page-click.spec.ts › should click the button with px border with offset [fail] +page/page-click.spec.ts › should fire contextmenu event on right click in correct order [fail] +page/page-click.spec.ts › should set PointerEvent.pressure on pointerdown [fail] +page/page-click.spec.ts › should set PointerEvent.pressure on pointermove [fail] +page/page-dispatchevent.spec.ts › should dispatch absolute device orientation event [fail] +page/page-drag.spec.ts › Drag and drop › should be able to drag the mouse in a frame [fail] +page/page-drag.spec.ts › Drag and drop › should cancel on escape [fail] +page/page-drag.spec.ts › Drag and drop › should work @smoke [fail] +page/page-drag.spec.ts › Drag and drop › should work if the drag event is captured but not canceled [fail] +page/page-drag.spec.ts › Drag and drop › should work with locators [fail] +page/page-drag.spec.ts › Drag and drop › should work with the helper method [fail] +page/page-drag.spec.ts › should handle custom dataTransfer [fail] +page/page-emulate-media.spec.ts › should emulate reduced motion [fail] +page/page-emulate-media.spec.ts › should keep reduced motion and color emulation after reload [fail] +page/page-expose-function.spec.ts › should fail with busted Array.prototype.toJSON [fail] +page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - date [fail] +page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - datetime-local [fail] +page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - time [fail] +page/page-fill.spec.ts › should be able to clear using fill() [fail] +page/page-fill.spec.ts › should be able to fill input[type=number] with empty string [fail] +page/page-fill.spec.ts › should fill date input after clicking [fail] +page/page-fill.spec.ts › should fill datetime-local input [fail] +page/page-fill.spec.ts › should fill month input [fail] +page/page-fill.spec.ts › should fill time input [fail] +page/page-fill.spec.ts › should fill week input [fail] +page/page-focus.spec.ts › keeps focus on element when attempting to focus a non-focusable element [fail] +page/page-focus.spec.ts › should traverse focus [fail] +page/page-focus.spec.ts › should traverse focus in all directions [fail] +page/page-focus.spec.ts › should traverse only form elements [fail] +page/page-goto.spec.ts › should disable timeout when its set to 0 [fail] +page/page-goto.spec.ts › should fail when canceled by another navigation [fail] +page/page-goto.spec.ts › should fail when navigating to bad url [fail] +page/page-goto.spec.ts › should fail when replaced by another navigation [fail] +page/page-goto.spec.ts › should override referrer-policy [fail] +page/page-goto.spec.ts › should return url with basic auth info [fail] +page/page-goto.spec.ts › should send referer [fail] +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 › 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 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 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-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] +page/page-mouse.spec.ts › should dblclick the div [fail] +page/page-mouse.spec.ts › should report correct pointerType property [fail] +page/page-network-request.spec.ts › should get the same headers as the server [fail] +page/page-network-request.spec.ts › should get the same headers as the server CORS [fail] +page/page-network-request.spec.ts › should not return allHeaders() until they are available [fail] +page/page-network-request.spec.ts › should parse the data if content-type is application/x-www-form-urlencoded [fail] +page/page-network-request.spec.ts › should parse the data if content-type is application/x-www-form-urlencoded; charset=UTF-8 [fail] +page/page-network-request.spec.ts › should report all cookies in one header [fail] +page/page-network-request.spec.ts › should report raw headers [fail] +page/page-network-request.spec.ts › should return postData [fail] +page/page-network-request.spec.ts › should work with binary post data [fail] +page/page-network-request.spec.ts › should work with binary post data and interception [fail] +page/page-network-sizes.spec.ts › should handle redirects [fail] +page/page-network-sizes.spec.ts › should have the correct responseBodySize for chunked request [fail] +page/page-network-sizes.spec.ts › should set bodySize and headersSize [fail] +page/page-network-sizes.spec.ts › should work with 200 status code [fail] +page/page-network-sizes.spec.ts › should work with 401 status code [fail] +page/page-network-sizes.spec.ts › should work with 404 status code [fail] +page/page-network-sizes.spec.ts › should work with 500 status code [fail] +page/page-request-continue.spec.ts › continue should not override cookie [fail] +page/page-request-continue.spec.ts › continue should not propagate cookie override to redirects [fail] +page/page-request-continue.spec.ts › propagate headers same origin redirect [fail] +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 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] +page/page-route.spec.ts › should fail navigation when aborting main resource [fail] +page/page-route.spec.ts › should not auto-intercept non-preflight OPTIONS with network interception [fail] +page/page-route.spec.ts › should not override cookie header [fail] +page/page-route.spec.ts › should send referer [fail] +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 stop animations that happen right before screenshot [fail] +page/page-screenshot.spec.ts › page screenshot › should capture blinking caret if explicitly asked for [fail] +page/page-set-input-files.spec.ts › should throw an error if the file does not exist [fail] +page/page-set-input-files.spec.ts › should throw if a directory and files are passed [fail] +page/page-set-input-files.spec.ts › should throw when uploading a file in a directory upload input [fail] +page/page-set-input-files.spec.ts › should throw when uploading a folder in a normal file upload input [fail] +page/page-set-input-files.spec.ts › should upload a folder and throw for multiple directories [fail] +page/page-wait-for-navigation.spec.ts › should respect timeout [fail] +page/page-wait-for-navigation.spec.ts › should work with DOM history.back()/history.forward() [fail] +page/page-wait-for-selector-1.spec.ts › should report logs while waiting for visible [fail] +page/page-wait-for-url.spec.ts › should work with DOM history.back()/history.forward() [fail] +page/wheel.spec.ts › should work when the event is canceled [fail] + +# ============================================================================ +# misc (99 tests) +# Failures that have not been grouped yet — eyeball before extending the +# categories above. +# ============================================================================ +page/elementhandle-screenshot.spec.ts › element screenshot › should work for an element with an offset [fail] +page/elementhandle-screenshot.spec.ts › element screenshot › should work for an element with fractional dimensions [fail] +page/elementhandle-scroll-into-view.spec.ts › should throw for detached element [fail] +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/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] +page/page-emulate-media.spec.ts › should report hover and fine pointer for desktop [fail] +page/page-evaluate.spec.ts › should await promise from popup [fail] +page/page-evaluate.spec.ts › should evaluate exception [fail] +page/page-evaluate.spec.ts › should evaluate exception with a function on the stack [fail] +page/page-evaluate.spec.ts › should not leak handles [fail] +page/page-evaluate.spec.ts › should not leak utility script [fail] +page/page-evaluate.spec.ts › should respect use strict expression [fail] +page/page-evaluate.spec.ts › should throw error with detailed information on exception inside promise [fail] +page/page-evaluate.spec.ts › should work even when JSON is set to null [fail] +page/page-evaluate.spec.ts › should work with CSP [fail] +page/page-evaluate.spec.ts › should work with new Function() and CSP [fail] +page/page-evaluate.spec.ts › should work with non-strict expressions [fail] +page/page-evaluate.spec.ts › should work with overridden Object.defineProperty [fail] +page/page-evaluate.spec.ts › should work with overridden globalThis.Window/Document/Node [fail] +page/page-event-network.spec.ts › should resolve responses after a navigation [fail] +page/page-event-pageerror.spec.ts › should contain sourceURL [fail] +page/page-event-pageerror.spec.ts › should fire [fail] +page/page-event-request.spec.ts › main resource xhr should have type xhr [fail] +page/page-event-request.spec.ts › should finish 204 request [fail] +page/page-event-request.spec.ts › should fire requestfailed when intercepting race [fail] +page/page-event-request.spec.ts › should report navigation requests and responses handled by service worker with routing [fail] +page/page-event-request.spec.ts › should return response body when Cross-Origin-Opener-Policy is set [fail] +page/page-expose-function.spec.ts › should work after cross origin navigation [fail] +page/page-expose-function.spec.ts › should work with busted Array.prototype.map/push [fail] +page/page-expose-function.spec.ts › should work with overridden console object [fail] +page/page-fill.spec.ts › should throw on incorrect date [fail] +page/page-fill.spec.ts › should throw on incorrect month [fail] +page/page-fill.spec.ts › should throw on incorrect time [fail] +page/page-fill.spec.ts › should throw on incorrect week [fail] +page/page-focus.spec.ts › clicking checkbox should activate it [fail] +page/page-focus.spec.ts › tab should cycle between document elements and browser [fail] +page/page-focus.spec.ts › tab should cycle between single input and browser [fail] +page/page-goto.spec.ts › js redirect overrides url bar navigation [fail] +page/page-goto.spec.ts › should fail when main resources failed to load [fail] +page/page-goto.spec.ts › should fail when navigating and show the url at the error message [fail] +page/page-goto.spec.ts › should fail when navigating to bad SSL [fail] +page/page-goto.spec.ts › should fail when navigating to bad SSL after redirects [fail] +page/page-goto.spec.ts › should fail when server returns 204 [fail] +page/page-goto.spec.ts › should work cross-process [fail] +page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy [fail] +page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy after redirect [fail] +page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy and interception [fail] +page/page-goto.spec.ts › should work with anchor navigation [fail] +page/page-goto.spec.ts › should work with file URL [fail] +page/page-goto.spec.ts › should work with file URL with subframes [fail] +page/page-history.spec.ts › page.goBack should work for file urls [fail] +page/page-history.spec.ts › page.goForward during renderer-initiated navigation [fail] +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-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] +page/page-leaks.spec.ts › waitFor should not leak [fail] +page/page-localstorage.spec.ts › storage methods are scoped to the current origin [fail] +page/page-mouse.spec.ts › should pointerdown the div with a custom button [fail] +page/page-mouse.spec.ts › should select the text with mouse [fail] +page/page-navigation.spec.ts › should work with _blank target in form [fail] +page/page-network-request.spec.ts › should handle mixed-content blocked requests [fail] +page/page-network-request.spec.ts › should report raw response headers in redirects [fail] +page/page-network-request.spec.ts › should return multipart/form-data [fail] +page/page-network-request.spec.ts › should return navigation bit [fail] +page/page-network-response.spec.ts › should bypass disk cache when context interception is enabled [fail] +page/page-network-response.spec.ts › should provide a Response with a file URL [fail] +page/page-network-response.spec.ts › should return body for prefetch script [fail] +page/page-network-response.spec.ts › should return set-cookie header after route.fulfill [fail] +page/page-request-continue.spec.ts › should continue preload link requests [fail] +page/page-request-continue.spec.ts › should not delete the origin header [fail] +page/page-request-continue.spec.ts › should not forward Host header on cross-origin redirect [fail] +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-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] +page/page-select-option.spec.ts › should select only first option [fail] +page/page-set-input-files.spec.ts › should upload large file [fail] +page/page-set-input-files.spec.ts › should upload large file with relative path [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of newPage [fail] +page/page-wait-for-url.spec.ts › should work on frame [fail] + +# ============================================================================ +# subframe-navigation (2 tests) +# Stock WebKit RDP has no Page.navigate; navigateFrame is implemented via +# Runtime.evaluate(window.location.href=...), which only works on the main +# frame — see wvPage.navigateFrame. +# ============================================================================ +page/frame-goto.spec.ts › should navigate subframes @smoke [fail] +page/page-wait-for-load-state.spec.ts › should work for frame [fail] + +# ============================================================================ +# history-navigation (3 tests) +# page.goBack / page.goForward are unimplemented — the stock RDP exposes no +# Page.goBack/goForward and we have not yet driven the back/forward list via +# Runtime.evaluate(history.back()). +# ============================================================================ +page/interception.spec.ts › should disable memory cache when intercepting [fail] +page/page-history.spec.ts › goBack/goForward should work with bfcache-able pages [fail] +page/page-history.spec.ts › page.goBack should work @smoke [fail] + +# ============================================================================ +# unimplemented-other (26 tests) +# Other PageDelegate methods that still throw 'Method not implemented' on the +# WebView backend. Re-triage once the corresponding stubs in wvPage.ts grow +# real implementations. +# ============================================================================ +page/frame-evaluate.spec.ts › should allow cross-frame element handles [fail] +page/page-aria-snapshot-ai.spec.ts › return empty snapshot when iframe is not loaded [fail] +page/page-aria-snapshot-ai.spec.ts › should gracefully fallback when child frame cant be captured [fail] +page/page-aria-snapshot-ai.spec.ts › should limit depth across iframe boundary [fail] +page/page-aria-snapshot-ai.spec.ts › should list iframes [fail] +page/page-aria-snapshot-ai.spec.ts › should persist iframe references [fail] +page/page-aria-snapshot-ai.spec.ts › should produce incremental snapshot for iframes [fail] +page/page-aria-snapshot-ai.spec.ts › should stitch all frame snapshots [fail] +page/page-emulate-media.spec.ts › should change the actual colors in css [fail] +page/page-emulate-media.spec.ts › should default to light [fail] +page/page-emulate-media.spec.ts › should emulate colorScheme should work @smoke [fail] +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-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] +page/page-request-intercept.spec.ts › should not follow redirects when maxRedirects is set to 0 in route.fetch [fail] +page/page-route.spec.ts › should properly return navigation response when URL has cookies [fail] +page/page-set-input-files.spec.ts › should detect mime type [fail] +page/page-set-input-files.spec.ts › should emit input and change events [fail] +page/page-set-input-files.spec.ts › should upload a folder [fail] +page/page-set-input-files.spec.ts › should upload the file [fail] +page/page-set-input-files.spec.ts › should upload the file with spaces in name [fail] + +# ============================================================================ +# hover-state-missing (5 tests) +# ':hover' is not applied during synthetic mouse-move on the WebView — the +# stock RDP has no Input.dispatchMouseEvent type='mouseHovered' equivalent. +# ============================================================================ +page/locator-misc-1.spec.ts › should hover @smoke [fail] +page/locator-misc-1.spec.ts › should hover when Node is removed [fail] +page/page-mouse.spec.ts › should trigger hover state [fail] +page/page-mouse.spec.ts › should trigger hover state on disabled button [fail] +page/page-mouse.spec.ts › should trigger hover state with removed window.Node [fail] + +# ============================================================================ +# timeout (26 tests) +# Test timed out — usually because an unimplemented hook never resolves. +# Re-triage once the relevant delegate methods are wired up. +# ============================================================================ +page/expect-boolean.spec.ts › toBeChecked › with impossible timeout .not [fail] +page/jshandle-as-element.spec.ts › should return ElementHandle for TextNodes [fail] +page/locator-convenience.spec.ts › innerText should produce log [fail] +page/locator-evaluate.spec.ts › should not throw in case of missing selector for all [fail] +page/locator-is-visible.spec.ts › isVisible inside a role=button [fail] +page/locator-query.spec.ts › should support has:locator [fail] +page/locator-query.spec.ts › should support locator.filter [fail] +page/locator-query.spec.ts › should throw on due to strictness [fail] +page/page-autowaiting-basic.spec.ts › should report and collapse log in action [fail] +page/page-autowaiting-basic.spec.ts › should report and collapse log in expect [fail] +page/page-autowaiting-no-hang.spec.ts › assigning location to about:blank after non-about:blank [fail] +page/page-autowaiting-no-hang.spec.ts › calling window.stop sync [fail] +page/page-click.spec.ts › should wait for becoming hit target [fail] +page/page-drag.spec.ts › Drag and drop › should respect the drop effect [fail] +page/page-drag.spec.ts › Drag and drop › should work if a frame is stalled [fail] +page/page-evaluate.spec.ts › should return -Infinity [fail] +page/page-goto.spec.ts › should not leak listeners during bad navigation [fail] +page/page-goto.spec.ts › should not throw unhandled rejections on invalid url [fail] +page/page-history.spec.ts › page.goBack during renderer-initiated navigation [fail] +page/page-localstorage.spec.ts › localStorage.setItem overwrites existing value [fail] +page/page-network-idle.spec.ts › should wait for networkidle from the child frame [fail] +page/page-network-idle.spec.ts › should wait for networkidle in setContent [fail] +page/page-request-continue.spec.ts › should not throw if request was cancelled by the page [fail] +page/page-route.spec.ts › should not throw "Invalid Interception Id" if the request was cancelled [fail] +page/page-screenshot.spec.ts › should capture css box-shadow [fail] +page/to-match-aria-snapshot.spec.ts › should not match what is not matched [fail] + +# ============================================================================ +# structural-unsupported (111 tests) +# Tests whose name contains 'iframe', 'popup', or 'filechooser'. Stock WebKit +# RDP exposes no Target.create / popup tracking / file-chooser API and our +# wvPage subframe support is intentionally minimal — these tests can't be +# made to pass without a real WebKit build. Bulk-skipped to keep the worker +# from stalling on popup/dialog interactions. +# ============================================================================ +page/elementhandle-content-frame.spec.ts › should return null for non-iframes [fail] +page/elementhandle-content-frame.spec.ts › should work for cross-process iframes [fail] +page/elementhandle-owner-frame.spec.ts › should work for cross-process iframes [fail] +page/elementhandle-owner-frame.spec.ts › should work for iframe elements [fail] +page/frame-evaluate.spec.ts › should work in iframes that failed initial navigation [fail] +page/frame-evaluate.spec.ts › should work in iframes that interrupted initial javascript url navigation [fail] +page/frame-hierarchy.spec.ts › should refuse to display x-frame-options:deny iframe [fail] +page/locator-frame.spec.ts › click should survive iframe navigation [fail] +page/locator-frame.spec.ts › locator.frameLocator should work for iframe [fail] +page/locator-frame.spec.ts › should click in lazy iframe [fail] +page/locator-frame.spec.ts › should work for iframe @smoke [fail] +page/locator-frame.spec.ts › should work for nested iframe [fail] +page/locator-frame.spec.ts › should work with COEP/COOP/CORP isolated iframe [fail] +page/page-add-init-script.spec.ts › init script should run only once in iframe [fail] +page/page-aria-snapshot-ai.spec.ts › return empty snapshot when iframe is not loaded [fail] +page/page-aria-snapshot-ai.spec.ts › should limit depth across iframe boundary [fail] +page/page-aria-snapshot-ai.spec.ts › should list iframes [fail] +page/page-aria-snapshot-ai.spec.ts › should mark iframe as active when it contains focused element [fail] +page/page-aria-snapshot-ai.spec.ts › should persist iframe references [fail] +page/page-aria-snapshot-ai.spec.ts › should produce incremental snapshot for iframes [fail] +page/page-aria-snapshot-ai.spec.ts › should snapshot a locator inside an iframe [fail] +page/page-aria-snapshot-ai.spec.ts › should support many properties on iframes [fail] +page/page-aria-snapshot.spec.ts › auto-waits the locator and does not include iframes [fail] +page/page-aria-snapshot.spec.ts › should snapshot a locator inside an iframe [fail] +page/page-autowaiting-no-hang.spec.ts › opening a popup [fail] +page/page-click-scroll.spec.ts › should scroll into view element in iframe [fail] +page/page-click.spec.ts › should click a button that is overlaid by a permission popup [fail] +page/page-click.spec.ts › should click in a nested transformed iframe [fail] +page/page-click.spec.ts › should click in a transformed iframe [fail] +page/page-click.spec.ts › should click in a transformed iframe with force [fail] +page/page-click.spec.ts › should click in an iframe with border [fail] +page/page-click.spec.ts › should click in an iframe with border 2 [fail] +page/page-click.spec.ts › should click the button inside an iframe [fail] +page/page-click.spec.ts › should click the button with fixed position inside an iframe [fail] +page/page-click.spec.ts › should issue clicks in parallel in page and popup [fail] +page/page-drag.spec.ts › Drag and drop › iframe › should drag into an iframe [fail] +page/page-drag.spec.ts › Drag and drop › iframe › should drag out of an iframe [fail] +page/page-drag.spec.ts › Drag and drop › should work inside iframe [fail] +page/page-evaluate.spec.ts › should await promise from popup [fail] +page/page-event-console.spec.ts › should not throw when there are console messages in detached iframes [fail] +page/page-event-load.spec.ts › should fire once with iframe navigation [fail] +page/page-event-popup.spec.ts › should be able to capture alert [fail] +page/page-event-popup.spec.ts › should emit for immediately closed popups [fail] +page/page-event-popup.spec.ts › should emit for immediately closed popups 2 [fail] +page/page-event-popup.spec.ts › should report popup opened from iframes [fail] +page/page-event-popup.spec.ts › should work @smoke [fail] +page/page-event-popup.spec.ts › should work with clicking target=_blank [fail] +page/page-event-popup.spec.ts › should work with clicking target=_blank and rel=noopener [fail] +page/page-event-popup.spec.ts › should work with empty url [fail] +page/page-event-popup.spec.ts › should work with fake-clicking target=_blank and rel=noopener [fail] +page/page-event-popup.spec.ts › should work with noopener and about:blank [fail] +page/page-event-popup.spec.ts › should work with noopener and no url [fail] +page/page-event-popup.spec.ts › should work with noopener and url [fail] +page/page-event-popup.spec.ts › should work with window features [fail] +page/page-event-request.spec.ts › should fire for iframes [fail] +page/page-filechooser.spec.ts › should accept single file [fail] +page/page-filechooser.spec.ts › should be able to read selected file [fail] +page/page-filechooser.spec.ts › should be able to reset selected files with empty file list [fail] +page/page-filechooser.spec.ts › should emit event addListener/removeListener [fail] +page/page-filechooser.spec.ts › should emit event after navigation [fail] +page/page-filechooser.spec.ts › should emit event for iframe [fail] +page/page-filechooser.spec.ts › should emit event on/off [fail] +page/page-filechooser.spec.ts › should emit event once [fail] +page/page-filechooser.spec.ts › should emit event via prepend [fail] +page/page-filechooser.spec.ts › should not throw when filechooser belongs to iframe [fail] +page/page-filechooser.spec.ts › should not throw when frame is detached immediately [fail] +page/page-filechooser.spec.ts › should not trim big uploaded files [fail] +page/page-filechooser.spec.ts › should prioritize exact timeout over default timeout [fail] +page/page-filechooser.spec.ts › should respect default timeout when there is no custom timeout [fail] +page/page-filechooser.spec.ts › should respect timeout [fail] +page/page-filechooser.spec.ts › should return the same file chooser when there are many watchdogs simultaneously [fail] +page/page-filechooser.spec.ts › should trigger listener added before navigation [fail] +page/page-filechooser.spec.ts › should upload multiple large files [fail] +page/page-filechooser.spec.ts › should work for "multiple" [fail] +page/page-filechooser.spec.ts › should work for "webkitdirectory" [fail] +page/page-filechooser.spec.ts › should work for single file pick [fail] +page/page-filechooser.spec.ts › should work when file input is attached to DOM [fail] +page/page-filechooser.spec.ts › should work when file input is not attached to DOM [fail] +page/page-filechooser.spec.ts › should work with no timeout [fail] +page/page-goto.spec.ts › should capture cross-process iframe navigation request [fail] +page/page-goto.spec.ts › should capture iframe navigation request [fail] +page/page-goto.spec.ts › should work with lazy loading iframes [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] +page/page-network-request.spec.ts › should not allow to access frame on popup main request [fail] +page/page-request-intercept.spec.ts › should fulfill popup main request using alias [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should mask inside iframe [fail] +page/page-screenshot.spec.ts › page screenshot › should work with iframe in shadow [fail] +page/page-set-content.spec.ts › should return empty content there is no iframe src [fail] +page/page-set-input-files.spec.ts › should upload a file after popup [fail] +page/page-wait-for-load-state.spec.ts › should resolve after popup load [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of about:blank popup [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of about:blank popup with noopener [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of empty url popup [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of popup with network url [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of popup with network url and noopener [fail] +page/page-wait-for-load-state.spec.ts › should work with broken blob-url iframe [fail] +page/page-wait-for-load-state.spec.ts › should work with broken data-url iframe [fail] +page/page-wait-for-load-state.spec.ts › should work with javascript: iframe [fail] +page/selectors-frame.spec.ts › click should survive iframe navigation [fail] +page/selectors-frame.spec.ts › should click in lazy iframe [fail] +page/selectors-frame.spec.ts › should work for iframe (handle) [fail] +page/selectors-frame.spec.ts › should work for iframe @smoke [fail] +page/selectors-frame.spec.ts › should work for nested iframe [fail] +page/selectors-frame.spec.ts › should work for nested iframe (handle) [fail] +page/selectors-frame.spec.ts › waitForSelector should survive iframe navigation (handle) [fail] +page/wheel.spec.ts › should dispatch wheel events after popup was opened @smoke [fail] +page/workers.spec.ts › should attribute network activity for worker inside iframe to the iframe [fail] + +# ============================================================================ +# native-ui-picker (15 tests) +# Tests that fill . +# On iOS Safari these open a modal native UI picker that the WebInspector +# protocol can't dismiss, leaving the worker stuck for every subsequent test. +# Bulk-skipped until we can drive the picker (or override it in the bridge). +# ============================================================================ +page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - color [fail] +page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - date [fail] +page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - datetime-local [fail] +page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - month [fail] +page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - time [fail] +page/page-fill.spec.ts › input event.composed should be true and cross shadow dom boundary - week [fail] +page/page-fill.spec.ts › should fill color input [fail] +page/page-fill.spec.ts › should fill color input case insensitive [fail] +page/page-fill.spec.ts › should fill date input after clicking [fail] +page/page-fill.spec.ts › should fill datetime-local input [fail] +page/page-fill.spec.ts › should fill month input [fail] +page/page-fill.spec.ts › should fill time input [fail] +page/page-fill.spec.ts › should fill week input [fail] +page/page-fill.spec.ts › should throw on incorrect color value [fail] +page/page-fill.spec.ts › should throw on incorrect datetime-local [fail] + +# ============================================================================ +# target-closed-cascade (107 tests) +# Cascade failures: a test tears down the page session and every following test +# sees 'Target closed'. A confirmed trigger is cross-process navigation, which +# leaves the page target dead for the next test's first navigation (see the +# worker-remaining section). Re-run and prune this section as triggers are fixed. +# ============================================================================ +page/elementhandle-click.spec.ts › should work @smoke [fail] +page/elementhandle-owner-frame.spec.ts › should work for adopted elements [fail] +page/elementhandle-query-selector.spec.ts › should work for adopted elements [fail] +page/elementhandle-scroll-into-view.spec.ts › should scroll display:contents into view [fail] +page/frame-evaluate.spec.ts › should execute after cross-site navigation [fail] +page/frame-evaluate.spec.ts › should not allow cross-frame js handles [fail] +page/frame-hierarchy.spec.ts › should not send attach/detach events for main frame [fail] +page/frame-hierarchy.spec.ts › should persist mainFrame on cross-process navigation [fail] +page/frame-hierarchy.spec.ts › should send "framenavigated" when navigating on anchor URLs [fail] +page/interception.spec.ts › should intercept network activity from worker [fail] +page/interception.spec.ts › should intercept worker requests when enabled after worker creation [fail] +page/interception.spec.ts › should work with navigation @smoke [fail] +page/locator-frame.spec.ts › click should survive frame reattach [fail] +page/locator-frame.spec.ts › frameLocator.owner should work [fail] +page/locator-frame.spec.ts › getBy coverage [fail] +page/locator-frame.spec.ts › locator.contentFrame should work [fail] +page/locator-frame.spec.ts › locator.frameLocator should not throw on first/last/nth [fail] +page/locator-frame.spec.ts › locator.frameLocator should throw on ambiguity [fail] +page/locator-frame.spec.ts › should wait for frame 2 [fail] +page/locator-frame.spec.ts › should wait for frame to go [fail] +page/locator-frame.spec.ts › should work for $ and $$ [fail] +page/locator-frame.spec.ts › waitFor should survive frame reattach [fail] +page/locator-misc-2.spec.ts › Locator.locator() and FrameLocator.locator() should accept locator [fail] +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-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] +page/page-autowaiting-no-hang.spec.ts › goBack in the middle of navigation that commits [fail] +page/page-basic.spec.ts › frame.press should work [fail] +page/page-basic.spec.ts › page.close should work with window.close [fail] +page/page-basic.spec.ts › should provide access to the opener page [fail] +page/page-click-scroll.spec.ts › should scroll into view display:contents [fail] +page/page-click-scroll.spec.ts › should scroll into view display:contents with position [fail] +page/page-click-timeout-1.spec.ts › should avoid side effects after timeout [fail] +page/page-click-timeout-3.spec.ts › should fail when element jumps during hit testing [fail] +page/page-click.spec.ts › should click a very large button with offset [fail] +page/page-click.spec.ts › should click the button after a cross origin navigation [fail] +page/page-click.spec.ts › should click the button when window.innerWidth is corrupted [fail] +page/page-click.spec.ts › should select the text by triple clicking [fail] +page/page-dispatchevent.spec.ts › should dispatch click after a cross origin navigation [fail] +page/page-dispatchevent.spec.ts › should dispatch click after navigation [fail] +page/page-dispatchevent.spec.ts › should dispatch click when node is added in shadow dom [fail] +page/page-dispatchevent.spec.ts › should throw if argument is from different frame [fail] +page/page-evaluate.spec.ts › should evaluate in the page context [fail] +page/page-evaluate.spec.ts › should not throw an error when evaluation does a navigation [fail] +page/page-evaluate.spec.ts › should transfer 100Mb of data from page to node.js [fail] +page/page-evaluate.spec.ts › should work right after a cross-origin navigation [fail] +page/page-expose-function.spec.ts › should alias Window, Document and Node [fail] +page/page-expose-function.spec.ts › should await returned promise [fail] +page/page-expose-function.spec.ts › should serialize cycles [fail] +page/page-expose-function.spec.ts › should survive navigation [fail] +page/page-expose-function.spec.ts › should work on frames [fail] +page/page-expose-function.spec.ts › should work on frames before navigation [fail] +page/page-expose-function.spec.ts › should work with complex objects [fail] +page/page-expose-function.spec.ts › should work with setContent [fail] +page/page-goto.spec.ts › js redirect overrides url bar navigation [fail] +# First does a cross-origin navigation that kills the page target; second is a +# plain navigation that inherits 'Target closed' when it runs after such a +# trigger. Keep skipped until cross-process navigation stops tearing down the +# session. +page/page-goto.spec.ts › should not crash when navigating to bad SSL after a cross origin navigation [fail] +page/page-goto.spec.ts › should not throw if networkidle0 is passed as an option [fail] +page/page-request-fulfill.spec.ts › should fulfill preload link requests [fail] +page/page-request-intercept.spec.ts › should give access to the intercepted response [fail] +page/page-request-intercept.spec.ts › should intercept with post data override [fail] +page/page-route.spec.ts › should be able to intercept every navigation to a page controlled by service worker [fail] +page/page-route.spec.ts › should intercept main resource during cross-process navigation [fail] +page/page-route.spec.ts › should not fulfill with redirect status [fail] +page/page-route.spec.ts › should not throw if request was cancelled by the page [fail] +page/page-screenshot.spec.ts › page screenshot animations › should trigger particular events for INfinite css animation [fail] +page/page-screenshot.spec.ts › page screenshot animations › should trigger particular events for css transitions [fail] +page/page-screenshot.spec.ts › page screenshot animations › should trigger particular events for finite css animation [fail] +page/page-screenshot.spec.ts › page screenshot › mask option › should work when subframe has stalled navigation [fail] +page/page-wait-for-function.spec.ts › should survive cross-process navigation [fail] +page/page-wait-for-function.spec.ts › should survive navigations [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of about:blank popup [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of about:blank popup with noopener [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of popup with network url [fail] +page/page-wait-for-load-state.spec.ts › should wait for load state of popup with network url and noopener [fail] +page/page-wait-for-load-state.spec.ts › should work with clicking target=_blank [fail] +page/page-wait-for-load-state.spec.ts › should work with pages that have loaded before being connected to [fail] +page/page-wait-for-navigation.spec.ts › should work for cross-process navigations [fail] +page/page-wait-for-navigation.spec.ts › should work on frame [fail] +page/page-wait-for-navigation.spec.ts › should work with clicking on anchor links [fail] +page/page-wait-for-navigation.spec.ts › should work with history.pushState() [fail] +page/page-wait-for-navigation.spec.ts › should work with history.replaceState() [fail] +page/page-wait-for-navigation.spec.ts › should work with url match for same document navigations [fail] +page/page-wait-for-selector-2.spec.ts › should support >> selector syntax [fail] +page/page-wait-for-selector-2.spec.ts › should survive cross-process navigation [fail] +page/page-wait-for-url.spec.ts › should work with clicking on anchor links [fail] +page/page-wait-for-url.spec.ts › should work with history.pushState() [fail] +page/page-wait-for-url.spec.ts › should work with history.replaceState() [fail] +page/page-wait-for-url.spec.ts › should work with url match for same document navigations [fail] +page/retarget.spec.ts › should check the box outside shadow dom label [fail] +page/selectors-frame.spec.ts › click should survive frame reattach [fail] +page/selectors-frame.spec.ts › click should survive navigation [fail] +page/selectors-frame.spec.ts › should capture after the enter-frame [fail] +page/selectors-frame.spec.ts › should not allow capturing before enter-frame [fail] +page/selectors-frame.spec.ts › should not allow dangling enter-frame [fail] +page/selectors-frame.spec.ts › should not allow leading enter-frame [fail] +page/selectors-frame.spec.ts › should work for $ and $$ [fail] +page/selectors-frame.spec.ts › should work for $ and $$ (handle) [fail] +page/selectors-frame.spec.ts › should work for $$eval [fail] +page/selectors-frame.spec.ts › should work for $$eval (handle) [fail] +page/selectors-frame.spec.ts › should work for $eval [fail] +page/selectors-frame.spec.ts › should work for $eval (handle) [fail] +page/selectors-frame.spec.ts › waitFor should survive frame reattach [fail] +page/selectors-frame.spec.ts › waitForSelector should survive frame reattach (handle) [fail] +# ============================================================================ +# worker-remaining (2 tests) +# Web Workers attach over the WebView backend now (Worker.enable is sent during +# session init), so page/workers.spec.ts mostly passes. Two gaps remain: +# - 'should clear upon cross-process navigation' passes its own assertions but +# the cross-process target swap leaves the page in a 'Target closed' state for +# the next test (same root cause as target-closed-cascade above). +# - 'should support extra http headers': setExtraHTTPHeaders is not applied to +# the worker script request, so worker.js arrives without the header. +# ============================================================================ +page/workers.spec.ts › should clear upon cross-process navigation [fail] +page/workers.spec.ts › should support extra http headers [fail] + diff --git a/tests/webview/playwright.config.ts b/tests/webview/playwright.config.ts index 7a011477dd644..00ada2a0323c3 100644 --- a/tests/webview/playwright.config.ts +++ b/tests/webview/playwright.config.ts @@ -52,12 +52,20 @@ const metadata = { video: false, }; +// macOS drives Mobile Safari in the iOS Simulator via ios_webkit_debug_proxy; +// Linux drives a WebKitGTK/WPE browser (Epiphany, MiniBrowser, ...) over its +// remote inspector HTTP server. Both share the same page tests and connectOverCDP +// transport; only the discovery/launch differs (see webviewTest.ts). +const isLinux = process.platform === 'linux'; +const projectName = isLinux ? 'webkit-webview-gtk-page' : 'webkit-webview-page'; +const snapshotSuffix = isLinux ? 'webkit-webview-gtk' : 'webkit-webview'; + config.projects!.push({ - name: 'webkit-webview-page', + name: projectName, use: { browserName: 'webkit', }, - snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-webkit-webview{ext}', + snapshotPathTemplate: `{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-${snapshotSuffix}{ext}`, testDir: path.join(testDir, 'page'), metadata, }); diff --git a/tests/webview/run-epiphany.sh b/tests/webview/run-epiphany.sh new file mode 100755 index 0000000000000..5bb302856f018 --- /dev/null +++ b/tests/webview/run-epiphany.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# +# Run the webview tests against Epiphany (GNOME Web) over the WebKitGTK remote +# inspector. Extra arguments are forwarded to `playwright test`, e.g.: +# +# tests/webview/run-epiphany.sh # full suite +# tests/webview/run-epiphany.sh page-evaluate # one file +# tests/webview/run-epiphany.sh --grep "should work" # by title +# +# Environment overrides: +# PW_WEBVIEW_PORT inspector HTTP server port (default 9233) +# PW_WEBVIEW_BROWSER browser binary (default: epiphany) +# EPIPHANY_PROFILE throwaway profile dir (default: a fresh mktemp dir) +# +set -euo pipefail + +# Repo root is two levels up from this script. +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT" + +PORT="${PW_WEBVIEW_PORT:-9233}" +BROWSER="${PW_WEBVIEW_BROWSER:-epiphany}" +# A throwaway profile keeps Epiphany from attaching to an already-running +# single instance that has no inspector server. +PROFILE="${EPIPHANY_PROFILE:-$(mktemp -d /tmp/epi-rdp.XXXXXX)}" + +# WebKitGTK binds the inspector on a single numeric address, so use 127.0.0.1. +export PW_WEBVIEW_PROXY_BASE="http://127.0.0.1:${PORT}" +export PW_WEBVIEW_BROWSER="$BROWSER" +export PW_WEBVIEW_BROWSER_ARGS="--private-instance --profile=${PROFILE}" + +echo "Running webview tests against ${BROWSER} on ${PW_WEBVIEW_PROXY_BASE} (profile: ${PROFILE})" + +# Under Xvfb when there is no display (e.g. CI), otherwise directly. +if [[ -z "${DISPLAY:-}" && -z "${WAYLAND_DISPLAY:-}" ]] && command -v xvfb-run >/dev/null 2>&1; then + exec xvfb-run -a npx playwright test --config tests/webview/playwright.config.ts "$@" +else + exec npx playwright test --config tests/webview/playwright.config.ts "$@" +fi diff --git a/tests/webview/webviewTest.ts b/tests/webview/webviewTest.ts index 2fc530c00e670..f3f92dc7dd67d 100644 --- a/tests/webview/webviewTest.ts +++ b/tests/webview/webviewTest.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { spawnSync } from 'child_process'; +import { spawn, spawnSync, type ChildProcess } from 'child_process'; import fs from 'fs'; import path from 'path'; @@ -25,6 +25,12 @@ export { expect } from '@playwright/test'; const PROXY_BASE = process.env.PW_WEBVIEW_PROXY_BASE || 'http://localhost:9222'; +// WebKitGTK / WPE: which browser to launch with its remote inspector HTTP server. +// Any WebKitGTK or WPE app works (Epiphany, MiniBrowser, Cog, ...). Override the +// binary with PW_WEBVIEW_BROWSER and pass extra args via PW_WEBVIEW_BROWSER_ARGS, +// e.g. PW_WEBVIEW_BROWSER=epiphany PW_WEBVIEW_BROWSER_ARGS="--private-instance". +const DEFAULT_WEBKITGTK_BROWSER = '/usr/lib/x86_64-linux-gnu/webkitgtk-6.0/MiniBrowser'; + type WebViewWorkerFixtures = PageWorkerFixtures & { webviewExpectations: Map; webviewEndpoint: string; @@ -112,6 +118,60 @@ async function discoverEndpoint(deadlineMs: number): Promise { throw new Error(`No webview tab visible on ${PROXY_BASE}/json after waiting. Last response: ${JSON.stringify(last)}`); } +// WebKitGTK binds its inspector server on a numeric IP (parsed with +// g_inet_socket_address_new_from_string), and only on that single address. Using +// "localhost" would both fail to bind and let Node's fetch try ::1 first while the +// server listens on 127.0.0.1 only. Normalize to a numeric IP everywhere. +const GTK_PROXY_BASE = (() => { + const u = new URL(PROXY_BASE); + if (u.hostname === 'localhost') + u.hostname = '127.0.0.1'; + return u.origin; +})(); + +// WEBKIT_INSPECTOR_HTTP_SERVER expects a bare host:port. +function inspectorHttpAddress(): string { + const u = new URL(GTK_PROXY_BASE); + return `${u.hostname}:${u.port || '9222'}`; +} + +// Launch a WebKitGTK/WPE browser with its remote inspector HTTP server enabled. +function launchWebKitGTKBrowser(): ChildProcess { + const command = process.env.PW_WEBVIEW_BROWSER || DEFAULT_WEBKITGTK_BROWSER; + const extraArgs = process.env.PW_WEBVIEW_BROWSER_ARGS ? process.env.PW_WEBVIEW_BROWSER_ARGS.split(' ').filter(Boolean) : []; + return spawn(command, [...extraArgs, 'about:blank'], { + env: { + ...process.env, + WEBKIT_INSPECTOR_HTTP_SERVER: inspectorHttpAddress(), + // WebKit's bubblewrap sandbox needs an unprivileged user namespace, which + // distros like Ubuntu 24.04 deny to unconfined processes + // (kernel.apparmor_restrict_unprivileged_userns=1) — bwrap then fails with + // "setting up uid map: Permission denied". The browser is a throwaway test + // target, so disable the sandbox to run regardless of the host policy. + WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS: '1', + }, + stdio: 'ignore', + }); +} + +// The WebKitGTK/WPE remote inspector publishes targets as an HTML page at `/`, +// each with a `/socket///WebPage` link. Wait until at +// least one page target shows up, then hand the HTTP base to connectOverCDP. +async function waitForWebKitGTKTarget(deadlineMs: number): Promise { + let lastError = ''; + while (Date.now() < deadlineMs) { + try { + const res = await fetch(`${GTK_PROXY_BASE}/`); + if (res.ok && /\/socket\/\d+\/\d+\/WebPage/.test(await res.text())) + return; + } catch (e) { + lastError = String(e); + } + await sleep(500); + } + throw new Error(`No WebPage target appeared at ${GTK_PROXY_BASE}/ within timeout. ${lastError}`); +} + export const webviewTest = baseTest.extend({ browserVersion: ['', { scope: 'worker' }], browserMajorVersion: [0, { scope: 'worker' }], @@ -126,16 +186,33 @@ export const webviewTest = baseTest.extend { - const udid = bootedSimulatorUdid(); - if (udid) + const udid = process.platform === 'darwin' ? bootedSimulatorUdid() : undefined; + if (udid) { await resetMobileSafari(udid); - const endpoint = await discoverEndpoint(Date.now() + 120000); - await run(endpoint); + const endpoint = await discoverEndpoint(Date.now() + 120000); + await run(endpoint); + return; + } + const browser = launchWebKitGTKBrowser(); + try { + await waitForWebKitGTKTarget(Date.now() + 60000); + // connectOverCDP discovers the page target(s) from the HTML listing itself. + await run(GTK_PROXY_BASE); + } finally { + // Sandboxed browsers (e.g. the Epiphany snap) refuse signals from outside + // their confinement, so a failed kill must not fail the worker. + try { + browser.kill('SIGKILL'); + } catch {} + } }, { scope: 'worker', timeout: 180000 }], page: async ({ playwright, webviewEndpoint }, run) => {