ohos-playwright 0.5.0 → 0.5.2

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
@@ -56,7 +56,7 @@ The following Playwright APIs have been validated on ArkWeb / HarmonyOS 6.1 (Chr
56
56
  | File upload | `page.setInputFiles()` — fires `change`, file content readable |
57
57
  | Cookies | `context.addCookies()`, `context.cookies()`, `context.clearCookies()` |
58
58
  | Dialog | `page.on('dialog')`, `dialog.accept()`, `dialog.dismiss()`, `dialog.message()`, `dialog.type()` |
59
- | Popup | `context.waitForEvent('page')` + `window.open()` — stub Page with `url()`, `waitForLoadState()`, `close()` |
59
+ | Popup | `context.waitForEvent('page')` + `window.open()` — real Page with full API. Requires `PW_CHROMIUM_ATTACH_TO_OTHER=1`. Falls back to idle-tab proxy or minimal stub when Target.createTarget is unavailable. |
60
60
  | Page events | `page.on('pageerror')`, `page.on('console')`, `page.on('download')` |
61
61
  | Script / style injection | `page.addScriptTag({ content \| path \| type:'module' })`, `page.addStyleTag({ content })` |
62
62
  | Init script | `page.addInitScript()` — function or string, persists across `goto()` navigations |
@@ -27,6 +27,7 @@ export interface StorageState {
27
27
  export type PageCleanup = (opts?: {
28
28
  navigateTo?: string;
29
29
  }) => Promise<void>;
30
+ export declare function createPopupPage(context: BrowserContext, seedPage: Page, popupUrl: string): Promise<Page | null>;
30
31
  export declare function installPageWrappers(page: Page, context: BrowserContext, baseURL: string | undefined): Promise<PageCleanup>;
31
32
  export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
32
33
  emulateDevice: (descriptor: DeviceDescriptor) => Promise<void>;
package/dist/fixture.mjs CHANGED
@@ -16,6 +16,64 @@ function readInfo() {
16
16
  return JSON.parse(readFileSync(INFO_PATH, 'utf8'));
17
17
  }
18
18
  function readEndpoint() { return readInfo().endpoint; }
19
+ // Create a real Page in the default context via Target.createTarget.
20
+ // Returns the new page (already navigated to popupUrl) on success, or null
21
+ // to let the caller fall back to idle-tab proxy or stub.
22
+ //
23
+ // Precondition: PW_CHROMIUM_ATTACH_TO_OTHER=1 must be set, otherwise the new
24
+ // target created by ArkWeb will be type:'other' and Playwright won't pick
25
+ // it up into ctx.pages().
26
+ export async function createPopupPage(context, seedPage, popupUrl) {
27
+ let session = null;
28
+ try {
29
+ session = await context.newCDPSession(seedPage);
30
+ const r = await Promise.race([
31
+ session
32
+ .send('Target.createTarget', { url: 'about:blank' }),
33
+ new Promise((_, rej) => setTimeout(() => rej(new Error('createTarget timeout')), 3000)),
34
+ ]);
35
+ if (!r.targetId)
36
+ return null;
37
+ // Poll ctx.pages() until Playwright picks up the new target (max 2s).
38
+ const pagesBefore = context.pages().length;
39
+ const deadline = Date.now() + 2000;
40
+ while (Date.now() < deadline) {
41
+ if (context.pages().length > pagesBefore)
42
+ break;
43
+ await new Promise((r) => setTimeout(r, 50));
44
+ }
45
+ const allPages = context.pages();
46
+ if (allPages.length <= pagesBefore)
47
+ return null;
48
+ // Pick the newly-added page (any page not equal to seedPage, preferring
49
+ // about:blank which is the createTarget's initial URL).
50
+ const newPage = allPages.find((p) => p !== seedPage && p.url() === 'about:blank') ??
51
+ allPages.find((p) => p !== seedPage);
52
+ if (!newPage)
53
+ return null;
54
+ // Navigate to the popup URL (skip for about:blank which is already loaded).
55
+ // On navigation failure, close the tab and return null — otherwise a
56
+ // half-loaded popup tab (url stuck at about:blank) gets mistaken for the
57
+ // launchUrl tab by the next test's fixture-page selector.
58
+ if (popupUrl && popupUrl !== 'about:blank') {
59
+ try {
60
+ await newPage.goto(popupUrl, { timeout: 5000 });
61
+ }
62
+ catch {
63
+ await newPage.close().catch(() => { });
64
+ return null;
65
+ }
66
+ }
67
+ return newPage;
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ finally {
73
+ if (session)
74
+ await session.detach().catch(() => { });
75
+ }
76
+ }
19
77
  export async function installPageWrappers(page, context, baseURL) {
20
78
  const ctxEmit = context.emit.bind(context);
21
79
  // connectOverCDP reuses an existing tab — Playwright has no record of its
@@ -38,10 +96,17 @@ export async function installPageWrappers(page, context, baseURL) {
38
96
  finally {
39
97
  await session.detach();
40
98
  }
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
- ;
99
+ // Override goto: connectOverCDP creates the server-side context with no baseURL in
100
+ // its _options, so Playwright's internal Frame.goto cannot resolve relative paths
101
+ // CDP rejects them as invalid. Resolve here before delegating to the real goto.
102
+ const savedGoto = page['goto'];
103
+ const origGoto = page.goto.bind(page);
104
+ page.goto = async (url, options) => {
105
+ if (baseURL && url && !url.includes('://') && !url.startsWith('about:') && !url.startsWith('data:')) {
106
+ url = new URL(url, baseURL).toString();
107
+ }
108
+ return origGoto(url, options);
109
+ };
45
110
  page.goBack = async (options) => {
46
111
  const timeout = options?.timeout ?? 30000;
47
112
  const s = await page.context().newCDPSession(page);
@@ -136,14 +201,39 @@ export async function installPageWrappers(page, context, baseURL) {
136
201
  return q;
137
202
  });
138
203
  for (const { url } of pending ?? []) {
139
- // context.newPage() calls Target.createTarget which hangs in ArkWeb.
140
- // Emit a minimal stub — satisfies waitForLoadState / url / close.
141
- const stub = {
142
- waitForLoadState: async () => { },
143
- url: () => url,
144
- close: async () => { },
145
- };
146
- ctxEmit('page', stub);
204
+ // 1) Target.createTarget(首选)
205
+ let emitted = null;
206
+ try {
207
+ emitted = await createPopupPage(context, page, url || 'about:blank');
208
+ }
209
+ catch { }
210
+ // 2) Fallback A:默认 context 闲置 about:blank tab
211
+ if (!emitted) {
212
+ const idle = context
213
+ .pages()
214
+ .find((p) => p !== page && p.url() === 'about:blank');
215
+ if (idle) {
216
+ try {
217
+ if (url && url !== 'about:blank') {
218
+ await idle.goto(url, { timeout: 5000 });
219
+ }
220
+ emitted = idle;
221
+ }
222
+ catch { }
223
+ }
224
+ }
225
+ // 3) Fallback B:退回原 stub(保持兼容)
226
+ if (!emitted) {
227
+ const stub = {
228
+ waitForLoadState: async () => { },
229
+ url: () => url,
230
+ close: async () => { },
231
+ };
232
+ ctxEmit('page', stub);
233
+ }
234
+ else {
235
+ ctxEmit('page', emitted);
236
+ }
147
237
  }
148
238
  }
149
239
  catch { }
@@ -165,6 +255,7 @@ export async function installPageWrappers(page, context, baseURL) {
165
255
  clearInterval(popupPoller);
166
256
  page.evaluate = savedEvaluate;
167
257
  page.locator = savedLocator;
258
+ page.goto = savedGoto;
168
259
  if (opts?.navigateTo) {
169
260
  // Reset the shared tab to a neutral state — page.close() would terminate
170
261
  // the ArkWeb DevTools socket so we navigate instead.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ohos-playwright",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Playwright adapter for OpenHarmony / ArkWeb via hdc + CDP",
5
5
  "license": "MIT",
6
6
  "author": "social4hyq",