From a861c6e44b60b2e861a92332e5ac0296535e13c0 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Tue, 4 Aug 2026 01:38:03 +0000 Subject: [PATCH] fix: report honest mobile touch fidelity --- .../src/browser-actions-runner.ts | 9 ++++++- .../src/browser-environment-matrix.ts | 13 ++++++++-- ...browser-environment-matrix.browser.test.ts | 25 ++++++++++++++++++- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/runtime-playground/src/browser-actions-runner.ts b/packages/runtime-playground/src/browser-actions-runner.ts index c8f06790b..5b62f1f0a 100644 --- a/packages/runtime-playground/src/browser-actions-runner.ts +++ b/packages/runtime-playground/src/browser-actions-runner.ts @@ -162,7 +162,14 @@ export async function runBrowserActionsCommand({ } resolvedEnvironment = session?.resolved ?? await resolvePlaywrightBrowserEnvironment(browserEnvironmentCell(requestedEnvironment), browser) const unsupportedEnvironment = resolvedEnvironment.capabilities.filter(({ fidelity }) => fidelity === "unsupported").map(({ id }) => id) - environmentEvidence = { requested: requestedEnvironment, resolved: resolvedEnvironment.effective, provider: resolvedEnvironment.provider, capabilities: resolvedEnvironment.capabilities, unsupported: unsupportedEnvironment, inconclusive: [] } + environmentEvidence = { + requested: requestedEnvironment, + resolved: resolvedEnvironment.effective, + provider: resolvedEnvironment.provider, + capabilities: resolvedEnvironment.capabilities, + unsupported: unsupportedEnvironment, + inconclusive: [...resolvedEnvironment.capabilities.filter(({ fidelity }) => fidelity === "emulated").map(({ id }) => id), "browser.environment.observation"], + } if (unsupportedEnvironment.length > 0) { throw new Error(`wordpress.browser-actions browser environment is unsupported: ${unsupportedEnvironment.join(", ")}`) } diff --git a/packages/runtime-playground/src/browser-environment-matrix.ts b/packages/runtime-playground/src/browser-environment-matrix.ts index 134645b4e..283b5de62 100644 --- a/packages/runtime-playground/src/browser-environment-matrix.ts +++ b/packages/runtime-playground/src/browser-environment-matrix.ts @@ -94,7 +94,7 @@ export async function resolvePlaywrightBrowserEnvironment(cell: BrowserEnvironme if (requested.userAgent) exact("browser.environment.user-agent") if (requested.permissions) exact("browser.environment.permissions") if (requested.deviceScaleFactor !== undefined) exact("browser.environment.device-scale-factor") - if (requested.isMobile !== undefined) exact("browser.environment.mobile") + if (requested.isMobile !== undefined) emulated("browser.environment.mobile", "Playwright applies the mobile context setting, but the page has no authoritative API for reading the context flag back.") if (requested.hasTouch !== undefined) exact("browser.environment.touch") if (requested.orientation) exact("browser.environment.orientation") if (requested.colorScheme) exact("browser.environment.color-scheme") @@ -194,13 +194,22 @@ export async function observePlaywrightBrowserEnvironment(page: Page, requested: const inconclusive: string[] = [] if (requested.device) inconclusive.push("browser.environment.device") if (requested.isMobile !== undefined) inconclusive.push("browser.environment.mobile") + if (requested.hasTouch !== undefined && observed && requested.hasTouch !== observed.hasTouch) { + const capability = "browser.environment.touch" + unsupported.push(capability) + } else if (requested.hasTouch !== undefined && !observed) { + inconclusive.push("browser.environment.touch") + } if (requested.geolocation && !observed?.geolocation) inconclusive.push("browser.environment.geolocation.coordinates") if (requested.permissions?.some((permission) => permission !== "geolocation")) inconclusive.push("browser.environment.permissions") for (const [key, capability] of [["orientation", "browser.environment.orientation"], ["zoom", "browser.environment.zoom"], ["colorScheme", "browser.environment.color-scheme"], ["reducedMotion", "browser.environment.reduced-motion"], ["forcedColors", "browser.environment.forced-colors"], ["contrast", "browser.environment.contrast"], ["networkProfile", "browser.environment.network-profile"], ["cpuProfile", "browser.environment.cpu-profile"], ["clock", "browser.environment.clock"], ["capabilities", "browser.environment.capability-state"]] as const) { if (requested[key] !== undefined) inconclusive.push(capability) } if (!observed) inconclusive.push("browser.environment.observation") - return { requested, resolved: resolved.effective, ...(observed ? { observed } : {}), provider: resolved.provider, capabilities: resolved.capabilities, unsupported, inconclusive } + const capabilities = resolved.capabilities.map((capability) => capability.id === "browser.environment.touch" && requested.hasTouch !== undefined && observed && requested.hasTouch !== observed.hasTouch + ? { ...capability, fidelity: "unsupported" as const, reason: `Observed hasTouch=${observed.hasTouch}, requested ${requested.hasTouch}.` } + : capability) + return { requested, resolved: resolved.effective, ...(observed ? { observed } : {}), provider: resolved.provider, capabilities, unsupported: [...new Set(unsupported)], inconclusive: [...new Set(inconclusive)] } } export async function applyPlaywrightGeolocationPermission(page: Page, state: "denied"): Promise<() => Promise> { diff --git a/tests/browser-environment-matrix.browser.test.ts b/tests/browser-environment-matrix.browser.test.ts index b3ae91399..078784616 100644 --- a/tests/browser-environment-matrix.browser.test.ts +++ b/tests/browser-environment-matrix.browser.test.ts @@ -8,7 +8,30 @@ import test from "node:test" import { chromium } from "playwright" import { browserEnvironmentMatrix } from "../packages/runtime-core/src/browser-environment-matrix.js" -import { createPlaywrightBrowserEnvironmentContext, resolvePlaywrightBrowserEnvironment, runPlaywrightBrowserEnvironmentMatrix } from "../packages/runtime-playground/src/browser-environment-matrix.js" +import { createPlaywrightBrowserEnvironmentContext, observePlaywrightBrowserEnvironment, resolvePlaywrightBrowserEnvironment, runPlaywrightBrowserEnvironmentMatrix } from "../packages/runtime-playground/src/browser-environment-matrix.js" + +test("real browser observes the mobile touch contract without overstating isMobile fidelity", async () => { + const browser = await chromium.launch({ headless: true }) + try { + const requested = { viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true } + const resolved = await resolvePlaywrightBrowserEnvironment({ id: "mobile", index: 0, seed: "mobile", selections: {}, requested, requiredCapabilities: [], optionalCapabilities: [] }, browser) + const runtime = await createPlaywrightBrowserEnvironmentContext(browser, resolved) + try { + await runtime.page.goto("data:text/html,
mobile
") + const evidence = await observePlaywrightBrowserEnvironment(runtime.page, requested, resolved) + assert.deepEqual(evidence.observed?.viewport, requested.viewport) + assert.equal(evidence.observed?.hasTouch, true) + assert.equal(evidence.capabilities.find(({ id }) => id === "browser.environment.touch")?.fidelity, "exact") + assert.equal(evidence.capabilities.find(({ id }) => id === "browser.environment.mobile")?.fidelity, "emulated") + assert.deepEqual(evidence.unsupported, []) + assert.deepEqual(evidence.inconclusive, ["browser.environment.mobile"]) + } finally { + await runtime.close() + } + } finally { + await browser.close() + } +}) test("real browser matrix applies viewport, media, locale, timezone, touch, zoom, and throttling", async () => { const artifactRoot = await mkdtemp(join(tmpdir(), "wp-codebox-browser-matrix-"))