ohos-playwright 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,12 +64,16 @@ The following Playwright APIs have been validated on ArkWeb / HarmonyOS 6.1 (Chr
64
64
  | Navigation wait | `page.waitForURL()` — string, glob, RegExp, and `history.pushState` client-side navigation |
65
65
  | Frames | `page.frames()`, `page.mainFrame()`, `frame.url()` |
66
66
  | Viewport | `page.viewportSize()` (pre-fetched via `Page.getLayoutMetrics` for reused CDP tabs), `page.setViewportSize()` (applies precisely) |
67
- | Media emulation | `page.emulateMedia({ colorScheme })` |
67
+ | Media emulation | `page.emulateMedia({ colorScheme, reducedMotion, forcedColors, media })` — all four options work; combine freely |
68
+ | Visual snapshot | `expect(page).toHaveScreenshot()`, `expect(locator).toHaveScreenshot()` — use `SNAPSHOT_ENGINE=<tag>` to separate baselines when comparing ArkWeb vs another engine (both produce `*-linux.png` filenames) |
69
+ | API request | `request` fixture (`page`-independent HTTP client), `playwright.request.newContext()` — both work |
70
+ | Locator handler | `page.addLocatorHandler()`, `page.removeLocatorHandler()` — auto-dismisses overlays before Playwright's action retry |
71
+ | HTTP credentials | `browser.newContext({ httpCredentials: { username, password } })` — auto-injects Basic Auth. `ctx.newPage()` requires `PW_CHROMIUM_ATTACH_TO_OTHER=1`; cookie/storage operations work without it. **Note:** do not combine with `page.route()` on the same page — both use `Fetch.enable` internally and credentials will not be injected when a route is active. |
68
72
  | Web workers | `page.workers()` — returns the list of active workers |
69
73
  | WebSocket | `page.routeWebSocket()` (requires Playwright ≥ 1.48) — intercepts WebSocket connections |
70
74
  | Accessibility (CDP) | `newCDPSession` + `Accessibility.getFullAXTree` — returns the full AX node tree |
71
75
  | Navigation history | `page.goBack()`, `page.goForward()` — implemented via `history.back/forward()` + CDP polling; returns when the history index changes |
72
- | Hover events | `locator.hover()` — fires `mouseover` / `mouseenter` event listeners **and** activates the CSS `:hover` pseudo-class via the real `Input.dispatchMouseEvent` path. Falls back to JS-only dispatch (DOM events without `:hover`) if Playwright's `boundingBox()` hangs for more than 5 s. |
76
+ | Hover events | `locator.hover()` — fires `mouseover` / `mouseenter` event listeners **and** activates the CSS `:hover` pseudo-class via `boundingBox()` + `page.mouse.move()` (real `Input.dispatchMouseEvent` path) |
73
77
  | Locale (partial) | `emulateLocale(tag)` fixture — rewrites `navigator.language` / `navigator.languages` via `addInitScript`; does not affect HTTP `Accept-Language` or browser UI locale |
74
78
  | User-Agent | `emulateDevice({ userAgent })` — overrides both `navigator.userAgent` and the outgoing HTTP `User-Agent` header; call before `page.goto()` for the override to take effect. (`context.setExtraHTTPHeaders({ 'User-Agent': ... })` does **not** override UA — ArkWeb preserves the browser default there.) |
75
79
  | Service Workers | `navigator.serviceWorker.register()` — works on HTTPS pages; `navigator.serviceWorker` is `undefined` on non-secure origins (`data:`, `about:blank`) as in all browsers |
@@ -77,7 +81,7 @@ The following Playwright APIs have been validated on ArkWeb / HarmonyOS 6.1 (Chr
77
81
 
78
82
  ### `emulateDevice` fixture
79
83
 
80
- Because `newContext()` is not available in default single-context mode (see "Multi-context / multi-page" in Limitations), device emulation is exposed as a Playwright fixture parameter backed by CDP `Emulation.*` commands.
84
+ Because `ctx.newPage()` requires `PW_CHROMIUM_ATTACH_TO_OTHER=1` (see "Multi-context" in Limitations), device emulation is exposed as a Playwright fixture parameter backed by CDP `Emulation.*` commands.
81
85
 
82
86
  ```ts
83
87
  import { test, expect } from '@playwright/test'
@@ -121,7 +125,9 @@ Coordinates are CSS pixels relative to the viewport (same as `touchscreen.tap`).
121
125
  ## Limitations
122
126
 
123
127
  - **Chromium only.** firefox and webkit aren't available on HarmonyOS.
124
- - **Multi-context / multi-page is opt-in.** By default the adapter intercepts `browser.newContext()` with a friendly error. The root cause: ArkWeb's `Target.createTarget` returns a target with `type: 'other'`, not `'page'`, so Playwright's `crBrowser._onAttachedToTarget` skips it and `ctx.newPage()` throws `Cannot read properties of undefined (reading '_page')`. To opt in, set `process.env.PW_CHROMIUM_ATTACH_TO_OTHER = '1'` **before** importing `@playwright/test` — Playwright's upstream escape hatch then treats `'other'` targets as pages and `browser.newContext()` / `ctx.newPage()` work normally. Trade-off: ArkWeb's internal `'other'` targets (shared workers, etc.) also get treated as pages, which can perturb tests that use `touchscreen.tap()` or `context.recordHar()`. For single-context tests (the common case) leave the env unset and isolate with `localStorage.clear()` + `page.reload()`. For device emulation use the `emulateDevice` fixture instead of `browser.newContext({ ...device })`.
128
+ - **`browser.newContext()` works; `ctx.newPage()` requires opt-in.** `browser.newContext()` is available without any opt-in use it freely for `addCookies` / `storageState()` / `clearCookies()`. However, `ctx.newPage()` on ArkWeb throws Playwright's natural `Cannot read properties of undefined (reading '_page')` because ArkWeb's `Target.createTarget` returns `type: 'other'` (not `'page'`), causing Playwright's `crBrowser._onAttachedToTarget` to skip the new target. To opt in to full `ctx.newPage()` support, set `process.env.PW_CHROMIUM_ATTACH_TO_OTHER = '1'` **before** importing `@playwright/test` — Playwright's upstream escape hatch then treats `'other'` targets as pages. Trade-off: ArkWeb's internal `'other'` targets (shared workers, etc.) also get treated as pages, which can perturb tests that use `touchscreen.tap()` or `context.recordHar()`. For single-context tests (the common case) leave the env unset and isolate with `localStorage.clear()` + `page.reload()`. For device emulation use the `emulateDevice` fixture instead of `browser.newContext({ ...device })`.
129
+
130
+ > **v0.5.0 breaking change:** Previously `browser.newContext()` threw a friendly error when `PW_CHROMIUM_ATTACH_TO_OTHER` was unset. Now it succeeds for cookie/storage operations; `ctx.newPage()` will throw Playwright's natural `_page undefined` error instead.
125
131
 
126
132
  **Multi-worker mode.** To run tests in parallel across multiple workers, two things are required together:
127
133
 
@@ -132,15 +138,16 @@ Coordinates are CSS pixels relative to the viewport (same as `touchscreen.tap`).
132
138
  import { test, expect } from 'ohos-playwright/parallel'
133
139
  ```
134
140
 
135
- The `ohos-playwright/parallel` fixture opens one ArkWeb context per **test** via `browser.newContext()` (test-scoped) and one page via `ctx.newPage()`. Both are closed after each test. Cookies and localStorage are fully isolated between tests. All ArkWeb workarounds (goBack/goForward, popup interception, hover, evaluate→pageerror) apply identically.
141
+ The `ohos-playwright/parallel` fixture opens one ArkWeb context per **test** via `browser.newContext()` (test-scoped) and one page via `ctx.newPage()`. Both are closed after each test. Cookies and localStorage are fully isolated between tests. All ArkWeb workarounds (goBack/goForward, popup interception, hover, evaluate→pageerror propagation) apply identically.
136
142
 
137
143
  Trade-offs to be aware of:
138
144
  - **`newContext()` serialises internally in ArkWeb.** Concurrent `newContext()` calls on the same CDP connection are safe (~100 ms for 3 at once). The apparent ~3.4 s bottleneck was from concurrent `connectOverCDP` calls (connection handshakes serialise), not context creation itself. More than ~4 workers may not yield additional throughput.
139
145
  - **The default `ohos-playwright` fixture is not parallel-safe.** Its `context` fixture always returns `browser.contexts()[0]` — all workers would race on the same ArkWeb tab. If you set `workers > 1` with `PW_CHROMIUM_ATTACH_TO_OTHER=1` but keep the default `test` import, `withOpenHarmony()` will warn at startup.
140
146
  - **HTTP `User-Agent` header can be changed via CDP, but not via `setExtraHTTPHeaders`.** `Emulation.setUserAgentOverride` (sent by `emulateDevice({ userAgent })`) rewrites both `navigator.userAgent` and the outgoing HTTP UA header — call it before `page.goto()` so it applies to the destination page. `context.setExtraHTTPHeaders({ 'User-Agent': '...' })` does **not** override UA on ArkWeb (the header is preserved as browser default).
141
- - **`locator.hover()` activates CSS `:hover` on typical pages.** The fixture goes through the real `Input.dispatchMouseEvent` path via `page.mouse.move`. On pages where Playwright's `boundingBox()` hangs (e.g. some MutationObserver configurations), it falls back to a JS `mouseover` dispatch after a 5 s timeout — DOM listeners still fire but `:hover` will not activate in that fallback path.
142
- - **`page.mouse.move()` / `page.mouse.down()` / `page.mouse.up()` work normally.** DOM listeners receive `mousemove` / `mousedown` / `mouseup` / `click` in ArkWeb just as they do on stock Chromium. A previously documented narrow edge case (data: URL with embedded newlines AND a shared function reference registered for multiple event types) could not be reproduced in the v0.3.3 reaudit. The `mouseMove` / `mouseDown` / `mouseUp` fixtures remain in the API surface for backward compatibility but are no longer needed for normal use.
147
+ - **`locator.hover()` activates CSS `:hover`.** The override calls `boundingBox()` + `page.mouse.move()` (real `Input.dispatchMouseEvent` path). `page.mouse.move()` / `page.mouse.down()` / `page.mouse.up()` deliver DOM events on ArkWeb identically to stock Chromium.
143
148
  - **`emulateDevice({ isMobile: true })` does not apply the viewport** — see the note in the `emulateDevice` fixture section above.
149
+ - **`context.recordVideo` is not supported.** ArkWeb does not implement `Page.startScreencast` (the CDP command Playwright uses for video recording). When running against a remote Chromium via `OHOS_PW_CDP_URL`, Playwright's ffmpeg binary also cannot execute on HarmonyOS without signing. Use `page.screenshot()` for visual verification instead.
150
+ - **`launchOptions.proxy` is not applicable** in `connectOverCDP` mode — there is no launch phase where proxy settings can be injected. Use `page.route()` to intercept and rewrite requests at the Playwright layer.
144
151
  - **`exposeBinding({ handle: true })` is not supported.** Playwright 1.60's public `exposeBinding` signature is `(name, callback)` — the third `{ handle }` argument is silently ignored. The callback receives a serialized form of the argument (DOM nodes arrive as the string `"ref: <Node>"`), not a JSHandle. Use `exposeFunction` or a plain `exposeBinding` callback that reads element properties directly and returns a by-value object.
145
152
  - **`process.platform` reads `'linux'`** during the run — Playwright's `calculatePlatform()` only branches on linux/darwin/win32 (falls through to `<unknown>` on openharmony), and 20+ other sites in `playwright-core` read `process.platform` directly (UA string assembly, headful window insets, modifier keys, registry). The adapter patches `process.platform` to `'linux'` and pins `PLAYWRIGHT_HOST_PLATFORM_OVERRIDE=ubuntu24.04-arm64` for `calculatePlatform()`. For real platform checks use `process.env.OHOS_PW_HOST`.
146
153
 
@@ -219,7 +226,7 @@ Then set `OHOS_PW_BUNDLE` (e.g. `OHOS_PW_BUNDLE=com.quark.ohosbrowser`).
219
226
 
220
227
  **`CDP probe failed`** — leftover `hdc` forward rule from a prior crashed run. `hdc fport ls` to inspect, `hdc fport rm tcp:<port> localabstract:<socket>` to clear.
221
228
 
222
- **`page.goto('/foo')` doesn't prepend baseURL** — Playwright's standard behavior is `/foo` → `http://localhost:5173/foo`. If it's not working, check `use.baseURL` in `playwright.config.ts`.
229
+ **`page.goto('/foo')` doesn't prepend baseURL** — Playwright's standard behavior is `/foo` → `http://localhost:5173/foo`. The adapter injects `baseURL` directly into the context's internal options so Playwright's own URL resolution applies. If it's not working, verify `use.baseURL` is set in `playwright.config.ts` and that `withOpenHarmony()` wraps the config.
223
230
 
224
231
  ## License
225
232
 
package/dist/fixture.mjs CHANGED
@@ -38,10 +38,17 @@ export async function installPageWrappers(page, context, baseURL) {
38
38
  finally {
39
39
  await session.detach();
40
40
  }
41
- // Override goBack: Page.navigateToHistoryEntry hangs in ArkWeb (never resolves).
42
- // ArkWeb also does not emit Page.frameNavigated for history navigation, so waitForURL
43
- // never fires. Poll Page.getNavigationHistory.currentIndex instead.
44
- ;
41
+ // Override goto: connectOverCDP creates the server-side context with no baseURL in
42
+ // its _options, so Playwright's internal Frame.goto cannot resolve relative paths
43
+ // CDP rejects them as invalid. Resolve here before delegating to the real goto.
44
+ const savedGoto = page['goto'];
45
+ const origGoto = page.goto.bind(page);
46
+ page.goto = async (url, options) => {
47
+ if (baseURL && url && !url.includes('://') && !url.startsWith('about:') && !url.startsWith('data:')) {
48
+ url = new URL(url, baseURL).toString();
49
+ }
50
+ return origGoto(url, options);
51
+ };
45
52
  page.goBack = async (options) => {
46
53
  const timeout = options?.timeout ?? 30000;
47
54
  const s = await page.context().newCDPSession(page);
@@ -165,6 +172,7 @@ export async function installPageWrappers(page, context, baseURL) {
165
172
  clearInterval(popupPoller);
166
173
  page.evaluate = savedEvaluate;
167
174
  page.locator = savedLocator;
175
+ page.goto = savedGoto;
168
176
  if (opts?.navigateTo) {
169
177
  // Reset the shared tab to a neutral state — page.close() would terminate
170
178
  // the ArkWeb DevTools socket so we navigate instead.
@@ -179,31 +187,11 @@ export const test = base.extend({
179
187
  browser: [
180
188
  async ({}, use) => {
181
189
  const browser = await chromium.connectOverCDP(readEndpoint());
182
- // ArkWeb's Target.createTarget returns type='other', so Playwright's
183
- // crBrowser._onAttachedToTarget skips it and ctx.newPage() throws
184
- // "Cannot read properties of undefined (reading '_page')". The upstream
185
- // PW_CHROMIUM_ATTACH_TO_OTHER=1 escape hatch fixes newContext/newPage
186
- // but also makes Playwright treat internal "other" targets as pages
187
- // (perturbs touchscreen / recordHar). When the user has not opted in,
188
- // intercept browser.newContext() with a friendly, actionable error.
189
- if (!process.env.PW_CHROMIUM_ATTACH_TO_OTHER) {
190
- const origNewContext = browser.newContext.bind(browser);
191
- browser.newContext = (() => {
192
- throw new Error('browser.newContext() is not supported on ArkWeb unless you opt in via ' +
193
- 'process.env.PW_CHROMIUM_ATTACH_TO_OTHER=\'1\' before importing @playwright/test. ' +
194
- 'See ohos-playwright README "Limitations" → "Multi-context" for trade-offs.');
195
- });
196
- try {
197
- await use(browser);
198
- }
199
- finally {
200
- ;
201
- browser.newContext = origNewContext;
202
- }
203
- }
204
- else {
205
- await use(browser);
206
- }
190
+ // browser.newContext() + addCookies / storageState() work in connectOverCDP mode.
191
+ // ctx.newPage() requires PW_CHROMIUM_ATTACH_TO_OTHER=1 on ArkWeb (Target.createTarget
192
+ // returns type='other' so Playwright skips the new target); without it Playwright
193
+ // throws its natural "_page undefined" error.
194
+ await use(browser);
207
195
  },
208
196
  { scope: 'worker' },
209
197
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohos-playwright",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Playwright adapter for OpenHarmony / ArkWeb via hdc + CDP",
5
5
  "license": "MIT",
6
6
  "author": "social4hyq",