browser-commander 0.28.0 → 0.30.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 +7 -20
- package/CHANGELOG.md +12 -0
- package/README.md +27 -10
- package/package.json +1 -1
- package/scripts/run-tests.mjs +3 -1
- package/src/browser/browser-profile-files.js +5 -2
- package/src/browser/connector.js +140 -65
- package/src/browser/launcher.js +19 -8
- 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 +33 -25
- 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/commander-actions.js +146 -0
- package/src/traces/debug-session.js +119 -0
- package/src/traces/drain-timer.js +25 -0
- package/src/traces/links.js +44 -5
- package/src/traces/mutation-stream.js +12 -5
- package/src/traces/network.js +65 -13
- package/src/traces/observers.js +50 -46
- package/src/traces/page-capture.js +65 -21
- package/src/traces/recorder.js +79 -28
- 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/src/traces/user-actions.js +405 -0
- package/tests/e2e/issue-160.e2e.test.js +291 -0
- package/tests/e2e/issue-162-recording.e2e.test.js +175 -0
- package/tests/e2e/traces.e2e.test.js +4 -1
- 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 +4 -4
- 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 +4 -4
- package/tests/fixtures/traces/conformance/scenario.json +1 -0
- package/tests/helpers/e2e-browser.js +2 -1
- 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/issue-162-defaults.test.js +223 -0
- package/tests/unit/traces/issue-162-user-actions.test.js +181 -0
- package/tests/unit/traces/network.test.js +55 -3
- package/tests/unit/traces/recorder.test.js +4 -1
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,
|
|
@@ -64,8 +58,8 @@
|
|
|
64
58
|
"326f1ff9016e1e37": 1,
|
|
65
59
|
"33357ce94e07cc4b": 1,
|
|
66
60
|
"35cf2769645f3234": 1,
|
|
61
|
+
"364e8930fe8fdf71": 1,
|
|
67
62
|
"37bd4114d0089f49": 1,
|
|
68
|
-
"39cf8134a7fdaa31": 1,
|
|
69
63
|
"3adca52a3f04837c": 1,
|
|
70
64
|
"3c1017def7b3216c": 1,
|
|
71
65
|
"3c5cb4b62d3739e3": 1,
|
|
@@ -79,7 +73,6 @@
|
|
|
79
73
|
"441570d51d6f70a4": 1,
|
|
80
74
|
"457c71f32f53833d": 1,
|
|
81
75
|
"4715a17d690865ee": 1,
|
|
82
|
-
"47dc143be0bd8be5": 1,
|
|
83
76
|
"4b0d3cc5c27227e7": 1,
|
|
84
77
|
"4c3b94caa28045d0": 1,
|
|
85
78
|
"4d1ff99bb19f5685": 1,
|
|
@@ -107,16 +100,15 @@
|
|
|
107
100
|
"5ba2f3218754f04c": 1,
|
|
108
101
|
"60052da86e7bb241": 1,
|
|
109
102
|
"608e1dd27138ec6f": 1,
|
|
110
|
-
"60a3149247760d9b": 1,
|
|
111
103
|
"60d0c58a01fc8746": 1,
|
|
112
104
|
"61ae5da6cc003836": 1,
|
|
113
105
|
"61bcadcd8dc9f63e": 1,
|
|
114
106
|
"63ff613b1780c67a": 1,
|
|
115
107
|
"642cc89ed49f8a21": 1,
|
|
116
108
|
"6479c3604c153ec4": 1,
|
|
109
|
+
"65ce52f8afd6a952": 1,
|
|
117
110
|
"66fb97eebd7ff0dc": 1,
|
|
118
111
|
"67a95e428b8fa31c": 1,
|
|
119
|
-
"67c309590c5d69a1": 1,
|
|
120
112
|
"6880d620e0e67229": 1,
|
|
121
113
|
"69d50e6f6a5f2ee9": 1,
|
|
122
114
|
"6a7ed9ad3fc00a39": 1,
|
|
@@ -153,6 +145,7 @@
|
|
|
153
145
|
"86aae81b1aa0d21b": 1,
|
|
154
146
|
"87484a2744e09748": 1,
|
|
155
147
|
"8c2b1408886c1db1": 1,
|
|
148
|
+
"8d24ce7d6e8f6795": 1,
|
|
156
149
|
"8e55579a412bc058": 1,
|
|
157
150
|
"8ec18cdbd4a96518": 1,
|
|
158
151
|
"901317bc4d7d30d0": 1,
|
|
@@ -175,13 +168,12 @@
|
|
|
175
168
|
"a36f66312d5bfbf8": 1,
|
|
176
169
|
"a4c8498d4f2d2191": 1,
|
|
177
170
|
"a5298242b5439858": 1,
|
|
171
|
+
"a5861532b8efced8": 1,
|
|
178
172
|
"a6ad7ab6c05656dc": 1,
|
|
179
173
|
"a6ffab1aacd00af6": 1,
|
|
180
174
|
"a71a4b720d466e32": 1,
|
|
181
|
-
"a71fab6edf38c846": 1,
|
|
182
175
|
"a78d76908162699e": 1,
|
|
183
176
|
"a9bd4ffef6da5faf": 1,
|
|
184
|
-
"ac3fe4ef966e0209": 1,
|
|
185
177
|
"acc95d694eb2f581": 1,
|
|
186
178
|
"ad4cfa76daa1b6fc": 1,
|
|
187
179
|
"ad50b48938af8aef": 1,
|
|
@@ -203,7 +195,6 @@
|
|
|
203
195
|
"bb5b0c8d22c9667a": 1,
|
|
204
196
|
"bc9115be3a462930": 1,
|
|
205
197
|
"bd0ae42c13d5b4f9": 1,
|
|
206
|
-
"bd17b166d89cb709": 1,
|
|
207
198
|
"bdb8ca169be36b80": 1,
|
|
208
199
|
"bdcd11c2bfb5fde0": 1,
|
|
209
200
|
"be0900fb3e87164e": 1,
|
|
@@ -221,7 +212,6 @@
|
|
|
221
212
|
"c714beebc66381dd": 1,
|
|
222
213
|
"c9cf3c28bed33a5b": 1,
|
|
223
214
|
"ca7e08d1eec2ad8c": 1,
|
|
224
|
-
"cab590a1d04b5a05": 1,
|
|
225
215
|
"cbd444e9a6a9072e": 1,
|
|
226
216
|
"cebff7d1cc87f646": 1,
|
|
227
217
|
"cfc928aa393146b6": 1,
|
|
@@ -235,8 +225,6 @@
|
|
|
235
225
|
"d6fa9884eee815f0": 1,
|
|
236
226
|
"d86bafb915488255": 1,
|
|
237
227
|
"da2c10a66acdb52f": 1,
|
|
238
|
-
"dabb9fc09638e178": 1,
|
|
239
|
-
"dcc616702e5b0cb4": 1,
|
|
240
228
|
"de0bf2317c53d427": 1,
|
|
241
229
|
"de1b481bfe8c3e58": 1,
|
|
242
230
|
"df2dc922f40485e7": 1,
|
|
@@ -256,7 +244,6 @@
|
|
|
256
244
|
"eb9a70c2a21094ff": 1,
|
|
257
245
|
"ebe03c5f8c2f5147": 1,
|
|
258
246
|
"ee19c8b9b332ee98": 1,
|
|
259
|
-
"ef6b9112e659147b": 1,
|
|
260
247
|
"f07f979f8c2706e8": 1,
|
|
261
248
|
"f38f72095a67c95e": 1,
|
|
262
249
|
"f40ba84763547f41": 1,
|
|
@@ -265,7 +252,6 @@
|
|
|
265
252
|
"f4bfa7e503443cd1": 1,
|
|
266
253
|
"f6678d6748e9c902": 1,
|
|
267
254
|
"f67a07a611f39cb2": 1,
|
|
268
|
-
"f762b61eef0af530": 1,
|
|
269
255
|
"f7b1f9d699bcd580": 1,
|
|
270
256
|
"f81e07af5d3a1c3c": 1,
|
|
271
257
|
"f904844f89cab699": 1,
|
|
@@ -275,6 +261,7 @@
|
|
|
275
261
|
"fb4a079a529a6285": 1,
|
|
276
262
|
"fc0d7bde0033411d": 1,
|
|
277
263
|
"fccfd8b004c12dd4": 1,
|
|
278
|
-
"fe859df9f97c75d4": 1
|
|
264
|
+
"fe859df9f97c75d4": 1,
|
|
265
|
+
"ff3b6c3e0b8bf33b": 1
|
|
279
266
|
}
|
|
280
267
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.30.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0abb9c9: Enable complete JavaScript session recording by default for commanders, launches, and explicit traces. Record trusted user actions with private target descriptions alongside commander actions, DOM changes, network metadata, and a self-contained trace.lino export. Use debug: false or BROWSER_COMMANDER_DEBUG=0 to opt out.
|
|
8
|
+
|
|
9
|
+
## 0.29.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
3
15
|
## 0.28.0
|
|
4
16
|
|
|
5
17
|
### 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,10 +812,17 @@ 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
|
|
|
819
|
+
JavaScript commanders and browser launches record complete sessions by default
|
|
820
|
+
under `./browser-commander-traces/`, including trusted user actions, DOM changes,
|
|
821
|
+
network metadata and `trace.lino`. Await `commander.ready` before using its raw
|
|
822
|
+
page, and call `commander.destroy()` or the launch's `close()` to finish the files.
|
|
823
|
+
Use `debug: false` or `BROWSER_COMMANDER_DEBUG=0` to opt out; `debug: { output }`
|
|
824
|
+
chooses the directory. `commander.tracePath` exposes its path.
|
|
825
|
+
|
|
809
826
|
`startTrace()` records a session into a schema-versioned bundle any of the three
|
|
810
827
|
languages can read - the manifest, an ordered NDJSON timeline, per-checkpoint
|
|
811
828
|
DOM snapshots and the mutation batches between them:
|
|
@@ -1028,7 +1045,7 @@ This is the **official extensibility mechanism** while awaiting browser-commande
|
|
|
1028
1045
|
|
|
1029
1046
|
## Command Line
|
|
1030
1047
|
|
|
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](
|
|
1048
|
+
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
1049
|
|
|
1033
1050
|
```bash
|
|
1034
1051
|
npx browser-commander version
|
|
@@ -1068,4 +1085,4 @@ See [src/ARCHITECTURE.md](src/ARCHITECTURE.md) for detailed architecture documen
|
|
|
1068
1085
|
|
|
1069
1086
|
[UNLICENSE](../LICENSE)
|
|
1070
1087
|
|
|
1071
|
-
See [navigation budgets, launch diagnostics, reusable helpers and sessions](
|
|
1088
|
+
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
package/scripts/run-tests.mjs
CHANGED
|
@@ -177,7 +177,9 @@ function runNodeTests(options, testFiles) {
|
|
|
177
177
|
process.execPath,
|
|
178
178
|
buildNodeTestArgs(options, testFiles),
|
|
179
179
|
{
|
|
180
|
-
|
|
180
|
+
// Most fixtures exercise unrelated APIs with incomplete fake pages. The
|
|
181
|
+
// default-recording regressions explicitly unset this harness opt-out.
|
|
182
|
+
env: { BROWSER_COMMANDER_DEBUG: '0', ...process.env, ...options.env },
|
|
181
183
|
stdio: 'inherit',
|
|
182
184
|
}
|
|
183
185
|
);
|
|
@@ -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 };
|
package/src/browser/launcher.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { assertSupportedEngine } from './connector.js';
|
|
9
9
|
import { emulateMedia } from './media.js';
|
|
10
10
|
import { reserveLoopbackPort } from './debugging-port.js';
|
|
11
|
+
import { attachLaunchDebug } from '../traces/debug-session.js';
|
|
11
12
|
import {
|
|
12
13
|
buildPlaywrightLaunchOptions,
|
|
13
14
|
buildPuppeteerLaunchOptions,
|
|
@@ -319,12 +320,16 @@ export async function launchBrowserWithDependencies(
|
|
|
319
320
|
) {
|
|
320
321
|
if (options.keepOpen) {
|
|
321
322
|
const { connectOrLaunch } = await import('./persistent-session.js');
|
|
322
|
-
return
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
323
|
+
return attachLaunchDebug(
|
|
324
|
+
await connectOrLaunch({
|
|
325
|
+
...options,
|
|
326
|
+
userDataDir:
|
|
327
|
+
options.userDataDir ?? (await createTemporaryUserDataDir()),
|
|
328
|
+
remoteDebuggingPort:
|
|
329
|
+
options.remoteDebuggingPort ?? (await reserveLoopbackPort()),
|
|
330
|
+
}),
|
|
331
|
+
options
|
|
332
|
+
);
|
|
328
333
|
}
|
|
329
334
|
sessionPersistencePath(options);
|
|
330
335
|
if (isSafariChannel(options.channel ?? options.browser)) {
|
|
@@ -333,11 +338,17 @@ export async function launchBrowserWithDependencies(
|
|
|
333
338
|
launch: options.launch ?? 'real',
|
|
334
339
|
attach: options.attach,
|
|
335
340
|
});
|
|
336
|
-
return
|
|
341
|
+
return attachLaunchDebug(
|
|
342
|
+
await launchSafari(options, dependencies),
|
|
343
|
+
options
|
|
344
|
+
);
|
|
337
345
|
}
|
|
338
346
|
const result = await launchNonSafariBrowser(options, dependencies);
|
|
339
347
|
try {
|
|
340
|
-
return await
|
|
348
|
+
return await attachLaunchDebug(
|
|
349
|
+
await installSessionPersistence(result, options),
|
|
350
|
+
options
|
|
351
|
+
);
|
|
341
352
|
} catch (error) {
|
|
342
353
|
await result.close();
|
|
343
354
|
throw error;
|