@open-wa/driver-puppeteer 5.0.0-alpha.8 → 5.0.0
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/LICENSE.md +2 -2
- package/README.md +1 -1
- package/dist/index.cjs +10 -4
- package/dist/index.d.cts +9 -10
- package/dist/index.d.cts.map +1 -1
- package/package.json +11 -7
- package/.turbo/turbo-build.log +0 -15
- package/CHANGELOG.md +0 -70
- package/src/PuppeteerBrowser.ts +0 -46
- package/src/PuppeteerDriver.ts +0 -91
- package/src/PuppeteerElementHandle.ts +0 -32
- package/src/PuppeteerPage.ts +0 -470
- package/src/__tests__/PuppeteerPage.test.ts +0 -149
- package/src/index.ts +0 -4
- package/tsconfig.json +0 -10
package/LICENSE.md
CHANGED
|
@@ -55,9 +55,9 @@ Licensor hereby grants permission by this license ("License"), free of charge,
|
|
|
55
55
|
4. Compliance with Human Rights Laws and Human Rights Principles:
|
|
56
56
|
|
|
57
57
|
a. **Human Rights Laws**. The Software shall not be used by any person or entity for any systems, activities, or other uses that violate any applicable laws, regulations, or rules that protect human, civil, labor, privacy, political, environmental, security, economic, due process, or similar rights (the "Human Rights Laws"). Where the Human Rights Laws of more than one jurisdiction are applicable to the use of the Software, the Human Rights Laws that are most protective of the individuals or groups harmed shall apply.
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
b. **Human Rights Principles**. Licensee is advised to consult the articles of the United Nations Universal Declaration of Human Rights (https://www.un.org/en/universal-declaration-human-rights/) and the United Nations Global Compact (https://www.unglobalcompact.org/ what-is-gc/mission/principles) that define recognized principles of international human rights (the "Human Rights Principles"). It is Licensor's express intent that all use of the Software be consistent with Human Rights Principles. If Licensor receives notification or otherwise learns of an alleged violation of any Human Rights Principles relating to Licensee's use of the Software, Licensor may in its discretion and without obligation (i) (a) notify Licensee of such allegation and (b) allow Licensee 90 days from notification under (i)(a) to investigate and respond to Licensor regarding the allegation and (ii) (a) after the earlier of 90 days from notification under (i)(a), or Licensee's response under (i)(b), notify Licensee of License termination and (b) allow Licensee an additional 90 days from notification under (ii)(a) to cease use of the Software.
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
c. **Indemnity**. Licensee shall hold harmless and indemnify Licensor against all losses, damages, liabilities, deficiencies, claims, actions, judgments, settlements, interest, awards, penalties, fines, costs, or expenses of whatever kind, including Licensor's reasonable attorneys' fees, arising out of or relating to Licensee's non-compliance with this License or use of the Software in violation of Human Rights Laws or Human Rights Principles.
|
|
62
62
|
|
|
63
63
|
5. Enforceability: If any portion or provision of this License is determined to be invalid, illegal, or unenforceable by a court of competent jurisdiction, then such invalidity, illegality, or unenforceability shall not affect any other term or provision of this License or invalidate or render unenforceable such term or provision in any other jurisdiction. Upon a determination that any term or provision is invalid, illegal, or unenforceable, to the extent permitted by applicable law, the court may modify this License to affect the original intent of the parties as closely as possible. The section headings are for convenience only and are not intended to affect the construction or interpretation of this License. Any rule of construction to the effect that ambiguities are to be resolved against the drafting party shall not apply in interpreting this License. The language in this License shall be interpreted as to its fair meaning and not strictly for or against any party.
|
package/README.md
CHANGED
|
@@ -16,4 +16,4 @@ See the [docs site](https://openwa.dev).
|
|
|
16
16
|
|
|
17
17
|
## License
|
|
18
18
|
|
|
19
|
-
[H-DNH
|
|
19
|
+
[H-DNH 1.1](https://github.com/open-wa/wa-automate-nodejs/blob/master/LICENSE.md) — Hippocratic + Do Not Harm
|
package/dist/index.cjs
CHANGED
|
@@ -319,7 +319,7 @@ var PuppeteerPage = class {
|
|
|
319
319
|
async stopTracing() {
|
|
320
320
|
this.require("tracing");
|
|
321
321
|
const buffer = await this.page.tracing.stop();
|
|
322
|
-
return buffer ? new Uint8Array(buffer) : new Uint8Array();
|
|
322
|
+
return buffer ? new Uint8Array(buffer) : /* @__PURE__ */ new Uint8Array();
|
|
323
323
|
}
|
|
324
324
|
unwrap() {
|
|
325
325
|
return this.page;
|
|
@@ -389,11 +389,14 @@ var PuppeteerBrowser = class {
|
|
|
389
389
|
await this.browser.close();
|
|
390
390
|
}
|
|
391
391
|
isConnected() {
|
|
392
|
-
return this.browser.
|
|
392
|
+
return this.browser.connected;
|
|
393
393
|
}
|
|
394
394
|
async versionString() {
|
|
395
395
|
return await this.browser.version();
|
|
396
396
|
}
|
|
397
|
+
processId() {
|
|
398
|
+
return this.browser.process()?.pid;
|
|
399
|
+
}
|
|
397
400
|
unwrap() {
|
|
398
401
|
return this.browser;
|
|
399
402
|
}
|
|
@@ -418,7 +421,9 @@ var PuppeteerDriver = class {
|
|
|
418
421
|
},
|
|
419
422
|
persistentContext: { supported: true },
|
|
420
423
|
browserExtensions: { supported: true },
|
|
421
|
-
exposeBinding: { supported: true }
|
|
424
|
+
exposeBinding: { supported: true },
|
|
425
|
+
screenshot: { supported: true },
|
|
426
|
+
rendering: { supported: true }
|
|
422
427
|
};
|
|
423
428
|
}
|
|
424
429
|
async init(ctx) {
|
|
@@ -427,7 +432,8 @@ var PuppeteerDriver = class {
|
|
|
427
432
|
this.puppeteer = pExtra.default ?? pExtra;
|
|
428
433
|
try {
|
|
429
434
|
const { createRequire } = await import("node:module");
|
|
430
|
-
|
|
435
|
+
const pkg = createRequire(__filename)("puppeteer/package.json");
|
|
436
|
+
this.version = pkg.version;
|
|
431
437
|
} catch {}
|
|
432
438
|
this.ctx?.logger?.info("PuppeteerDriver initialized", { version: this.version });
|
|
433
439
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { ConnectOptions, DisposableHandle, DriverCapabilities, IBrowser, IDriver, IDriverContext, IElementHandle, IFrame, IPage, LaunchOptions, NavigationWaitUntil, PageEventHandler, WaitForFunctionOptions } from "@open-wa/driver-interface";
|
|
2
2
|
import { Browser, CDPSession, ElementHandle, Page } from "puppeteer";
|
|
3
|
-
|
|
4
3
|
//#region src/PuppeteerDriver.d.ts
|
|
5
|
-
declare class PuppeteerDriver implements IDriver {
|
|
6
|
-
readonly name:
|
|
4
|
+
export declare class PuppeteerDriver implements IDriver {
|
|
5
|
+
readonly name: 'puppeteer';
|
|
7
6
|
version?: string;
|
|
8
7
|
readonly capabilities: DriverCapabilities;
|
|
9
8
|
private puppeteer;
|
|
@@ -18,10 +17,10 @@ declare class PuppeteerDriver implements IDriver {
|
|
|
18
17
|
}
|
|
19
18
|
//#endregion
|
|
20
19
|
//#region src/PuppeteerBrowser.d.ts
|
|
21
|
-
declare class PuppeteerBrowser implements IBrowser {
|
|
20
|
+
export declare class PuppeteerBrowser implements IBrowser {
|
|
22
21
|
private browser;
|
|
23
22
|
private capabilities;
|
|
24
|
-
readonly name:
|
|
23
|
+
readonly name: 'puppeteer';
|
|
25
24
|
constructor(browser: Browser, capabilities: DriverCapabilities);
|
|
26
25
|
newPage(options?: {
|
|
27
26
|
clearFirstPage?: boolean;
|
|
@@ -30,14 +29,15 @@ declare class PuppeteerBrowser implements IBrowser {
|
|
|
30
29
|
close(): Promise<void>;
|
|
31
30
|
isConnected(): boolean;
|
|
32
31
|
versionString(): Promise<string>;
|
|
32
|
+
processId(): number | undefined;
|
|
33
33
|
unwrap(): Browser;
|
|
34
34
|
}
|
|
35
35
|
//#endregion
|
|
36
36
|
//#region src/PuppeteerPage.d.ts
|
|
37
|
-
declare class PuppeteerPage implements IPage {
|
|
37
|
+
export declare class PuppeteerPage implements IPage {
|
|
38
38
|
private page;
|
|
39
39
|
private capabilities;
|
|
40
|
-
readonly name:
|
|
40
|
+
readonly name: 'puppeteer';
|
|
41
41
|
private readonly listenerWrappers;
|
|
42
42
|
constructor(page: Page, capabilities: DriverCapabilities);
|
|
43
43
|
goto(url: string, options?: {
|
|
@@ -60,7 +60,7 @@ declare class PuppeteerPage implements IPage {
|
|
|
60
60
|
waitForSelector(selector: string, options?: {
|
|
61
61
|
timeoutMs?: number;
|
|
62
62
|
}): Promise<IElementHandle | null>;
|
|
63
|
-
waitForFunction
|
|
63
|
+
waitForFunction(script: string, options?: WaitForFunctionOptions): Promise<void>;
|
|
64
64
|
waitForFunction<Arg>(fn: (arg: Arg) => boolean, arg: Arg, options?: WaitForFunctionOptions): Promise<void>;
|
|
65
65
|
$(selector: string): Promise<IElementHandle | null>;
|
|
66
66
|
$$(selector: string): Promise<IElementHandle[]>;
|
|
@@ -98,7 +98,7 @@ declare class PuppeteerPage implements IPage {
|
|
|
98
98
|
}
|
|
99
99
|
//#endregion
|
|
100
100
|
//#region src/PuppeteerElementHandle.d.ts
|
|
101
|
-
declare class PuppeteerElementHandle implements IElementHandle {
|
|
101
|
+
export declare class PuppeteerElementHandle implements IElementHandle {
|
|
102
102
|
private element;
|
|
103
103
|
constructor(element: ElementHandle);
|
|
104
104
|
click(): Promise<void>;
|
|
@@ -109,5 +109,4 @@ declare class PuppeteerElementHandle implements IElementHandle {
|
|
|
109
109
|
unwrap(): ElementHandle;
|
|
110
110
|
}
|
|
111
111
|
//#endregion
|
|
112
|
-
export { PuppeteerBrowser, PuppeteerDriver, PuppeteerElementHandle, PuppeteerPage };
|
|
113
112
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/PuppeteerDriver.ts","../src/PuppeteerBrowser.ts","../src/PuppeteerPage.ts","../src/PuppeteerElementHandle.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/PuppeteerDriver.ts","../src/PuppeteerBrowser.ts","../src/PuppeteerPage.ts","../src/PuppeteerElementHandle.ts"],"mappings":";;;qBAGa,2BAA2B;WAC3B;EACT;WACS,cAAc;UAEf;UACA;EAER;EAgBM,KAAK,MAAM,iBAAiB;EAgB5B,OAAO,UAAU,gBAAgB,QAAQ;EAoBzC,QAAQ,SAAS,iBAAiB,QAAQ;EAWhD;EAIA,IAAI,kBAAkB,KAAK;EAI3B,QAAQ,kBAAkB,KAAK;;;;qBC9EtB,4BAA4B;UAIzB;UACA;WAJH;EAET,YACY,SAAS,SACT,cAAc;EAGpB,QAAQ;IACV;MACA,QAAQ;EAWN,SAAS,QAAQ;EAKjB,SAAS;EAIf;EAIM,iBAAiB;EAIvB;EAIA,UAAU;;;;qBCoHD,yBAAyB;UAKtB;UACA;WALH;mBACQ;EAEjB,YACY,MAAM,MACN,cAAc;EAGpB,KAAK,aAAa;IAAY,YAAY;IAAqB;MAAuB;EAO5F;EAIM,UAAU;EAIhB,aAAa;EAIP,sBAAsB,KAAK,KAC7B,KAAK,KAAK,QAAQ,MAAM,QAAQ,MAChC,KAAK,MACN;EAIG,SAAS,KAAK,KAAK,KAAK,KAAK,QAAQ,MAAM,QAAQ,MAAM,KAAK,MAAM,QAAQ;EAS5E,eAAe,eAAe,iBAAiB,QAAQ;EAQvD,cAAc,iBAAiB,QAAQ;EAiBvC,YAAY;IAAY;IAAe;MAAmB;EAI1D,aAAa,aAAa;EAI1B,uBAAuB,mBAAmB;EAK1C,gBAAgB,kBAAkB;IAAY;MAAuB,QAAQ;EAY7E,gBAAgB,gBAAgB,UAAU,yBAAyB;EACnE,gBAAgB,KAAK,KAAK,KAAK,iBAAiB,KAAK,KAAK,UAAU,yBAAyB;EAoC7F,EAAE,mBAAmB,QAAQ;EAK7B,GAAG,mBAAmB,QAAQ;EAK9B,MAAM,mBAAmB;EAIzB,KAAK,kBAAkB,cAAc;IAAY;MAAqB;EAItE,WAAW;IAAY;IAAuB;MAAuB,QAAQ;EAK7E,eAAe,cAAc,QAAQ,sBAAsB;EAIjE,GAAG,kBAAkB,OAAO,GAAG,cAAc,uBAAuB,iBAAiB,wBAAwB;EAiB7G,IAAI,kBAAkB,OAAO,GAAG,cAAc,uBAAuB,iBAAiB;EAchF,SAAS;EAIf;EAIA,IAAI,kBAAkB,KAAK;EAI3B,QAAQ,kBAAkB,KAAK;EAUzB,OAAO,QAAQ;EAKf,uBAAuB,kBAAkB;EAKzC,IAAI;IAAY;IAAe;MAAoB,QAAQ;EAM3D,aAAa;IAAY;IAAe;MAA0B;EAKlE,eAAe,QAAQ;EAM7B,UAAU;UAIF;UAgBA;UAkBA;UAcA;;;;qBCjcC,kCAAkC;UACvB;EAApB,YAAoB,SAAS;EAEvB,SAAS;EAIT,KAAK,eAAe;EAIpB,aAAa,eAAe;EAK5B,eAAe;EAKf,WAAW;EAIjB,UAAU"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-wa/driver-puppeteer",
|
|
3
|
-
"version": "5.0.0
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Puppeteer driver implementation for open-wa",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"types": "./dist/index.d.cts",
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"
|
|
16
|
-
"puppeteer
|
|
17
|
-
"
|
|
15
|
+
"@open-wa/driver-interface": "5.0.0",
|
|
16
|
+
"puppeteer": "^25.12.0",
|
|
17
|
+
"puppeteer-extra": "^3.3.6"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@types/node": "^
|
|
21
|
-
"typescript": "^
|
|
22
|
-
"vitest": "
|
|
20
|
+
"@types/node": "^26.6.3",
|
|
21
|
+
"typescript": "^7.0.2",
|
|
22
|
+
"vitest": "5.0.2"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"type": "git",
|
|
29
29
|
"url": "https://github.com/open-wa/wa-automate-nodejs.git"
|
|
30
30
|
},
|
|
31
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
31
35
|
"scripts": {
|
|
32
36
|
"build": "tsdown src/index.ts --format cjs",
|
|
33
37
|
"dev": "tsdown src/index.ts --format cjs --watch",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
$ tsdown src/index.ts --format cjs
|
|
2
|
-
[34mℹ[39m [34mtsdown v0.21.7[39m powered by [38;2;255;126;23mrolldown v1.0.0-rc.12[39m
|
|
3
|
-
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
4
|
-
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
5
|
-
[34mℹ[39m Build start
|
|
6
|
-
[34mℹ[39m [2mdist/[22m[1mindex.cjs[22m [2m13.63 kB[22m [2m│ gzip: 3.39 kB[22m
|
|
7
|
-
[34mℹ[39m 1 files, total: 13.63 kB
|
|
8
|
-
[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugin `tsdown:deps`. See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
9
|
-
|
|
10
|
-
[34mℹ[39m [2mdist/[22mindex.d.cts.map [2m2.94 kB[22m [2m│ gzip: 0.91 kB[22m
|
|
11
|
-
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.cts[22m[39m [2m4.28 kB[22m [2m│ gzip: 1.25 kB[22m
|
|
12
|
-
[34mℹ[39m 2 files, total: 7.22 kB
|
|
13
|
-
[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugin `rolldown-plugin-dts:generate`. See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
14
|
-
|
|
15
|
-
[32m✔[39m Build complete in [32m5777ms[39m
|
package/CHANGELOG.md
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# @open-wa/driver-puppeteer
|
|
2
|
-
|
|
3
|
-
## 5.0.0-alpha.8
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- Updated dependencies []:
|
|
8
|
-
- @open-wa/driver-interface@5.0.0-alpha.8
|
|
9
|
-
|
|
10
|
-
## 5.0.0-alpha.7
|
|
11
|
-
|
|
12
|
-
### Patch Changes
|
|
13
|
-
|
|
14
|
-
- Updated dependencies []:
|
|
15
|
-
- @open-wa/driver-interface@5.0.0-alpha.7
|
|
16
|
-
|
|
17
|
-
## 5.0.0-alpha.6
|
|
18
|
-
|
|
19
|
-
### Patch Changes
|
|
20
|
-
|
|
21
|
-
- Updated dependencies []:
|
|
22
|
-
- @open-wa/driver-interface@5.0.0-alpha.6
|
|
23
|
-
|
|
24
|
-
## 5.0.0-alpha.5
|
|
25
|
-
|
|
26
|
-
### Patch Changes
|
|
27
|
-
|
|
28
|
-
- Updated dependencies []:
|
|
29
|
-
- @open-wa/driver-interface@5.0.0-alpha.5
|
|
30
|
-
|
|
31
|
-
## 5.0.0-alpha.4
|
|
32
|
-
|
|
33
|
-
### Patch Changes
|
|
34
|
-
|
|
35
|
-
- Updated dependencies []:
|
|
36
|
-
- @open-wa/driver-interface@5.0.0-alpha.4
|
|
37
|
-
|
|
38
|
-
## 5.0.0-alpha.3
|
|
39
|
-
|
|
40
|
-
### Patch Changes
|
|
41
|
-
|
|
42
|
-
- Updated dependencies []:
|
|
43
|
-
- @open-wa/driver-interface@5.0.0-alpha.3
|
|
44
|
-
|
|
45
|
-
## 5.0.0-alpha.2
|
|
46
|
-
|
|
47
|
-
### Patch Changes
|
|
48
|
-
|
|
49
|
-
- Updated dependencies []:
|
|
50
|
-
- @open-wa/driver-interface@5.0.0-alpha.2
|
|
51
|
-
|
|
52
|
-
## 6.0.0-alpha.2
|
|
53
|
-
|
|
54
|
-
### Minor Changes
|
|
55
|
-
|
|
56
|
-
- Dry run minor bump
|
|
57
|
-
|
|
58
|
-
### Patch Changes
|
|
59
|
-
|
|
60
|
-
- Updated dependencies []:
|
|
61
|
-
- @open-wa/driver-interface@6.0.0-alpha.2
|
|
62
|
-
|
|
63
|
-
## 5.0.1-alpha.2
|
|
64
|
-
|
|
65
|
-
### Patch Changes
|
|
66
|
-
|
|
67
|
-
- Dry run patch bump
|
|
68
|
-
|
|
69
|
-
- Updated dependencies []:
|
|
70
|
-
- @open-wa/driver-interface@5.0.1-alpha.2
|
package/src/PuppeteerBrowser.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { IBrowser, IPage, DriverCapabilities } from '@open-wa/driver-interface';
|
|
2
|
-
import type { Browser } from 'puppeteer';
|
|
3
|
-
import { PuppeteerPage } from './PuppeteerPage';
|
|
4
|
-
|
|
5
|
-
export class PuppeteerBrowser implements IBrowser {
|
|
6
|
-
readonly name = 'puppeteer' as const;
|
|
7
|
-
|
|
8
|
-
constructor(
|
|
9
|
-
private browser: Browser,
|
|
10
|
-
private capabilities: DriverCapabilities
|
|
11
|
-
) { }
|
|
12
|
-
|
|
13
|
-
async newPage(options?: {
|
|
14
|
-
clearFirstPage?: boolean
|
|
15
|
-
}): Promise<IPage> {
|
|
16
|
-
const page = await this.browser.newPage();
|
|
17
|
-
if (options?.clearFirstPage) {
|
|
18
|
-
const pages = await this.browser.pages();
|
|
19
|
-
if (pages.length > 0) {
|
|
20
|
-
await pages[0].close();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return new PuppeteerPage(page, this.capabilities);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async pages(): Promise<IPage[]> {
|
|
27
|
-
const pages = await this.browser.pages();
|
|
28
|
-
return pages.map(p => new PuppeteerPage(p, this.capabilities));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async close(): Promise<void> {
|
|
32
|
-
await this.browser.close();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
isConnected(): boolean {
|
|
36
|
-
return this.browser.isConnected();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async versionString(): Promise<string> {
|
|
40
|
-
return await this.browser.version();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
unwrap(): Browser {
|
|
44
|
-
return this.browser;
|
|
45
|
-
}
|
|
46
|
-
}
|
package/src/PuppeteerDriver.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { IDriver, IBrowser, LaunchOptions, ConnectOptions, IDriverContext, DriverCapabilities } from '@open-wa/driver-interface';
|
|
2
|
-
import { PuppeteerBrowser } from './PuppeteerBrowser';
|
|
3
|
-
|
|
4
|
-
export class PuppeteerDriver implements IDriver {
|
|
5
|
-
readonly name = 'puppeteer' as const;
|
|
6
|
-
version?: string;
|
|
7
|
-
readonly capabilities: DriverCapabilities;
|
|
8
|
-
|
|
9
|
-
private puppeteer: any;
|
|
10
|
-
private ctx?: IDriverContext;
|
|
11
|
-
|
|
12
|
-
constructor() {
|
|
13
|
-
this.capabilities = {
|
|
14
|
-
cdp: { supported: true },
|
|
15
|
-
requestInterception: { supported: true },
|
|
16
|
-
serviceWorkerBypass: { supported: true },
|
|
17
|
-
stealth: { supported: true, notes: 'Via puppeteer-extra plugins' },
|
|
18
|
-
pdf: { supported: true },
|
|
19
|
-
tracing: { supported: true, notes: 'Basic tracing support' },
|
|
20
|
-
persistentContext: { supported: true },
|
|
21
|
-
browserExtensions: { supported: true },
|
|
22
|
-
exposeBinding: { supported: true },
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async init(ctx?: IDriverContext): Promise<void> {
|
|
27
|
-
this.ctx = ctx;
|
|
28
|
-
|
|
29
|
-
const pExtra = await import('puppeteer-extra');
|
|
30
|
-
this.puppeteer = pExtra.default ?? pExtra;
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const { createRequire } = await import('node:module');
|
|
34
|
-
const _require = createRequire(__filename);
|
|
35
|
-
const pkg = _require('puppeteer/package.json');
|
|
36
|
-
this.version = pkg.version;
|
|
37
|
-
} catch {}
|
|
38
|
-
|
|
39
|
-
this.ctx?.logger?.info('PuppeteerDriver initialized', { version: this.version });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async launch(options?: LaunchOptions): Promise<IBrowser> {
|
|
43
|
-
if (!this.puppeteer) await this.init();
|
|
44
|
-
|
|
45
|
-
const browser = await this.puppeteer.launch({
|
|
46
|
-
headless: options?.headless ?? true,
|
|
47
|
-
executablePath: options?.executablePath,
|
|
48
|
-
args: options?.args || [],
|
|
49
|
-
defaultViewport: options?.defaultViewport,
|
|
50
|
-
userDataDir: options?.userDataDir,
|
|
51
|
-
timeout: options?.timeoutMs,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const browserVersion = await browser.version();
|
|
56
|
-
this.ctx?.logger?.info('Browser executable version', { version: browserVersion });
|
|
57
|
-
} catch {}
|
|
58
|
-
|
|
59
|
-
return new PuppeteerBrowser(browser, this.capabilities);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async connect(options: ConnectOptions): Promise<IBrowser> {
|
|
63
|
-
if (!this.puppeteer) await this.init();
|
|
64
|
-
|
|
65
|
-
const browser = await this.puppeteer.connect({
|
|
66
|
-
browserWSEndpoint: options.wsEndpoint,
|
|
67
|
-
timeout: options?.timeoutMs,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
return new PuppeteerBrowser(browser, this.capabilities);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
unwrap(): any {
|
|
74
|
-
return this.puppeteer;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
has<C extends string>(cap: C): boolean {
|
|
78
|
-
return (this.capabilities as any)[cap]?.supported === true;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
require<C extends string>(cap: C): void {
|
|
82
|
-
if (!this.has(cap)) {
|
|
83
|
-
const capability = (this.capabilities as any)[cap];
|
|
84
|
-
throw new Error(
|
|
85
|
-
`Driver '${this.name}' does not support capability '${cap}'${
|
|
86
|
-
capability?.reason ? `: ${capability.reason}` : ''
|
|
87
|
-
}`
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { IElementHandle } from '@open-wa/driver-interface';
|
|
2
|
-
import type { ElementHandle } from 'puppeteer';
|
|
3
|
-
|
|
4
|
-
export class PuppeteerElementHandle implements IElementHandle {
|
|
5
|
-
constructor(private element: ElementHandle) {}
|
|
6
|
-
|
|
7
|
-
async click(): Promise<void> {
|
|
8
|
-
await this.element.click();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async type(text: string): Promise<void> {
|
|
12
|
-
await this.element.type(text);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async getAttribute(name: string): Promise<string | null> {
|
|
16
|
-
const value = await this.element.evaluate((el, attr) => el.getAttribute(attr), name);
|
|
17
|
-
return value;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async textContent(): Promise<string | null> {
|
|
21
|
-
const text = await this.element.evaluate(el => el.textContent);
|
|
22
|
-
return text;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async dispose(): Promise<void> {
|
|
26
|
-
await this.element.dispose();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
unwrap(): ElementHandle {
|
|
30
|
-
return this.element;
|
|
31
|
-
}
|
|
32
|
-
}
|
package/src/PuppeteerPage.ts
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IPage,
|
|
3
|
-
IElementHandle,
|
|
4
|
-
DriverCapabilities,
|
|
5
|
-
type DisposableHandle,
|
|
6
|
-
type IConsoleMessage,
|
|
7
|
-
type IFrame,
|
|
8
|
-
type IRequest,
|
|
9
|
-
type IRequestContinueOverrides,
|
|
10
|
-
type IRequestResponse,
|
|
11
|
-
type NavigationWaitUntil,
|
|
12
|
-
type PageEventHandler,
|
|
13
|
-
type WaitForFunctionOptions,
|
|
14
|
-
} from '@open-wa/driver-interface';
|
|
15
|
-
import type { ConsoleMessage, Frame, HTTPRequest, Page, CDPSession } from 'puppeteer';
|
|
16
|
-
import { PuppeteerElementHandle } from './PuppeteerElementHandle';
|
|
17
|
-
|
|
18
|
-
type ListenerWrapper = (...args: any[]) => void | Promise<void>;
|
|
19
|
-
|
|
20
|
-
class PuppeteerDisposableHandle implements DisposableHandle {
|
|
21
|
-
constructor(private readonly disposer: () => Promise<void> | void) { }
|
|
22
|
-
|
|
23
|
-
dispose(): Promise<void> | void {
|
|
24
|
-
return this.disposer();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class PuppeteerFrame implements IFrame {
|
|
29
|
-
constructor(private readonly frame: Frame, private readonly isMain: boolean) { }
|
|
30
|
-
|
|
31
|
-
url(): string {
|
|
32
|
-
return this.frame.url();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
name(): string {
|
|
36
|
-
return this.frame.name();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
isMainFrame(): boolean {
|
|
40
|
-
return this.isMain;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
parentFrame(): IFrame | null {
|
|
44
|
-
const parent = this.frame.parentFrame();
|
|
45
|
-
return parent ? new PuppeteerFrame(parent, false) : null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async content(): Promise<string> {
|
|
49
|
-
return this.frame.content();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
unwrap(): Frame {
|
|
53
|
-
return this.frame;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function normalizeSnippet(input: string): string {
|
|
58
|
-
return input.replace(/\s+/g, ' ').trim().slice(0, 160);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function enrichEvaluateError(kind: 'evaluate' | 'evaluateScript', source: string, error: unknown): never {
|
|
62
|
-
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
63
|
-
const preview = normalizeSnippet(source);
|
|
64
|
-
const enriched = new Error(
|
|
65
|
-
`[puppeteer:${kind}] ${normalized.message} | scriptBytes=${source.length} | scriptPreview=${preview}`,
|
|
66
|
-
);
|
|
67
|
-
enriched.stack = normalized.stack ?? enriched.message;
|
|
68
|
-
throw enriched;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function enrichWaitError(
|
|
72
|
-
kind: 'waitForSelector' | 'waitForFunction' | 'waitForFunctionFn',
|
|
73
|
-
details: Record<string, unknown>,
|
|
74
|
-
error: unknown,
|
|
75
|
-
): never {
|
|
76
|
-
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
77
|
-
const detailText = Object.entries(details)
|
|
78
|
-
.filter(([, value]) => typeof value !== 'undefined')
|
|
79
|
-
.map(([key, value]) => `${key}=${typeof value === 'string' ? value : JSON.stringify(value)}`)
|
|
80
|
-
.join(' | ');
|
|
81
|
-
const enriched = new Error(`[puppeteer:${kind}] ${normalized.message}${detailText ? ` | ${detailText}` : ''}`);
|
|
82
|
-
enriched.stack = normalized.stack ?? enriched.message;
|
|
83
|
-
throw enriched;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
class PuppeteerRequest implements IRequest {
|
|
87
|
-
constructor(private readonly request: HTTPRequest, private readonly page: Page) { }
|
|
88
|
-
|
|
89
|
-
url(): string {
|
|
90
|
-
return this.request.url();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
method(): string {
|
|
94
|
-
return this.request.method();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
headers(): Record<string, string> {
|
|
98
|
-
return this.request.headers();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
resourceType(): string | null {
|
|
102
|
-
return this.request.resourceType?.() ?? null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
isNavigationRequest(): boolean {
|
|
106
|
-
return this.request.isNavigationRequest();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
frame(): IFrame | null {
|
|
110
|
-
const frame = this.request.frame();
|
|
111
|
-
return frame ? new PuppeteerFrame(frame, frame === this.page.mainFrame()) : null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async abort(errorCode?: string): Promise<void> {
|
|
115
|
-
await this.request.abort(errorCode as never);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async continue(overrides?: IRequestContinueOverrides): Promise<void> {
|
|
119
|
-
await this.request.continue(overrides);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async respond(response: IRequestResponse): Promise<void> {
|
|
123
|
-
await this.request.respond({
|
|
124
|
-
...response,
|
|
125
|
-
body: response.body instanceof Uint8Array ? Buffer.from(response.body) : response.body,
|
|
126
|
-
} as never);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
unwrap(): HTTPRequest {
|
|
130
|
-
return this.request;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
class PuppeteerConsoleMessage implements IConsoleMessage {
|
|
135
|
-
constructor(private readonly message: ConsoleMessage) { }
|
|
136
|
-
|
|
137
|
-
type(): string {
|
|
138
|
-
return this.message.type();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
text(): string {
|
|
142
|
-
return this.message.text();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
location(): { url?: string; lineNumber?: number; columnNumber?: number } {
|
|
146
|
-
const location = this.message.location();
|
|
147
|
-
return {
|
|
148
|
-
url: location.url,
|
|
149
|
-
lineNumber: location.lineNumber,
|
|
150
|
-
columnNumber: location.columnNumber,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
args(): unknown[] {
|
|
155
|
-
return this.message.args();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
unwrap(): ConsoleMessage {
|
|
159
|
-
return this.message;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export class PuppeteerPage implements IPage {
|
|
164
|
-
readonly name = 'puppeteer' as const;
|
|
165
|
-
private readonly listenerWrappers = new Map<string, Map<Function, Set<ListenerWrapper>>>();
|
|
166
|
-
|
|
167
|
-
constructor(
|
|
168
|
-
private page: Page,
|
|
169
|
-
private capabilities: DriverCapabilities
|
|
170
|
-
) { }
|
|
171
|
-
|
|
172
|
-
async goto(url: string, options?: { waitUntil?: NavigationWaitUntil; timeoutMs?: number }): Promise<void> {
|
|
173
|
-
await this.page.goto(url, {
|
|
174
|
-
waitUntil: options?.waitUntil as any,
|
|
175
|
-
timeout: options?.timeoutMs,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
url(): string {
|
|
180
|
-
return this.page.url();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async reload(): Promise<void> {
|
|
184
|
-
await this.page.reload();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
mainFrame(): IFrame {
|
|
188
|
-
return new PuppeteerFrame(this.page.mainFrame(), true);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async evaluateOnNewDocument<Arg, Ret>(
|
|
192
|
-
fn: (arg: Arg) => Ret | Promise<Ret>,
|
|
193
|
-
arg: Arg
|
|
194
|
-
): Promise<void> {
|
|
195
|
-
await this.page.evaluateOnNewDocument(fn as any, arg as any);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async evaluate<Arg, Ret>(fn: (arg: Arg) => Ret | Promise<Ret>, arg: Arg): Promise<Ret> {
|
|
199
|
-
try {
|
|
200
|
-
return await this.page.evaluate(fn as any, arg as any) as Ret;
|
|
201
|
-
} catch (error) {
|
|
202
|
-
const fnSource = typeof fn === 'function' ? fn.toString() : '[anonymous evaluate fn]';
|
|
203
|
-
return enrichEvaluateError('evaluate', fnSource, error);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async evaluateScript<Ret = unknown>(script: string): Promise<Ret> {
|
|
208
|
-
try {
|
|
209
|
-
return await this.page.evaluate(script) as Ret;
|
|
210
|
-
} catch (error) {
|
|
211
|
-
return enrichEvaluateError('evaluateScript', script, error);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async addInitScript(script: string): Promise<DisposableHandle> {
|
|
216
|
-
const registration = await (this.page as any).evaluateOnNewDocument(script);
|
|
217
|
-
const identifier = this.getInitScriptIdentifier(registration);
|
|
218
|
-
let disposed = false;
|
|
219
|
-
|
|
220
|
-
return new PuppeteerDisposableHandle(async () => {
|
|
221
|
-
if (disposed) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
disposed = true;
|
|
226
|
-
if (identifier && typeof (this.page as any).removeScriptToEvaluateOnNewDocument === 'function') {
|
|
227
|
-
await (this.page as any).removeScriptToEvaluateOnNewDocument(identifier);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async setViewport(viewport: { width: number; height: number }): Promise<void> {
|
|
233
|
-
await this.page.setViewport(viewport);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async setUserAgent(ua: string): Promise<void> {
|
|
237
|
-
await this.page.setUserAgent(ua);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async setRequestInterception(enabled: boolean): Promise<void> {
|
|
241
|
-
this.require('requestInterception');
|
|
242
|
-
await this.page.setRequestInterception(enabled);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async waitForSelector(selector: string, options?: { timeoutMs?: number }): Promise<IElementHandle | null> {
|
|
246
|
-
try {
|
|
247
|
-
const element = await this.page.waitForSelector(selector, { timeout: options?.timeoutMs });
|
|
248
|
-
return element ? new PuppeteerElementHandle(element) : null;
|
|
249
|
-
} catch (error) {
|
|
250
|
-
return enrichWaitError('waitForSelector', {
|
|
251
|
-
selector,
|
|
252
|
-
timeoutMs: options?.timeoutMs,
|
|
253
|
-
}, error);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
async waitForFunction<Arg>(script: string, options?: WaitForFunctionOptions): Promise<void>;
|
|
258
|
-
async waitForFunction<Arg>(fn: (arg: Arg) => boolean, arg: Arg, options?: WaitForFunctionOptions): Promise<void>;
|
|
259
|
-
async waitForFunction<Arg>(fnOrScript: string | ((arg: Arg) => boolean), argOrOptions?: Arg | WaitForFunctionOptions, maybeOptions?: WaitForFunctionOptions): Promise<void> {
|
|
260
|
-
if (typeof fnOrScript === 'string') {
|
|
261
|
-
const options = argOrOptions as WaitForFunctionOptions | undefined;
|
|
262
|
-
try {
|
|
263
|
-
await this.page.waitForFunction(fnOrScript, {
|
|
264
|
-
timeout: options?.timeoutMs,
|
|
265
|
-
polling: options?.polling as any,
|
|
266
|
-
});
|
|
267
|
-
return;
|
|
268
|
-
} catch (error) {
|
|
269
|
-
return enrichWaitError('waitForFunction', {
|
|
270
|
-
timeoutMs: options?.timeoutMs,
|
|
271
|
-
polling: options?.polling,
|
|
272
|
-
scriptBytes: fnOrScript.length,
|
|
273
|
-
scriptPreview: normalizeSnippet(fnOrScript),
|
|
274
|
-
}, error);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const options = maybeOptions;
|
|
279
|
-
try {
|
|
280
|
-
await this.page.waitForFunction(fnOrScript as any, {
|
|
281
|
-
timeout: options?.timeoutMs,
|
|
282
|
-
polling: options?.polling as any,
|
|
283
|
-
}, argOrOptions as any);
|
|
284
|
-
} catch (error) {
|
|
285
|
-
return enrichWaitError('waitForFunctionFn', {
|
|
286
|
-
timeoutMs: options?.timeoutMs,
|
|
287
|
-
polling: options?.polling,
|
|
288
|
-
fnPreview: normalizeSnippet(fnOrScript.toString()),
|
|
289
|
-
argPreview: typeof argOrOptions === 'undefined' ? undefined : normalizeSnippet(JSON.stringify(argOrOptions)),
|
|
290
|
-
}, error);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async $(selector: string): Promise<IElementHandle | null> {
|
|
295
|
-
const element = await this.page.$(selector);
|
|
296
|
-
return element ? new PuppeteerElementHandle(element) : null;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async $$(selector: string): Promise<IElementHandle[]> {
|
|
300
|
-
const elements = await this.page.$$(selector);
|
|
301
|
-
return elements.map(el => new PuppeteerElementHandle(el));
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
async click(selector: string): Promise<void> {
|
|
305
|
-
await this.page.click(selector);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async type(selector: string, text: string, options?: { delayMs?: number }): Promise<void> {
|
|
309
|
-
await this.page.type(selector, text, { delay: options?.delayMs });
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
async screenshot(options?: { type?: 'png' | 'jpeg'; fullPage?: boolean }): Promise<Uint8Array> {
|
|
313
|
-
const buffer = await this.page.screenshot(options);
|
|
314
|
-
return new Uint8Array(buffer);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async exposeFunction(name: string, fn: (...args: any[]) => any): Promise<void> {
|
|
318
|
-
await this.page.exposeFunction(name, fn);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
on<K extends string>(event: K, handler: ((...args: any[]) => void | Promise<void>) | PageEventHandler<any>): DisposableHandle {
|
|
322
|
-
const wrapped = (...args: any[]) => {
|
|
323
|
-
const payload = this.wrapEventPayload(event, args);
|
|
324
|
-
if (event === 'close') {
|
|
325
|
-
return (handler as () => void | Promise<void>)();
|
|
326
|
-
}
|
|
327
|
-
return (handler as (payload: unknown) => void | Promise<void>)(payload);
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
this.getWrapperSet(event, handler as Function).add(wrapped);
|
|
331
|
-
this.page.on(event as any, wrapped as any);
|
|
332
|
-
|
|
333
|
-
return new PuppeteerDisposableHandle(() => {
|
|
334
|
-
this.removeWrappedListener(event, handler as Function, wrapped);
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
off<K extends string>(event: K, handler: ((...args: any[]) => void | Promise<void>) | PageEventHandler<any>): void {
|
|
339
|
-
const wrappers = this.listenerWrappers.get(event)?.get(handler as Function);
|
|
340
|
-
if (!wrappers || wrappers.size === 0) {
|
|
341
|
-
this.page.off(event as any, handler as any);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
for (const wrapped of wrappers) {
|
|
346
|
-
this.page.off(event as any, wrapped as any);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
this.listenerWrappers.get(event)?.delete(handler as Function);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async close(): Promise<void> {
|
|
353
|
-
await this.page.close();
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
isClosed(): boolean {
|
|
357
|
-
return this.page.isClosed();
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
has<C extends string>(cap: C): boolean {
|
|
361
|
-
return (this.capabilities as any)[cap]?.supported === true;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
require<C extends string>(cap: C): void {
|
|
365
|
-
if (!this.has(cap)) {
|
|
366
|
-
const capability = (this.capabilities as any)[cap];
|
|
367
|
-
throw new Error(
|
|
368
|
-
`Page does not support capability '${cap}'${capability?.reason ? `: ${capability.reason}` : ''
|
|
369
|
-
}`
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
async cdp(): Promise<CDPSession> {
|
|
375
|
-
this.require('cdp');
|
|
376
|
-
return await this.page.createCDPSession();
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async setBypassServiceWorker(bypass: boolean): Promise<void> {
|
|
380
|
-
this.require('serviceWorkerBypass');
|
|
381
|
-
await this.page.setBypassServiceWorker(bypass);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
async pdf(options?: { path?: string; format?: string }): Promise<Uint8Array> {
|
|
385
|
-
this.require('pdf');
|
|
386
|
-
const buffer = await this.page.pdf(options as any);
|
|
387
|
-
return new Uint8Array(buffer);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
async startTracing(options?: { path?: string; screenshots?: boolean }): Promise<void> {
|
|
391
|
-
this.require('tracing');
|
|
392
|
-
await this.page.tracing.start(options);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async stopTracing(): Promise<Uint8Array> {
|
|
396
|
-
this.require('tracing');
|
|
397
|
-
const buffer = await this.page.tracing.stop();
|
|
398
|
-
return buffer ? new Uint8Array(buffer) : new Uint8Array();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
unwrap(): Page {
|
|
402
|
-
return this.page;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
private getWrapperSet(event: string, handler: Function): Set<ListenerWrapper> {
|
|
406
|
-
let wrappers = this.listenerWrappers.get(event);
|
|
407
|
-
if (!wrappers) {
|
|
408
|
-
wrappers = new Map();
|
|
409
|
-
this.listenerWrappers.set(event, wrappers);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
let handlerWrappers = wrappers.get(handler);
|
|
413
|
-
if (!handlerWrappers) {
|
|
414
|
-
handlerWrappers = new Set();
|
|
415
|
-
wrappers.set(handler, handlerWrappers);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return handlerWrappers;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
private removeWrappedListener(event: string, handler: Function, wrapped: ListenerWrapper): void {
|
|
422
|
-
this.page.off(event as any, wrapped as any);
|
|
423
|
-
|
|
424
|
-
const eventWrappers = this.listenerWrappers.get(event);
|
|
425
|
-
const handlerWrappers = eventWrappers?.get(handler);
|
|
426
|
-
if (!handlerWrappers) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
handlerWrappers.delete(wrapped);
|
|
431
|
-
if (handlerWrappers.size === 0) {
|
|
432
|
-
eventWrappers?.delete(handler);
|
|
433
|
-
}
|
|
434
|
-
if (eventWrappers && eventWrappers.size === 0) {
|
|
435
|
-
this.listenerWrappers.delete(event);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
private getInitScriptIdentifier(registration: unknown): string | undefined {
|
|
440
|
-
if (typeof registration === 'string') {
|
|
441
|
-
return registration;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (registration && typeof registration === 'object') {
|
|
445
|
-
const candidate = (registration as { identifier?: unknown; id?: unknown }).identifier
|
|
446
|
-
?? (registration as { identifier?: unknown; id?: unknown }).id;
|
|
447
|
-
return typeof candidate === 'string' ? candidate : undefined;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return undefined;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
private wrapEventPayload(event: string, args: any[]): unknown {
|
|
454
|
-
const [payload] = args;
|
|
455
|
-
switch (event) {
|
|
456
|
-
case 'request':
|
|
457
|
-
case 'requestfailed':
|
|
458
|
-
case 'requestfinished':
|
|
459
|
-
return new PuppeteerRequest(payload as HTTPRequest, this.page);
|
|
460
|
-
case 'framenavigated':
|
|
461
|
-
return new PuppeteerFrame(payload as Frame, payload === this.page.mainFrame());
|
|
462
|
-
case 'console':
|
|
463
|
-
return new PuppeteerConsoleMessage(payload as ConsoleMessage);
|
|
464
|
-
case 'pageerror':
|
|
465
|
-
return payload as Error;
|
|
466
|
-
default:
|
|
467
|
-
return payload;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { PuppeteerPage } from '../PuppeteerPage';
|
|
3
|
-
|
|
4
|
-
type MockHandler = (...args: any[]) => void | Promise<void>;
|
|
5
|
-
|
|
6
|
-
class MockPuppeteerPage {
|
|
7
|
-
private readonly listeners = new Map<string, Set<MockHandler>>();
|
|
8
|
-
|
|
9
|
-
readonly mainFrameValue = {
|
|
10
|
-
url: () => 'https://example.test',
|
|
11
|
-
name: () => 'main',
|
|
12
|
-
parentFrame: () => null,
|
|
13
|
-
content: vi.fn(async () => '<html></html>'),
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
readonly setViewport = vi.fn(async () => undefined);
|
|
17
|
-
readonly setUserAgent = vi.fn(async () => undefined);
|
|
18
|
-
readonly setRequestInterception = vi.fn(async () => undefined);
|
|
19
|
-
readonly waitForSelector = vi.fn(async () => null);
|
|
20
|
-
readonly waitForFunction = vi.fn(async () => undefined);
|
|
21
|
-
readonly $ = vi.fn(async () => null);
|
|
22
|
-
readonly $$ = vi.fn(async () => []);
|
|
23
|
-
readonly click = vi.fn(async () => undefined);
|
|
24
|
-
readonly type = vi.fn(async () => undefined);
|
|
25
|
-
readonly screenshot = vi.fn(async () => Buffer.from([]));
|
|
26
|
-
readonly exposeFunction = vi.fn(async () => undefined);
|
|
27
|
-
readonly close = vi.fn(async () => undefined);
|
|
28
|
-
readonly goto = vi.fn(async () => undefined);
|
|
29
|
-
readonly reload = vi.fn(async () => undefined);
|
|
30
|
-
readonly evaluate = vi.fn(async () => undefined);
|
|
31
|
-
readonly url = vi.fn(() => 'about:blank');
|
|
32
|
-
readonly isClosed = vi.fn(() => false);
|
|
33
|
-
readonly evaluateOnNewDocument = vi.fn(async () => ({ identifier: 'init-script-1' }));
|
|
34
|
-
readonly removeScriptToEvaluateOnNewDocument = vi.fn(async () => undefined);
|
|
35
|
-
|
|
36
|
-
mainFrame() {
|
|
37
|
-
return this.mainFrameValue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
on(event: string, handler: MockHandler): void {
|
|
41
|
-
const handlers = this.listeners.get(event) ?? new Set<MockHandler>();
|
|
42
|
-
handlers.add(handler);
|
|
43
|
-
this.listeners.set(event, handlers);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
off(event: string, handler: MockHandler): void {
|
|
47
|
-
this.listeners.get(event)?.delete(handler);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async emit(event: string, payload?: unknown): Promise<void> {
|
|
51
|
-
const handlers = [...(this.listeners.get(event) ?? [])];
|
|
52
|
-
await Promise.all(handlers.map((handler) => Promise.resolve(handler(payload))));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const capabilities = {
|
|
57
|
-
cdp: { supported: true },
|
|
58
|
-
requestInterception: { supported: true },
|
|
59
|
-
serviceWorkerBypass: { supported: true },
|
|
60
|
-
stealth: { supported: true },
|
|
61
|
-
pdf: { supported: true },
|
|
62
|
-
tracing: { supported: true },
|
|
63
|
-
persistentContext: { supported: true },
|
|
64
|
-
browserExtensions: { supported: true },
|
|
65
|
-
exposeBinding: { supported: true },
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
describe('PuppeteerPage', () => {
|
|
69
|
-
it('keeps duplicate listeners independently disposable', async () => {
|
|
70
|
-
const rawPage = new MockPuppeteerPage();
|
|
71
|
-
const page = new PuppeteerPage(rawPage as any, capabilities as any);
|
|
72
|
-
const handler = vi.fn();
|
|
73
|
-
|
|
74
|
-
const first = page.on('request', handler);
|
|
75
|
-
const second = page.on('request', handler);
|
|
76
|
-
|
|
77
|
-
const request = {
|
|
78
|
-
url: () => 'https://example.test',
|
|
79
|
-
method: () => 'GET',
|
|
80
|
-
headers: () => ({ accept: 'application/json' }),
|
|
81
|
-
resourceType: () => 'document',
|
|
82
|
-
isNavigationRequest: () => true,
|
|
83
|
-
frame: () => rawPage.mainFrameValue,
|
|
84
|
-
abort: vi.fn(async () => undefined),
|
|
85
|
-
continue: vi.fn(async () => undefined),
|
|
86
|
-
respond: vi.fn(async () => undefined),
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
await rawPage.emit('request', request);
|
|
90
|
-
expect(handler).toHaveBeenCalledTimes(2);
|
|
91
|
-
|
|
92
|
-
await first.dispose();
|
|
93
|
-
await rawPage.emit('request', request);
|
|
94
|
-
expect(handler).toHaveBeenCalledTimes(3);
|
|
95
|
-
|
|
96
|
-
await second.dispose();
|
|
97
|
-
await rawPage.emit('request', request);
|
|
98
|
-
expect(handler).toHaveBeenCalledTimes(3);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('removes init scripts via returned identifiers and makes disposal idempotent', async () => {
|
|
102
|
-
const rawPage = new MockPuppeteerPage();
|
|
103
|
-
const page = new PuppeteerPage(rawPage as any, capabilities as any);
|
|
104
|
-
|
|
105
|
-
const handle = await page.addInitScript('window.__OPENWA__ = true;');
|
|
106
|
-
|
|
107
|
-
await handle.dispose();
|
|
108
|
-
await handle.dispose();
|
|
109
|
-
|
|
110
|
-
expect(rawPage.evaluateOnNewDocument).toHaveBeenCalledWith('window.__OPENWA__ = true;');
|
|
111
|
-
expect(rawPage.removeScriptToEvaluateOnNewDocument).toHaveBeenCalledTimes(1);
|
|
112
|
-
expect(rawPage.removeScriptToEvaluateOnNewDocument).toHaveBeenCalledWith('init-script-1');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('wraps request payloads and normalizes Uint8Array response bodies', async () => {
|
|
116
|
-
const rawPage = new MockPuppeteerPage();
|
|
117
|
-
const page = new PuppeteerPage(rawPage as any, capabilities as any);
|
|
118
|
-
const respond = vi.fn(async () => undefined);
|
|
119
|
-
let wrappedRequest: any;
|
|
120
|
-
|
|
121
|
-
page.on('request', (request: any) => {
|
|
122
|
-
wrappedRequest = request;
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
await rawPage.emit('request', {
|
|
126
|
-
url: () => 'https://example.test/api',
|
|
127
|
-
method: () => 'POST',
|
|
128
|
-
headers: () => ({ 'content-type': 'application/json' }),
|
|
129
|
-
resourceType: () => 'xhr',
|
|
130
|
-
isNavigationRequest: () => false,
|
|
131
|
-
frame: () => rawPage.mainFrameValue,
|
|
132
|
-
abort: vi.fn(async () => undefined),
|
|
133
|
-
continue: vi.fn(async () => undefined),
|
|
134
|
-
respond,
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
await wrappedRequest.respond({
|
|
138
|
-
status: 200,
|
|
139
|
-
body: new Uint8Array([1, 2, 3]),
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
expect(wrappedRequest.url()).toBe('https://example.test/api');
|
|
143
|
-
expect(wrappedRequest.frame()?.isMainFrame()).toBe(true);
|
|
144
|
-
expect(respond).toHaveBeenCalledWith({
|
|
145
|
-
status: 200,
|
|
146
|
-
body: Buffer.from([1, 2, 3]),
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
});
|
package/src/index.ts
DELETED