browser-commander 0.28.0 → 0.29.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/.jscpd-baseline.json +5 -19
- package/CHANGELOG.md +6 -0
- package/README.md +20 -10
- package/package.json +1 -1
- package/src/browser/browser-profile-files.js +5 -2
- package/src/browser/connector.js +140 -65
- package/src/browser/persistent-session.js +110 -38
- package/src/browser/session-adoption.js +96 -0
- package/src/browser/session-worker.js +12 -0
- package/src/cli/args.js +3 -0
- package/src/cli/commands.js +3 -0
- package/src/cli/high-level-methods.js +7 -0
- package/src/cli/sessions.js +3 -0
- package/src/core/commander-features.js +6 -1
- package/src/core/navigation-safety.js +1 -0
- package/src/core/page-trigger-manager.js +173 -134
- package/src/index.js +2 -0
- package/src/interactions/overlays.js +59 -14
- package/src/traces/bundle.js +41 -3
- package/src/traces/checkpoints.js +7 -3
- package/src/traces/links.js +44 -5
- package/src/traces/mutation-stream.js +8 -2
- package/src/traces/network.js +65 -13
- package/src/traces/page-capture.js +65 -21
- package/src/traces/recorder.js +33 -3
- package/src/traces/redaction.js +89 -6
- package/src/traces/rolling-bundle.js +240 -0
- package/src/traces/rolling.js +10 -113
- package/src/traces/stop.js +1 -0
- package/tests/e2e/issue-160.e2e.test.js +291 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.html +1 -1
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.html +1 -1
- package/tests/fixtures/traces/conformance/expected/bundle/events.ndjson +2 -2
- package/tests/fixtures/traces/conformance/expected/bundle/manifest.json +4 -0
- package/tests/fixtures/traces/conformance/expected/bundle/viewer.html +1 -1
- package/tests/fixtures/traces/conformance/expected/trace.lino +2 -2
- package/tests/unit/browser/issue-160-connector.test.js +157 -0
- package/tests/unit/core/interrupted-navigation.test.js +32 -0
- package/tests/unit/core/trigger-stop-race.test.js +80 -0
- package/tests/unit/interactions/overlays.test.js +58 -0
- package/tests/unit/traces/issue-160-privacy.test.js +110 -0
- package/tests/unit/traces/issue-160-rotation.test.js +140 -0
- package/tests/unit/traces/network.test.js +55 -3
package/.jscpd-baseline.json
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"fingerprints": {
|
|
4
|
-
"00504cdec3a262ed": 1,
|
|
5
4
|
"00d381dd611ae8ac": 1,
|
|
6
5
|
"029a62ef1f48e856": 1,
|
|
7
|
-
"02fc9852e9ba4c49": 1,
|
|
8
6
|
"038170e72e0af1f5": 1,
|
|
9
7
|
"0413f601c8911a92": 1,
|
|
10
8
|
"04b28d01d0bb47dc": 1,
|
|
@@ -14,22 +12,18 @@
|
|
|
14
12
|
"08e28c7706cdddd6": 1,
|
|
15
13
|
"090a69d9528e4cdb": 1,
|
|
16
14
|
"09694dc8fb9d683c": 1,
|
|
17
|
-
"0a3459aad03492bb": 1,
|
|
18
15
|
"0ae281facb3c28dd": 1,
|
|
19
|
-
"0b48b63049c42591": 1,
|
|
20
16
|
"0b8c71391c107a5e": 1,
|
|
21
17
|
"0ba2d165b56db0b3": 1,
|
|
22
18
|
"0ca2d9c2f2b44d6c": 1,
|
|
23
19
|
"0caa402c4eae32d8": 1,
|
|
24
20
|
"0fd23be8410c2c95": 1,
|
|
25
21
|
"0fe4f3b5e5e939b3": 1,
|
|
26
|
-
"126a1cfe8f2264ce": 1,
|
|
27
22
|
"1273365073edd790": 1,
|
|
28
23
|
"13c4b58d3ea0719b": 1,
|
|
29
24
|
"14beedf656e57fba": 1,
|
|
30
25
|
"150c8efe188ac225": 1,
|
|
31
26
|
"16340a16ed52849e": 1,
|
|
32
|
-
"17c26c0db2d35ad1": 1,
|
|
33
27
|
"19ff70fc80fad524": 1,
|
|
34
28
|
"1a9f0f0f68ed445c": 1,
|
|
35
29
|
"1ad4dc7e19a77f68": 1,
|
|
@@ -42,7 +36,6 @@
|
|
|
42
36
|
"22755b35701c5bb6": 1,
|
|
43
37
|
"2296c06fa00da254": 1,
|
|
44
38
|
"229b903386b17538": 1,
|
|
45
|
-
"22ab9a1ca6316d24": 1,
|
|
46
39
|
"22cbd954358d25f8": 1,
|
|
47
40
|
"230bfe57cfda164d": 1,
|
|
48
41
|
"2357ae472525f6bf": 1,
|
|
@@ -51,6 +44,7 @@
|
|
|
51
44
|
"266b9cb95f783cdf": 1,
|
|
52
45
|
"26bb09996580437c": 1,
|
|
53
46
|
"26ebb2e251c9a39e": 1,
|
|
47
|
+
"282eb8d84bbe54a4": 1,
|
|
54
48
|
"29edfb9e08094991": 1,
|
|
55
49
|
"2a78ee4b05679c8d": 1,
|
|
56
50
|
"2b1a759e2cfd21a2": 1,
|
|
@@ -65,7 +59,6 @@
|
|
|
65
59
|
"33357ce94e07cc4b": 1,
|
|
66
60
|
"35cf2769645f3234": 1,
|
|
67
61
|
"37bd4114d0089f49": 1,
|
|
68
|
-
"39cf8134a7fdaa31": 1,
|
|
69
62
|
"3adca52a3f04837c": 1,
|
|
70
63
|
"3c1017def7b3216c": 1,
|
|
71
64
|
"3c5cb4b62d3739e3": 1,
|
|
@@ -79,7 +72,6 @@
|
|
|
79
72
|
"441570d51d6f70a4": 1,
|
|
80
73
|
"457c71f32f53833d": 1,
|
|
81
74
|
"4715a17d690865ee": 1,
|
|
82
|
-
"47dc143be0bd8be5": 1,
|
|
83
75
|
"4b0d3cc5c27227e7": 1,
|
|
84
76
|
"4c3b94caa28045d0": 1,
|
|
85
77
|
"4d1ff99bb19f5685": 1,
|
|
@@ -107,16 +99,15 @@
|
|
|
107
99
|
"5ba2f3218754f04c": 1,
|
|
108
100
|
"60052da86e7bb241": 1,
|
|
109
101
|
"608e1dd27138ec6f": 1,
|
|
110
|
-
"60a3149247760d9b": 1,
|
|
111
102
|
"60d0c58a01fc8746": 1,
|
|
112
103
|
"61ae5da6cc003836": 1,
|
|
113
104
|
"61bcadcd8dc9f63e": 1,
|
|
114
105
|
"63ff613b1780c67a": 1,
|
|
115
106
|
"642cc89ed49f8a21": 1,
|
|
116
107
|
"6479c3604c153ec4": 1,
|
|
108
|
+
"65ce52f8afd6a952": 1,
|
|
117
109
|
"66fb97eebd7ff0dc": 1,
|
|
118
110
|
"67a95e428b8fa31c": 1,
|
|
119
|
-
"67c309590c5d69a1": 1,
|
|
120
111
|
"6880d620e0e67229": 1,
|
|
121
112
|
"69d50e6f6a5f2ee9": 1,
|
|
122
113
|
"6a7ed9ad3fc00a39": 1,
|
|
@@ -153,6 +144,7 @@
|
|
|
153
144
|
"86aae81b1aa0d21b": 1,
|
|
154
145
|
"87484a2744e09748": 1,
|
|
155
146
|
"8c2b1408886c1db1": 1,
|
|
147
|
+
"8d24ce7d6e8f6795": 1,
|
|
156
148
|
"8e55579a412bc058": 1,
|
|
157
149
|
"8ec18cdbd4a96518": 1,
|
|
158
150
|
"901317bc4d7d30d0": 1,
|
|
@@ -178,10 +170,8 @@
|
|
|
178
170
|
"a6ad7ab6c05656dc": 1,
|
|
179
171
|
"a6ffab1aacd00af6": 1,
|
|
180
172
|
"a71a4b720d466e32": 1,
|
|
181
|
-
"a71fab6edf38c846": 1,
|
|
182
173
|
"a78d76908162699e": 1,
|
|
183
174
|
"a9bd4ffef6da5faf": 1,
|
|
184
|
-
"ac3fe4ef966e0209": 1,
|
|
185
175
|
"acc95d694eb2f581": 1,
|
|
186
176
|
"ad4cfa76daa1b6fc": 1,
|
|
187
177
|
"ad50b48938af8aef": 1,
|
|
@@ -203,7 +193,6 @@
|
|
|
203
193
|
"bb5b0c8d22c9667a": 1,
|
|
204
194
|
"bc9115be3a462930": 1,
|
|
205
195
|
"bd0ae42c13d5b4f9": 1,
|
|
206
|
-
"bd17b166d89cb709": 1,
|
|
207
196
|
"bdb8ca169be36b80": 1,
|
|
208
197
|
"bdcd11c2bfb5fde0": 1,
|
|
209
198
|
"be0900fb3e87164e": 1,
|
|
@@ -235,8 +224,6 @@
|
|
|
235
224
|
"d6fa9884eee815f0": 1,
|
|
236
225
|
"d86bafb915488255": 1,
|
|
237
226
|
"da2c10a66acdb52f": 1,
|
|
238
|
-
"dabb9fc09638e178": 1,
|
|
239
|
-
"dcc616702e5b0cb4": 1,
|
|
240
227
|
"de0bf2317c53d427": 1,
|
|
241
228
|
"de1b481bfe8c3e58": 1,
|
|
242
229
|
"df2dc922f40485e7": 1,
|
|
@@ -256,7 +243,6 @@
|
|
|
256
243
|
"eb9a70c2a21094ff": 1,
|
|
257
244
|
"ebe03c5f8c2f5147": 1,
|
|
258
245
|
"ee19c8b9b332ee98": 1,
|
|
259
|
-
"ef6b9112e659147b": 1,
|
|
260
246
|
"f07f979f8c2706e8": 1,
|
|
261
247
|
"f38f72095a67c95e": 1,
|
|
262
248
|
"f40ba84763547f41": 1,
|
|
@@ -265,7 +251,6 @@
|
|
|
265
251
|
"f4bfa7e503443cd1": 1,
|
|
266
252
|
"f6678d6748e9c902": 1,
|
|
267
253
|
"f67a07a611f39cb2": 1,
|
|
268
|
-
"f762b61eef0af530": 1,
|
|
269
254
|
"f7b1f9d699bcd580": 1,
|
|
270
255
|
"f81e07af5d3a1c3c": 1,
|
|
271
256
|
"f904844f89cab699": 1,
|
|
@@ -275,6 +260,7 @@
|
|
|
275
260
|
"fb4a079a529a6285": 1,
|
|
276
261
|
"fc0d7bde0033411d": 1,
|
|
277
262
|
"fccfd8b004c12dd4": 1,
|
|
278
|
-
"fe859df9f97c75d4": 1
|
|
263
|
+
"fe859df9f97c75d4": 1,
|
|
264
|
+
"ff3b6c3e0b8bf33b": 1
|
|
279
265
|
}
|
|
280
266
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.29.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4939956: Harden trace privacy and bounded mutation capture; rotate continuous bundles before writes and retain segments by default. Fix CDP selection cleanup and zero-page reconnect, add persistent profile adoption, idle heartbeats and tab enforcement, preserve queued trigger starts during stop, and make overlay dismissal report failures safely. Export trace rendering/summary tools and document the 0.28 trigger migration and packaged guides.
|
|
8
|
+
|
|
3
9
|
## 0.28.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A universal browser automation library for JavaScript/TypeScript that supports Playwright, Puppeteer, and Selenium with a unified API. The key focus is on **stoppable page triggers** - ensuring automation logic is properly mounted/unmounted during page navigation.
|
|
4
4
|
|
|
5
|
-
See [engine support and native/CLI defaults](
|
|
5
|
+
See [engine support and native/CLI defaults](https://github.com/link-foundation/browser-commander/blob/main/docs/engine-support.md) for the
|
|
6
6
|
cross-language API matrix and Selenium examples.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
@@ -119,7 +119,7 @@ const { browser, page } = await launchBrowser({
|
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
Every switch Browser Commander used to add on its own is available as a named
|
|
122
|
-
[launch restriction](
|
|
122
|
+
[launch restriction](https://github.com/link-foundation/browser-commander/blob/main/docs/feature-parity.md#launch-command-line-and-opt-in-restrictions);
|
|
123
123
|
`restrictions: ['legacy-defaults']` restores the old `CHROME_ARGS`. Pass
|
|
124
124
|
`launch: 'engine'` to use Playwright's `launchPersistentContext()` or
|
|
125
125
|
`puppeteer.launch()` instead; the engine then adds its own switches, and
|
|
@@ -379,6 +379,10 @@ commander.pageTrigger({
|
|
|
379
379
|
});
|
|
380
380
|
```
|
|
381
381
|
|
|
382
|
+
For trace budgets/privacy, persistent session adoption and heartbeats, CDP
|
|
383
|
+
`noDefaults`, overlay reporting, and the `readyOn`/`concurrency` trigger migration,
|
|
384
|
+
see [trace and session hardening](https://github.com/link-foundation/browser-commander/blob/f5e426d6933c681189c4989bac182a1558d8edb8/docs/trace-session-hardening.md).
|
|
385
|
+
|
|
382
386
|
## API Reference
|
|
383
387
|
|
|
384
388
|
### launchBrowser(options)
|
|
@@ -520,7 +524,7 @@ headless. `connectWebDriver({ serverUrl, capabilities })` opens a session on a
|
|
|
520
524
|
server that is already running (a Selenium Grid, a cloud provider); pass
|
|
521
525
|
`capabilities: { webSocketUrl: true }` for BiDi. Media emulation, fingerprint
|
|
522
526
|
overrides and managed downloads need CDP and throw on this engine; see
|
|
523
|
-
[docs/feature-parity.md](
|
|
527
|
+
[docs/feature-parity.md](https://github.com/link-foundation/browser-commander/blob/main/docs/feature-parity.md#webdriver-engine).
|
|
524
528
|
|
|
525
529
|
### listBrowserProfiles(options)
|
|
526
530
|
|
|
@@ -686,6 +690,8 @@ const commander = makeBrowserCommander({
|
|
|
686
690
|
verbose: false, // Enable debug logging
|
|
687
691
|
enableNetworkTracking: true, // Track HTTP requests
|
|
688
692
|
enableNavigationManager: true, // Enable navigation events
|
|
693
|
+
overlays: [{ selector: '.consent-close', timeout: 1000 }],
|
|
694
|
+
onOverlayDismiss: (report) => console.log(report.selector, report.status),
|
|
689
695
|
});
|
|
690
696
|
```
|
|
691
697
|
|
|
@@ -693,10 +699,14 @@ const commander = makeBrowserCommander({
|
|
|
693
699
|
|
|
694
700
|
```javascript
|
|
695
701
|
const unregister = commander.pageTrigger({
|
|
696
|
-
name: '
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
702
|
+
name: 'application-ready',
|
|
703
|
+
readyOn: 'domcontentloaded', // Default: networkidle
|
|
704
|
+
concurrency: 'queue', // Default: skip; also restart
|
|
705
|
+
condition: ({ url }) => url.includes('/application'),
|
|
706
|
+
action: async (ctx) => {
|
|
707
|
+
ctx.checkStopped();
|
|
708
|
+
},
|
|
709
|
+
priority: 0, // Higher runs first
|
|
700
710
|
});
|
|
701
711
|
```
|
|
702
712
|
|
|
@@ -802,7 +812,7 @@ failed or cancelled download raises the failure rather than returning a path.
|
|
|
802
812
|
|
|
803
813
|
### Portable Traces
|
|
804
814
|
|
|
805
|
-
See [capture, debugging and reusable sessions](
|
|
815
|
+
See [capture, debugging and reusable sessions](https://github.com/link-foundation/browser-commander/blob/main/docs/capture-and-debugging.md)
|
|
806
816
|
for screenshots, recordings, full/text DOM links, network/HAR, rotation,
|
|
807
817
|
persistent browsers, early trigger readiness and engine capability limits.
|
|
808
818
|
|
|
@@ -1028,7 +1038,7 @@ This is the **official extensibility mechanism** while awaiting browser-commande
|
|
|
1028
1038
|
|
|
1029
1039
|
## Command Line
|
|
1030
1040
|
|
|
1031
|
-
The package installs a `browser-commander` command. The Rust and Python packages ship the same command, and the contract all three follow is [docs/cli-and-bridge.md](
|
|
1041
|
+
The package installs a `browser-commander` command. The Rust and Python packages ship the same command, and the contract all three follow is [docs/cli-and-bridge.md](https://github.com/link-foundation/browser-commander/blob/main/docs/cli-and-bridge.md). Every command prints exactly one JSON document to stdout. The exit code is `0` on success and `1` on error. `doctor` exits `2` when it finds an unlisted difference, and a usage error exits `64`.
|
|
1032
1042
|
|
|
1033
1043
|
```bash
|
|
1034
1044
|
npx browser-commander version
|
|
@@ -1068,4 +1078,4 @@ See [src/ARCHITECTURE.md](src/ARCHITECTURE.md) for detailed architecture documen
|
|
|
1068
1078
|
|
|
1069
1079
|
[UNLICENSE](../LICENSE)
|
|
1070
1080
|
|
|
1071
|
-
See [navigation budgets, launch diagnostics, reusable helpers and sessions](
|
|
1081
|
+
See [navigation budgets, launch diagnostics, reusable helpers and sessions](https://github.com/link-foundation/browser-commander/blob/main/docs/navigation-launch-and-sessions.md) for the shared API contracts and engine limitations.
|
package/package.json
CHANGED
|
@@ -10,13 +10,16 @@ export function localStatePathForProfile(profileDir) {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/** Resolve existing ancestors even when the target profile does not exist. */
|
|
13
|
-
export function physicalPath(value) {
|
|
13
|
+
export function physicalPath(value, resolve = realpathSync) {
|
|
14
14
|
let existing = path.resolve(value);
|
|
15
15
|
const suffix = [];
|
|
16
16
|
while (true) {
|
|
17
17
|
try {
|
|
18
|
-
return path.join(
|
|
18
|
+
return path.join(resolve(existing), ...suffix.reverse());
|
|
19
19
|
} catch (error) {
|
|
20
|
+
if (['EPERM', 'EACCES'].includes(error.code)) {
|
|
21
|
+
return path.resolve(value);
|
|
22
|
+
}
|
|
20
23
|
if (!['ENOENT', 'ENOTDIR'].includes(error.code)) {
|
|
21
24
|
throw error;
|
|
22
25
|
}
|
package/src/browser/connector.js
CHANGED
|
@@ -45,11 +45,17 @@ async function connectSelenium(options, dependencies) {
|
|
|
45
45
|
...options,
|
|
46
46
|
debuggerAddress: new URL(options.cdpEndpoint).host,
|
|
47
47
|
});
|
|
48
|
-
|
|
48
|
+
try {
|
|
49
|
+
await selectWebDriverTab(connected.driver, options);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
await ignoreCleanup(() => connected.close());
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
49
54
|
return {
|
|
50
55
|
...connected,
|
|
51
56
|
browser: connected.driver,
|
|
52
57
|
disconnect: connected.close,
|
|
58
|
+
detach: connected.close,
|
|
53
59
|
};
|
|
54
60
|
}
|
|
55
61
|
|
|
@@ -60,11 +66,14 @@ async function selectWebDriverTab(driver, options) {
|
|
|
60
66
|
const original = await driver.getWindowHandle();
|
|
61
67
|
const handles = await driver.getAllWindowHandles();
|
|
62
68
|
let selected = original;
|
|
63
|
-
|
|
69
|
+
const matchers = urlMatchers(options);
|
|
70
|
+
if (options.targetId || matchers.length) {
|
|
64
71
|
selected = null;
|
|
72
|
+
const tabs = [];
|
|
65
73
|
for (const handle of handles) {
|
|
66
74
|
if (
|
|
67
75
|
options.targetId &&
|
|
76
|
+
!options.fallback &&
|
|
68
77
|
handle !== options.targetId &&
|
|
69
78
|
handle !== `CDwindow-${options.targetId}`
|
|
70
79
|
) {
|
|
@@ -72,22 +81,26 @@ async function selectWebDriverTab(driver, options) {
|
|
|
72
81
|
}
|
|
73
82
|
await driver.switchTo().window(handle);
|
|
74
83
|
const url = await driver.getCurrentUrl();
|
|
75
|
-
|
|
76
|
-
options.url &&
|
|
77
|
-
!(typeof options.url === 'function'
|
|
78
|
-
? options.url(url)
|
|
79
|
-
: options.url instanceof RegExp
|
|
80
|
-
? options.url.test(url)
|
|
81
|
-
: url === options.url)
|
|
82
|
-
) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
selected = handle;
|
|
86
|
-
break;
|
|
84
|
+
tabs.push({ handle, url });
|
|
87
85
|
}
|
|
86
|
+
selected = matchers.length
|
|
87
|
+
? matchers
|
|
88
|
+
.map(
|
|
89
|
+
(matcher) =>
|
|
90
|
+
tabs.find((tab) => matchesUrl(matcher, tab.url))?.handle
|
|
91
|
+
)
|
|
92
|
+
.find(Boolean)
|
|
93
|
+
: tabs.find(
|
|
94
|
+
(tab) =>
|
|
95
|
+
tab.handle === options.targetId ||
|
|
96
|
+
tab.handle === `CDwindow-${options.targetId}`
|
|
97
|
+
)?.handle;
|
|
88
98
|
if (!selected) {
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
if (!options.fallback) {
|
|
100
|
+
await driver.switchTo().window(original);
|
|
101
|
+
throw new Error('No tab matches the requested targetId or URL');
|
|
102
|
+
}
|
|
103
|
+
selected = handles.includes(original) ? original : handles[0];
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
106
|
if (options.singleTab) {
|
|
@@ -108,8 +121,13 @@ function addDefinedOptions(options, values) {
|
|
|
108
121
|
return options;
|
|
109
122
|
}
|
|
110
123
|
|
|
111
|
-
function buildPlaywrightConnectOptions({
|
|
112
|
-
|
|
124
|
+
function buildPlaywrightConnectOptions({
|
|
125
|
+
slowMo,
|
|
126
|
+
timeout,
|
|
127
|
+
headers,
|
|
128
|
+
noDefaults,
|
|
129
|
+
}) {
|
|
130
|
+
return addDefinedOptions({}, { slowMo, timeout, headers, noDefaults });
|
|
113
131
|
}
|
|
114
132
|
|
|
115
133
|
function buildPuppeteerConnectOptions({
|
|
@@ -130,6 +148,55 @@ function buildPuppeteerConnectOptions({
|
|
|
130
148
|
);
|
|
131
149
|
}
|
|
132
150
|
|
|
151
|
+
export function matchesUrl(matcher, url) {
|
|
152
|
+
if (matcher instanceof RegExp) {
|
|
153
|
+
matcher.lastIndex = 0;
|
|
154
|
+
return matcher.test(url);
|
|
155
|
+
}
|
|
156
|
+
return typeof matcher === 'function' ? matcher(url) : matcher === url;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function urlMatchers(options) {
|
|
160
|
+
return (
|
|
161
|
+
options.urlMatchers ??
|
|
162
|
+
(Array.isArray(options.url)
|
|
163
|
+
? options.url
|
|
164
|
+
: options.url
|
|
165
|
+
? [options.url]
|
|
166
|
+
: [])
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function ignoreCleanup(cleanup) {
|
|
171
|
+
try {
|
|
172
|
+
await cleanup();
|
|
173
|
+
} catch {
|
|
174
|
+
/* Preserve the connection failure. */
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function requestedPage(pages, targets, options) {
|
|
179
|
+
const matchers = urlMatchers(options);
|
|
180
|
+
if (!options.targetId && !matchers.length) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
const candidates = options.targetId
|
|
184
|
+
? pages.filter((page) => targets.get(page) === options.targetId)
|
|
185
|
+
: pages;
|
|
186
|
+
const eligible = candidates.length || !options.fallback ? candidates : pages;
|
|
187
|
+
const selected = matchers.length
|
|
188
|
+
? matchers
|
|
189
|
+
.map((matcher) =>
|
|
190
|
+
eligible.find((page) => matchesUrl(matcher, page.url()))
|
|
191
|
+
)
|
|
192
|
+
.find(Boolean)
|
|
193
|
+
: candidates[0];
|
|
194
|
+
if (!selected && !options.fallback) {
|
|
195
|
+
throw new RangeError('No tab matches the requested targetId/URL');
|
|
196
|
+
}
|
|
197
|
+
return selected;
|
|
198
|
+
}
|
|
199
|
+
|
|
133
200
|
async function prepareStorageState({ storageState, seedCookies }) {
|
|
134
201
|
if (seedCookies !== undefined && !Array.isArray(seedCookies)) {
|
|
135
202
|
throw new TypeError('seedCookies must be an array');
|
|
@@ -180,30 +247,10 @@ export async function pickForegroundPage(pages, options = {}) {
|
|
|
180
247
|
// Non-Chromium engines do not expose CDP. Explicit URL matching still
|
|
181
248
|
// works there, and normal visibility is not affected by this emulation.
|
|
182
249
|
} finally {
|
|
183
|
-
await session?.detach?.()
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
let selected;
|
|
187
|
-
if (options.targetId || options.url) {
|
|
188
|
-
selected = pages.find((page) => {
|
|
189
|
-
if (options.targetId && targets.get(page) !== options.targetId) {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
const url = page.url();
|
|
193
|
-
const matcher = options.url;
|
|
194
|
-
if (matcher instanceof RegExp) {
|
|
195
|
-
matcher.lastIndex = 0;
|
|
196
|
-
return matcher.test(url);
|
|
197
|
-
}
|
|
198
|
-
if (typeof matcher === 'function') {
|
|
199
|
-
return matcher(url);
|
|
200
|
-
}
|
|
201
|
-
return !matcher || matcher === url;
|
|
202
|
-
});
|
|
203
|
-
if (!selected) {
|
|
204
|
-
throw new RangeError('No tab matches the requested targetId/URL');
|
|
250
|
+
await ignoreCleanup(() => session?.detach?.());
|
|
205
251
|
}
|
|
206
252
|
}
|
|
253
|
+
let selected = requestedPage(pages, targets, options);
|
|
207
254
|
if (!selected) {
|
|
208
255
|
for (const page of pages) {
|
|
209
256
|
if (typeof page?.evaluate !== 'function') {
|
|
@@ -232,20 +279,38 @@ export async function pickForegroundPage(pages, options = {}) {
|
|
|
232
279
|
async function connectPlaywright({ options, loadPlaywright, storageState }) {
|
|
233
280
|
const { chromium } = await loadPlaywright();
|
|
234
281
|
const endpoint = options.cdpEndpoint ?? options.wsEndpoint;
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
282
|
+
const settings = buildPlaywrightConnectOptions(options);
|
|
283
|
+
let browser;
|
|
284
|
+
try {
|
|
285
|
+
browser = await chromium.connectOverCDP(endpoint, settings);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (
|
|
288
|
+
settings.noDefaults !== undefined ||
|
|
289
|
+
!/Browser\.setDownloadBehavior/.test(error.message) ||
|
|
290
|
+
!/Browser context management is not supported/.test(error.message)
|
|
291
|
+
) {
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
browser = await chromium.connectOverCDP(endpoint, {
|
|
295
|
+
...settings,
|
|
296
|
+
noDefaults: true,
|
|
297
|
+
});
|
|
242
298
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
(
|
|
299
|
+
try {
|
|
300
|
+
const context = browser.contexts()[0];
|
|
301
|
+
if (!context) {
|
|
302
|
+
throw new Error('Connected Playwright browser has no default context');
|
|
303
|
+
}
|
|
304
|
+
const page =
|
|
305
|
+
(await pickForegroundPage(context.pages(), options)) ??
|
|
306
|
+
(await context.newPage());
|
|
246
307
|
|
|
247
|
-
|
|
248
|
-
|
|
308
|
+
await restorePlaywrightStorageState({ context, storageState });
|
|
309
|
+
return { browser, page, detach: browser.close?.bind(browser) };
|
|
310
|
+
} catch (error) {
|
|
311
|
+
await ignoreCleanup(() => browser.close?.());
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
249
314
|
}
|
|
250
315
|
|
|
251
316
|
async function connectPuppeteer({ options, loadPuppeteer, storageState }) {
|
|
@@ -254,12 +319,17 @@ async function connectPuppeteer({ options, loadPuppeteer, storageState }) {
|
|
|
254
319
|
const browser = await puppeteer.connect(
|
|
255
320
|
buildPuppeteerConnectOptions(options)
|
|
256
321
|
);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
322
|
+
try {
|
|
323
|
+
const page =
|
|
324
|
+
(await pickForegroundPage(await browser.pages(), options)) ??
|
|
325
|
+
(await browser.newPage());
|
|
260
326
|
|
|
261
|
-
|
|
262
|
-
|
|
327
|
+
await restorePuppeteerStorageState({ page, storageState });
|
|
328
|
+
return { browser, page, detach: browser.disconnect?.bind(browser) };
|
|
329
|
+
} catch (error) {
|
|
330
|
+
await ignoreCleanup(() => browser.disconnect?.());
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
263
333
|
}
|
|
264
334
|
|
|
265
335
|
/**
|
|
@@ -313,7 +383,7 @@ export async function connectBrowserWithDependencies(
|
|
|
313
383
|
});
|
|
314
384
|
return { ...result, downloads };
|
|
315
385
|
} catch (error) {
|
|
316
|
-
await result.close();
|
|
386
|
+
await ignoreCleanup(() => result.close());
|
|
317
387
|
throw error;
|
|
318
388
|
}
|
|
319
389
|
}
|
|
@@ -346,14 +416,19 @@ export async function connectBrowserWithDependencies(
|
|
|
346
416
|
|
|
347
417
|
// An attached browser gets the same managed lifecycle as a launched one:
|
|
348
418
|
// the manager is built from the browser and page, not from how we got them.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
419
|
+
try {
|
|
420
|
+
const downloads = await attachDownloads({
|
|
421
|
+
engine,
|
|
422
|
+
browser: result.browser,
|
|
423
|
+
page: result.page,
|
|
424
|
+
downloads: normalizedOptions.downloads,
|
|
425
|
+
});
|
|
355
426
|
|
|
356
|
-
|
|
427
|
+
return { ...result, downloads };
|
|
428
|
+
} catch (error) {
|
|
429
|
+
await ignoreCleanup(() => result.detach?.());
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
357
432
|
}
|
|
358
433
|
|
|
359
434
|
export { buildPlaywrightConnectOptions, buildPuppeteerConnectOptions };
|