browser-commander 0.20.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jscpd.json +2 -1
- package/.prettierignore +1 -0
- package/CHANGELOG.md +159 -0
- package/README.md +216 -32
- package/bin/browser-commander.js +16 -0
- package/eslint.config.js +10 -0
- package/examples/react-test-app/src/App.jsx +24 -23
- package/extension/background.js +14 -0
- package/extension/manifest.json +12 -0
- package/extension/relay-handler.js +353 -0
- package/package.json +21 -4
- package/src/browser/attach/differences.js +131 -0
- package/src/browser/attach/extension-relay.js +402 -0
- package/src/browser/attach/index.js +81 -0
- package/src/browser/attach/snapshot-launch.js +146 -0
- package/src/browser/attach/snapshot.js +374 -0
- package/src/browser/attach/websocket.js +615 -0
- package/src/browser/browser-cookie-cache.js +26 -19
- package/src/browser/browser-cookie-credentials.js +6 -9
- package/src/browser/browser-cookies.js +5 -2
- package/src/browser/cdp-session.js +94 -0
- package/src/browser/connector.js +42 -4
- package/src/browser/debugging-port.js +155 -0
- package/src/browser/launch-options.js +63 -52
- package/src/browser/launch-restrictions.json +147 -0
- package/src/browser/launcher.js +329 -133
- package/src/browser/media.js +5 -0
- package/src/browser/migration/bookmarks.js +73 -0
- package/src/browser/migration/chromium-crypto.js +89 -0
- package/src/browser/migration/cookies.js +155 -0
- package/src/browser/migration/extensions.js +187 -0
- package/src/browser/migration/firefox-bookmarks.js +144 -0
- package/src/browser/migration/firefox-der.js +84 -0
- package/src/browser/migration/firefox-nss.js +281 -0
- package/src/browser/migration/firefox.js +334 -0
- package/src/browser/migration/fs-utils.js +51 -0
- package/src/browser/migration/history.js +103 -0
- package/src/browser/migration/index.js +307 -0
- package/src/browser/migration/os-crypt-keys.js +106 -0
- package/src/browser/migration/passwords.js +171 -0
- package/src/browser/migration/preferences.js +132 -0
- package/src/browser/migration/sqlite-snapshot.js +138 -0
- package/src/browser/open-in-user-browser.js +112 -0
- package/src/browser/parity.js +439 -0
- package/src/browser/profile-directory.js +205 -0
- package/src/browser/real-browser.js +545 -306
- package/src/browser/restrictions.js +148 -0
- package/src/browser/system-browser.js +232 -0
- package/src/browser/webdriver.js +572 -0
- package/src/cli/args.js +300 -0
- package/src/cli/commands.js +332 -0
- package/src/cli/dispatcher.js +99 -0
- package/src/cli/handle-methods.js +136 -0
- package/src/cli/handles.js +273 -0
- package/src/cli/high-level-methods.js +310 -0
- package/src/cli/main.js +67 -0
- package/src/cli/modules.js +100 -0
- package/src/cli/rpc-error.js +77 -0
- package/src/cli/script.js +77 -0
- package/src/cli/serve.js +144 -0
- package/src/cli/sessions.js +199 -0
- package/src/core/constants.js +7 -1
- package/src/core/engine-adapter.js +107 -1
- package/src/core/engine-detection.js +18 -3
- package/src/core/navigation-safety.js +4 -0
- package/src/core/webdriver-conversions.js +347 -0
- package/src/core/webdriver-events.js +229 -0
- package/src/core/webdriver-page.js +716 -0
- package/src/downloads/attach.js +6 -0
- package/src/elements/selectors.js +3 -2
- package/src/elements/visibility.js +4 -0
- package/src/exports.js +61 -2
- package/src/factory.js +19 -4
- package/src/fingerprint/apply.js +6 -0
- package/src/fingerprint/automation-parity.js +7 -8
- package/src/fingerprint/limitations.js +1 -1
- package/src/fingerprint/limitations.json +45 -2
- package/src/parity/harness.js +303 -0
- package/src/parity/probe.js +838 -0
- package/src/parity/version-page.js +144 -0
- package/src/traces/bundle.js +3 -2
- package/src/traces/observers.js +14 -5
- package/src/traces/recorder.js +3 -0
- package/src/traces/viewer.js +16 -8
- package/src/utilities/subprocess.js +199 -0
- package/tests/e2e/api-coverage.e2e.test.js +174 -0
- package/tests/e2e/cli.e2e.test.js +160 -0
- package/tests/e2e/fingerprint-parity.e2e.test.js +104 -46
- package/tests/e2e/google-signin-probe.e2e.test.js +112 -0
- package/tests/e2e/measure-parity.e2e.test.js +118 -0
- package/tests/e2e/playwright.e2e.test.js +26 -17
- package/tests/e2e/puppeteer.e2e.test.js +18 -23
- package/tests/e2e/webdriver.e2e.test.js +239 -0
- package/tests/fixtures/firefox-nss-fixtures.mjs +211 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0001.html +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0001.png +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0001.state.json +54 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.html +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.png +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.state.json +60 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.html +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.png +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.state.json +60 -0
- package/tests/fixtures/traces/conformance/expected/bundle/events.ndjson +18 -0
- package/tests/fixtures/traces/conformance/expected/bundle/manifest.json +77 -0
- package/tests/fixtures/traces/conformance/expected/bundle/mutations/0001.ndjson +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/mutations/0002.ndjson +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/viewer.html +309 -0
- package/tests/fixtures/traces/conformance/expected/redaction.json +86 -0
- package/tests/fixtures/traces/conformance/expected/trace.lino +27 -0
- package/tests/fixtures/traces/conformance/scenario.json +249 -0
- package/tests/helpers/cli-fakes.js +214 -0
- package/tests/helpers/declared-api.js +80 -0
- package/tests/helpers/e2e-browser.js +40 -14
- package/tests/helpers/fake-chrome.js +124 -0
- package/tests/helpers/migration-fixtures.js +242 -0
- package/tests/helpers/temp-directory.js +24 -0
- package/tests/helpers/webdriver-mocks.js +183 -0
- package/tests/unit/browser/attach/extension-relay.test.js +360 -0
- package/tests/unit/browser/attach/snapshot-launch.test.js +321 -0
- package/tests/unit/browser/attach/snapshot.test.js +409 -0
- package/tests/unit/browser/attach/websocket.test.js +240 -0
- package/tests/unit/browser/browser-cookie-lock.test.js +102 -0
- package/tests/unit/browser/browser-cookies.test.js +2 -32
- package/tests/unit/browser/cdp-session.test.js +162 -0
- package/tests/unit/browser/connector.test.js +23 -0
- package/tests/unit/browser/launch-building-blocks.test.js +361 -0
- package/tests/unit/browser/launch-options.test.js +41 -22
- package/tests/unit/browser/launcher.test.js +281 -102
- package/tests/unit/browser/media.test.js +13 -1
- package/tests/unit/browser/migration/bookmarks.test.js +69 -0
- package/tests/unit/browser/migration/chromium-crypto.test.js +94 -0
- package/tests/unit/browser/migration/cookies.test.js +94 -0
- package/tests/unit/browser/migration/extensions.test.js +104 -0
- package/tests/unit/browser/migration/firefox-nss.test.js +113 -0
- package/tests/unit/browser/migration/firefox.test.js +139 -0
- package/tests/unit/browser/migration/history.test.js +48 -0
- package/tests/unit/browser/migration/index.test.js +117 -0
- package/tests/unit/browser/migration/os-crypt-keys.test.js +95 -0
- package/tests/unit/browser/migration/passwords.test.js +144 -0
- package/tests/unit/browser/migration/preferences.test.js +77 -0
- package/tests/unit/browser/migration/sqlite-snapshot.test.js +75 -0
- package/tests/unit/browser/open-in-user-browser.test.js +123 -0
- package/tests/unit/browser/parity.test.js +281 -0
- package/tests/unit/browser/real-browser.test.js +458 -33
- package/tests/unit/browser/storage-state.test.js +2 -0
- package/tests/unit/browser/webdriver.test.js +473 -0
- package/tests/unit/cli/bin.test.js +69 -0
- package/tests/unit/cli/declared-api.test.js +49 -0
- package/tests/unit/cli/dispatcher.test.js +429 -0
- package/tests/unit/cli/handles.test.js +170 -0
- package/tests/unit/cli/main.test.js +437 -0
- package/tests/unit/cli/serve.test.js +167 -0
- package/tests/unit/core/navigation-manager.test.js +11 -1
- package/tests/unit/core/webdriver-conversions.test.js +235 -0
- package/tests/unit/core/webdriver-page.test.js +534 -0
- package/tests/unit/fingerprint/automation-parity.test.js +17 -4
- package/tests/unit/fingerprint/limitations.test.js +1 -1
- package/tests/unit/parity/harness.test.js +138 -0
- package/tests/unit/playwright-protocol-coverage.test.js +231 -0
- package/tests/unit/puppeteer-bindings-coverage.test.js +214 -0
- package/tests/unit/scripts/ci-timeout-budgets.test.js +27 -0
- package/tests/unit/scripts/feature-parity.test.js +327 -0
- package/tests/unit/traces/conformance.test.js +92 -0
- package/tests/unit/traces/reader.test.js +1 -0
- package/tests/unit/traces/recorder.test.js +1 -0
- package/tests/unit/traces/viewer.test.js +78 -0
- package/tests/unit/utilities/command-stream-only.test.js +28 -0
- package/tests/unit/utilities/subprocess.test.js +80 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
(trace: (format: browser-commander-trace) (links: 1) (schema: 2) (bundle: bundle) (mode: continuous) (engine: playwright) (started: '2026-01-01T00:00:00.000Z') (commander: 0.0.0-conformance))
|
|
2
|
+
(timeline: (sequence: 1) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: trace.start) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-1) (actor: recorder) (outcome: recorded) (mode: continuous) (engine: playwright) (dom: '{"html":true,"liveControlState":true,"liveState":true,"mutations":true,"openShadowRoots":true}') (events: '["navigation","interaction","console","pageerror","dialog","requestfailed","download"]') (initialCheckpoint: true))
|
|
3
|
+
(timeline: (sequence: 2) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: checkpoint) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-1) (actor: recorder) (target: 'https://[redacted]:[redacted]@example.com/start?ticket=[redacted]&lang=en#access_token=[redacted]&view=1') (outcome: recorded) (index: 1) (name: initial) (reason: initial) (url: 'https://[redacted]:[redacted]@example.com/start?ticket=[redacted]&lang=en#access_token=[redacted]&view=1') (truncated: false))
|
|
4
|
+
(checkpoint: (index: 1) (sequence: 2) (at: '2026-01-01T00:00:00.000Z') (name: initial) (actor: recorder) (reason: initial) (page: page-1) (navigation: nav-1) (url: 'https://[redacted]:[redacted]@example.com/start?ticket=[redacted]&lang=en#access_token=[redacted]&view=1') (outcome: recorded) (html: checkpoints/0001.html) (state: checkpoints/0001.state.json) (screenshot: checkpoints/0001.png))
|
|
5
|
+
(timeline: (sequence: 3) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: navigation) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (action: framenavigated) (target: 'https://example.com/next?token=[redacted]&q=a+b') (outcome: recorded) (phase: framenavigated) (mainFrame: true) (url: 'https://example.com/next?token=[redacted]&q=a+b'))
|
|
6
|
+
(timeline: (sequence: 4) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: navigation) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (action: framenavigated) (target: 'https://ads.example.com/frame') (outcome: recorded) (phase: framenavigated) (mainFrame: false) (url: 'https://ads.example.com/frame'))
|
|
7
|
+
(timeline: (sequence: 5) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: console) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (outcome: recorded) (level: log) (text: "key [redacted] loaded\n\ttab \u0022and\u0022 'quotes'"))
|
|
8
|
+
(timeline: (sequence: 6) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: pageerror) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (outcome: recorded) (message: 'boom: (unbalanced') (stack: 'Error: boom\n at main.js:1:2'))
|
|
9
|
+
(timeline: (sequence: 7) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: requestfailed) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (target: 'https://api.example.com/v1?api_key=[redacted]&page=2') (outcome: failed) (url: 'https://api.example.com/v1?api_key=[redacted]&page=2') (method: POST) (failure: 'net::ERR_FAILED'))
|
|
10
|
+
(timeline: (sequence: 8) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: dialog) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (outcome: recorded) (type: confirm) (message: 'Continue? #yes'))
|
|
11
|
+
(timeline: (sequence: 9) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: download) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (action: completed) (target: 'https://files.example.com/r.csv?signature=[redacted]') (outcome: recorded) (phase: completed) (id: download-1) (suggestedFilename: 'report final.csv') (path: '/downloads/report final.csv') (checksum: 'sha256:00ff') (bytes: 42) (url: 'https://files.example.com/r.csv?signature=[redacted]'))
|
|
12
|
+
(timeline: (sequence: 10) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: interaction) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: automation) (action: goto) (target: 'https://example.com/next?code=abc') (outcome: ok) (actionId: trace-1-action-1) (durationMs: 0) (ok: true))
|
|
13
|
+
(timeline: (sequence: 11) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: interaction) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: automation) (action: clickButton) (target: '#go') (outcome: failed) (actionId: trace-1-action-2) (durationMs: 0) (ok: false) (error: 'button #go is not visible'))
|
|
14
|
+
(timeline: (sequence: 12) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: dropped) (actor: recorder) (target: mutations) (outcome: dropped) (reason: size-limit) (member: mutations) (detail: '2 records over the in-page queue limit'))
|
|
15
|
+
(timeline: (sequence: 13) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: mutations) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: recorder) (target: mutations/0001.ndjson) (outcome: recorded) (member: mutations/0001.ndjson) (batches: 2) (checkpoint: 1) (frames: 1))
|
|
16
|
+
(timeline: (sequence: 14) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: checkpoint) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: automation) (target: 'https://example.com/next?token=[redacted]') (outcome: partial) (index: 2) (name: 'after click') (reason: checkpoint) (url: 'https://example.com/next?token=[redacted]') (truncated: true))
|
|
17
|
+
(checkpoint: (index: 2) (sequence: 14) (at: '2026-01-01T00:00:00.000Z') (name: 'after click') (actor: automation) (reason: checkpoint) (page: page-1) (navigation: nav-2) (url: 'https://example.com/next?token=[redacted]') (outcome: partial) (html: checkpoints/0002.html) (state: checkpoints/0002.state.json) (screenshot: checkpoints/0002.png))
|
|
18
|
+
(timeline: (sequence: 15) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: interaction) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: caller) (action: note) (outcome: recorded) (authorization: [redacted]) (detail: 'uses [redacted]') (pageUrl: 'https://example.com/?session=[redacted]') (count: 3) (ratio: 0.25) (whole: 2) (nested: '{"list":[1,"two",null,true]}'))
|
|
19
|
+
(timeline: (sequence: 16) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: mutations) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: recorder) (target: mutations/0002.ndjson) (outcome: recorded) (member: mutations/0002.ndjson) (batches: 1) (checkpoint: 2) (frames: 1))
|
|
20
|
+
(timeline: (sequence: 17) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: checkpoint) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: caller) (target: 'https://example.com/next?token=[redacted]') (outcome: partial) (index: 3) (name: final) (reason: failure) (url: 'https://example.com/next?token=[redacted]') (truncated: true))
|
|
21
|
+
(checkpoint: (index: 3) (sequence: 17) (at: '2026-01-01T00:00:00.000Z') (name: final) (actor: caller) (reason: failure) (page: page-1) (navigation: nav-2) (url: 'https://example.com/next?token=[redacted]') (outcome: partial) (html: checkpoints/0003.html) (state: checkpoints/0003.state.json) (screenshot: checkpoints/0003.png))
|
|
22
|
+
(timeline: (sequence: 18) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: trace.stop) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: recorder) (outcome: recorded) (discarded: false))
|
|
23
|
+
(control-diff: (checkpoint: 2) (previous: 1) (path: 'form > input:nth-of-type(1)') (change: changed) (before: alice) (after: bob) (actor: automation))
|
|
24
|
+
(control-diff: (checkpoint: 2) (previous: 1) (path: 'form > input:nth-of-type(2)') (change: changed) (before: false) (after: true) (actor: automation))
|
|
25
|
+
(control-diff: (checkpoint: 2) (previous: 1) (path: 'form > textarea') (change: added) (after: '[redacted] note') (actor: automation))
|
|
26
|
+
(control-diff: (checkpoint: 2) (previous: 1) (path: 'form > select') (change: removed) (before: a) (actor: automation))
|
|
27
|
+
(result: (outcome: partial) (stopped: '2026-01-01T00:00:00.000Z') (events: 18) (checkpoints: 3) (mutationBatches: 3) (dropped: 1) (truncated: true) (replay: checkpoints mutations childListPositions liveState identifiers))
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "One continuous trace recorded against a fake page by JavaScript, Python and Rust; all three must write these bytes.",
|
|
3
|
+
"clock": { "now": 1767225600000, "monotonic": 1234.5 },
|
|
4
|
+
"engine": "playwright",
|
|
5
|
+
"options": {
|
|
6
|
+
"mode": "continuous",
|
|
7
|
+
"commanderVersion": "0.0.0-conformance",
|
|
8
|
+
"privacy": {
|
|
9
|
+
"redactPatterns": ["sk-[a-z0-9]+"],
|
|
10
|
+
"redactQueryParams": ["ticket"]
|
|
11
|
+
},
|
|
12
|
+
"limits": { "maxQueuedMutations": 100 },
|
|
13
|
+
"links": true
|
|
14
|
+
},
|
|
15
|
+
"snapshot": {
|
|
16
|
+
"html": "<!DOCTYPE html>\n<html><head><title>Start</title></head><body><form><input name=\"user\" value=\"alice\"><input type=\"checkbox\" name=\"agree\"><select name=\"plan\"><option>a</option></select></form></body></html>",
|
|
17
|
+
"truncated": false,
|
|
18
|
+
"state": {
|
|
19
|
+
"url": "https://user:pw@example.com/start?ticket=t-1&lang=en#access_token=abc&view=1",
|
|
20
|
+
"title": "Start – “quoted” 'single' \\ back",
|
|
21
|
+
"readyState": "complete",
|
|
22
|
+
"scroll": { "x": 0, "y": 12.5 },
|
|
23
|
+
"viewport": { "width": 1280, "height": 720 },
|
|
24
|
+
"activeElement": null,
|
|
25
|
+
"controls": [
|
|
26
|
+
{
|
|
27
|
+
"path": "form > input:nth-of-type(1)",
|
|
28
|
+
"tag": "input",
|
|
29
|
+
"type": "text",
|
|
30
|
+
"name": "user",
|
|
31
|
+
"id": null,
|
|
32
|
+
"focused": false,
|
|
33
|
+
"value": "alice",
|
|
34
|
+
"redacted": false
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"path": "form > input:nth-of-type(2)",
|
|
38
|
+
"tag": "input",
|
|
39
|
+
"type": "checkbox",
|
|
40
|
+
"name": "agree",
|
|
41
|
+
"id": null,
|
|
42
|
+
"focused": false,
|
|
43
|
+
"value": "on",
|
|
44
|
+
"checked": false,
|
|
45
|
+
"redacted": false
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"path": "form > select",
|
|
49
|
+
"tag": "select",
|
|
50
|
+
"type": null,
|
|
51
|
+
"name": "plan",
|
|
52
|
+
"id": null,
|
|
53
|
+
"focused": false,
|
|
54
|
+
"value": "a",
|
|
55
|
+
"selected": [0],
|
|
56
|
+
"redacted": false
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"frames": []
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"steps": [
|
|
63
|
+
{
|
|
64
|
+
"op": "queueMutations",
|
|
65
|
+
"dropped": 2,
|
|
66
|
+
"batches": [
|
|
67
|
+
{
|
|
68
|
+
"at": 1767225600200,
|
|
69
|
+
"records": [
|
|
70
|
+
{
|
|
71
|
+
"type": "attributes",
|
|
72
|
+
"target": 4,
|
|
73
|
+
"name": "class",
|
|
74
|
+
"value": "busy"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"at": 1767225600100,
|
|
80
|
+
"records": [
|
|
81
|
+
{
|
|
82
|
+
"type": "live-state",
|
|
83
|
+
"target": 2,
|
|
84
|
+
"property": "value",
|
|
85
|
+
"value": "bo"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"type": "childList",
|
|
89
|
+
"target": 1,
|
|
90
|
+
"added": [{ "id": 9, "html": "<p>new
line</p>" }],
|
|
91
|
+
"removed": [],
|
|
92
|
+
"index": 3
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"op": "emit",
|
|
100
|
+
"event": "framenavigated",
|
|
101
|
+
"data": {
|
|
102
|
+
"url": "https://example.com/next?token=abc&q=a b",
|
|
103
|
+
"main": true
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"op": "emit",
|
|
108
|
+
"event": "framenavigated",
|
|
109
|
+
"data": { "url": "https://ads.example.com/frame", "main": false }
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"op": "emit",
|
|
113
|
+
"event": "console",
|
|
114
|
+
"data": {
|
|
115
|
+
"type": "log",
|
|
116
|
+
"text": "key sk-abc123 loaded\n\ttab \"and\" 'quotes'"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"op": "emit",
|
|
121
|
+
"event": "pageerror",
|
|
122
|
+
"data": {
|
|
123
|
+
"message": "boom: (unbalanced",
|
|
124
|
+
"stack": "Error: boom\n at main.js:1:2"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"op": "emit",
|
|
129
|
+
"event": "requestfailed",
|
|
130
|
+
"data": {
|
|
131
|
+
"url": "https://api.example.com/v1?api_key=k&page=2",
|
|
132
|
+
"method": "POST",
|
|
133
|
+
"errorText": "net::ERR_FAILED"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"op": "dialog",
|
|
138
|
+
"data": { "type": "confirm", "message": "Continue? #yes" }
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"op": "download",
|
|
142
|
+
"phase": "completed",
|
|
143
|
+
"artifact": {
|
|
144
|
+
"id": "download-1",
|
|
145
|
+
"suggestedFilename": "report final.csv",
|
|
146
|
+
"path": "/downloads/report final.csv",
|
|
147
|
+
"checksum": "sha256:00ff",
|
|
148
|
+
"bytes": 42,
|
|
149
|
+
"url": "https://files.example.com/r.csv?signature=s"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"op": "interaction",
|
|
154
|
+
"action": "goto",
|
|
155
|
+
"target": "https://example.com/next?code=abc"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"op": "interaction",
|
|
159
|
+
"action": "clickButton",
|
|
160
|
+
"target": "#go",
|
|
161
|
+
"fail": "button #go is not visible"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"op": "setSnapshot",
|
|
165
|
+
"snapshot": {
|
|
166
|
+
"html": "<!DOCTYPE html>\n<html><body><form><input name=\"user\" value=\"bob\"><textarea>sk-zzz9 note</textarea></form></body></html>",
|
|
167
|
+
"truncated": true,
|
|
168
|
+
"state": {
|
|
169
|
+
"url": "https://example.com/next?token=abc",
|
|
170
|
+
"title": "Next",
|
|
171
|
+
"readyState": "interactive",
|
|
172
|
+
"scroll": { "x": 0, "y": 0 },
|
|
173
|
+
"viewport": { "width": 1280, "height": 720 },
|
|
174
|
+
"activeElement": "form > textarea",
|
|
175
|
+
"controls": [
|
|
176
|
+
{
|
|
177
|
+
"path": "form > input:nth-of-type(1)",
|
|
178
|
+
"tag": "input",
|
|
179
|
+
"type": "text",
|
|
180
|
+
"name": "user",
|
|
181
|
+
"id": null,
|
|
182
|
+
"focused": false,
|
|
183
|
+
"value": "bob",
|
|
184
|
+
"redacted": false
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"path": "form > input:nth-of-type(2)",
|
|
188
|
+
"tag": "input",
|
|
189
|
+
"type": "checkbox",
|
|
190
|
+
"name": "agree",
|
|
191
|
+
"id": null,
|
|
192
|
+
"focused": false,
|
|
193
|
+
"value": "on",
|
|
194
|
+
"checked": true,
|
|
195
|
+
"redacted": false
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"path": "form > textarea",
|
|
199
|
+
"tag": "textarea",
|
|
200
|
+
"type": null,
|
|
201
|
+
"name": null,
|
|
202
|
+
"id": "note",
|
|
203
|
+
"focused": true,
|
|
204
|
+
"value": "sk-zzz9 note",
|
|
205
|
+
"scroll": { "x": 0, "y": 3 },
|
|
206
|
+
"redacted": false
|
|
207
|
+
}
|
|
208
|
+
],
|
|
209
|
+
"frames": [
|
|
210
|
+
{ "url": "https://ads.example.com/frame", "crossOrigin": true }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{ "op": "checkpoint", "name": "after click", "actor": "automation" },
|
|
216
|
+
{
|
|
217
|
+
"op": "queueMutations",
|
|
218
|
+
"dropped": 0,
|
|
219
|
+
"batches": [
|
|
220
|
+
{
|
|
221
|
+
"at": 1767225600300,
|
|
222
|
+
"records": [
|
|
223
|
+
{ "type": "characterData", "target": 7, "value": "done ✓ 😀" }
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"op": "event",
|
|
230
|
+
"name": "note",
|
|
231
|
+
"data": {
|
|
232
|
+
"authorization": "Bearer xyz",
|
|
233
|
+
"detail": "uses sk-q1w2",
|
|
234
|
+
"pageUrl": "https://example.com/?session=s1",
|
|
235
|
+
"count": 3,
|
|
236
|
+
"ratio": 0.25,
|
|
237
|
+
"whole": 2.0,
|
|
238
|
+
"nested": { "list": [1, "two", null, true] }
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"op": "checkpoint",
|
|
243
|
+
"name": "final",
|
|
244
|
+
"actor": "caller",
|
|
245
|
+
"reason": "failure"
|
|
246
|
+
},
|
|
247
|
+
{ "op": "stop" }
|
|
248
|
+
]
|
|
249
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fake engine objects for the CLI and JSON-RPC dispatcher tests (issue #104).
|
|
3
|
+
*
|
|
4
|
+
* The dispatcher only calls a handful of page methods for its high-level
|
|
5
|
+
* methods, so a small in-memory page is enough to check what it sends and
|
|
6
|
+
* what it returns without a browser.
|
|
7
|
+
*/
|
|
8
|
+
import { EventEmitter } from 'node:events';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
/** A page that records calls and keeps a URL, a title and field values. */
|
|
12
|
+
export class FakePage extends EventEmitter {
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.calls = [];
|
|
16
|
+
this.currentUrl = 'about:blank';
|
|
17
|
+
this.values = {};
|
|
18
|
+
this.cookies = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async goto(url) {
|
|
22
|
+
this.calls.push(['goto', url]);
|
|
23
|
+
this.currentUrl = url;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
url() {
|
|
27
|
+
return this.currentUrl;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async title() {
|
|
31
|
+
return `Title of ${this.currentUrl}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async click(selector) {
|
|
35
|
+
this.calls.push(['click', selector]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async fill(selector, value) {
|
|
39
|
+
this.calls.push(['fill', selector, value]);
|
|
40
|
+
this.values[selector] = value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
locator(selector) {
|
|
44
|
+
return { fill: (value) => this.fill(selector, value) };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async evaluate(expression, ...args) {
|
|
48
|
+
this.calls.push(['evaluate', expression]);
|
|
49
|
+
return typeof expression === 'function'
|
|
50
|
+
? expression(...args)
|
|
51
|
+
: { expression };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async screenshot(options = {}) {
|
|
55
|
+
this.calls.push(['screenshot', options]);
|
|
56
|
+
return Buffer.from('png-bytes');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async pdf(options = {}) {
|
|
60
|
+
this.calls.push(['pdf', options]);
|
|
61
|
+
return Buffer.from('%PDF');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async setCookie(...cookies) {
|
|
65
|
+
this.cookies.push(...cookies);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
context() {
|
|
69
|
+
return this.owner;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A Playwright-like context owning one page. */
|
|
74
|
+
export class FakeContext {
|
|
75
|
+
constructor(page) {
|
|
76
|
+
this.page = page;
|
|
77
|
+
page.owner = this;
|
|
78
|
+
this.cookies = [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pages() {
|
|
82
|
+
return [this.page];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async addCookies(cookies) {
|
|
86
|
+
if (cookies.some((cookie) => cookie.name === 'bad')) {
|
|
87
|
+
throw new Error('Invalid cookie fields');
|
|
88
|
+
}
|
|
89
|
+
this.cookies.push(...cookies);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Dependencies for `createDispatcher()` backed by fakes.
|
|
95
|
+
*
|
|
96
|
+
* @returns {{dependencies: Object, launches: Object[], connections: Object[], pages: FakePage[], relays: Object[]}}
|
|
97
|
+
*/
|
|
98
|
+
export function createFakeDependencies() {
|
|
99
|
+
const launches = [];
|
|
100
|
+
const connections = [];
|
|
101
|
+
const pages = [];
|
|
102
|
+
const relays = [];
|
|
103
|
+
const newPage = () => {
|
|
104
|
+
const page = new FakePage();
|
|
105
|
+
pages.push(page);
|
|
106
|
+
return page;
|
|
107
|
+
};
|
|
108
|
+
const dependencies = {
|
|
109
|
+
packageInfo: async () => ({
|
|
110
|
+
name: 'browser-commander',
|
|
111
|
+
version: '0.0.0-test',
|
|
112
|
+
language: 'js',
|
|
113
|
+
}),
|
|
114
|
+
launchBrowser: async (options) => {
|
|
115
|
+
const page = newPage();
|
|
116
|
+
const context = new FakeContext(page);
|
|
117
|
+
const record = { options, closed: false };
|
|
118
|
+
launches.push(record);
|
|
119
|
+
return {
|
|
120
|
+
browser: context,
|
|
121
|
+
page,
|
|
122
|
+
connectedBrowser: { name: 'fake-browser' },
|
|
123
|
+
cdpEndpoint: 'http://127.0.0.1:9222',
|
|
124
|
+
remoteDebuggingPort: 9222,
|
|
125
|
+
userDataDir: '/tmp/fake-profile',
|
|
126
|
+
temporaryProfile: true,
|
|
127
|
+
args: ['--user-data-dir=/tmp/fake-profile'],
|
|
128
|
+
close: async () => {
|
|
129
|
+
record.closed = true;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
connectBrowser: async (options) => {
|
|
134
|
+
const page = newPage();
|
|
135
|
+
const context = new FakeContext(page);
|
|
136
|
+
const record = { options, closed: false, disconnected: false };
|
|
137
|
+
connections.push(record);
|
|
138
|
+
const browser = new EventEmitter();
|
|
139
|
+
browser.close = async () => {
|
|
140
|
+
record.closed = true;
|
|
141
|
+
};
|
|
142
|
+
browser.disconnect = async () => {
|
|
143
|
+
record.disconnected = true;
|
|
144
|
+
};
|
|
145
|
+
browser.defaultBrowserContext = () => context;
|
|
146
|
+
return { browser, page };
|
|
147
|
+
},
|
|
148
|
+
startTrace: async ({ output }) => ({
|
|
149
|
+
path: output,
|
|
150
|
+
stopped: false,
|
|
151
|
+
async stop() {
|
|
152
|
+
this.stopped = true;
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
writeTraceViewer: async (bundle, output) =>
|
|
156
|
+
output ?? path.join(bundle, 'viewer.html'),
|
|
157
|
+
readBrowserCookies: async ({ domainFilter }) => [
|
|
158
|
+
{ name: 'sid', value: '1', domain: `.${domainFilter}`, path: '/' },
|
|
159
|
+
{ name: 'bad', value: '2', domain: `.${domainFilter}`, path: '/' },
|
|
160
|
+
],
|
|
161
|
+
loadEngine: async (name) => ({ default: new FakeEngine(name) }),
|
|
162
|
+
openInUserBrowser: async (url) => ({ opened: url, command: ['open', url] }),
|
|
163
|
+
migrateProfile: async (options) => ({ migrated: {}, options }),
|
|
164
|
+
measureParity: async () => ({ ok: true, unlisted: [] }),
|
|
165
|
+
snapshotUserDataDir: async (options) => ({
|
|
166
|
+
source: {
|
|
167
|
+
browser: options.browser,
|
|
168
|
+
profile: options.profile ?? 'Default',
|
|
169
|
+
userDataDir: `/home/user/.config/${options.browser}`,
|
|
170
|
+
},
|
|
171
|
+
target: options.to ?? '/tmp/fake-snapshot',
|
|
172
|
+
copied: { files: 3, databases: 1 },
|
|
173
|
+
skipped: [{ item: 'SingletonLock', reason: 'lock' }],
|
|
174
|
+
warnings: [],
|
|
175
|
+
}),
|
|
176
|
+
attachUserBrowser: async (options) => {
|
|
177
|
+
const relay = {
|
|
178
|
+
options,
|
|
179
|
+
mode: 'extension',
|
|
180
|
+
extension: { id: 'abc', version: '0.20.0', userAgent: 'Chrome' },
|
|
181
|
+
differences: [{ aspect: 'debugger-infobar', description: 'infobar' }],
|
|
182
|
+
closed: false,
|
|
183
|
+
sent: [],
|
|
184
|
+
tabs: async () => [
|
|
185
|
+
{ tabId: 1, url: 'https://example.com/', title: 'Example' },
|
|
186
|
+
],
|
|
187
|
+
session: async (tabId) => ({
|
|
188
|
+
tabId,
|
|
189
|
+
send: async (method, params) => {
|
|
190
|
+
relay.sent.push({ tabId, method, params });
|
|
191
|
+
return { ok: true };
|
|
192
|
+
},
|
|
193
|
+
}),
|
|
194
|
+
close: async () => {
|
|
195
|
+
relay.closed = true;
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
relays.push(relay);
|
|
199
|
+
return relay;
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
return { dependencies, launches, connections, pages, relays };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** A stand-in for the Playwright or Puppeteer entry object. */
|
|
206
|
+
export class FakeEngine {
|
|
207
|
+
constructor(name) {
|
|
208
|
+
this.name = name;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
launch() {
|
|
212
|
+
return `launched with ${this.name}`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public methods an engine declares in its TypeScript definitions
|
|
3
|
+
* (issue #104, API coverage).
|
|
4
|
+
*
|
|
5
|
+
* `serve --stdio` promises that the generic handle methods reach every public
|
|
6
|
+
* Playwright and Puppeteer method. The authoritative list of those methods is
|
|
7
|
+
* what each package ships for TypeScript users:
|
|
8
|
+
*
|
|
9
|
+
* - Puppeteer: `puppeteer-core/lib/types.d.ts` (`export declare class …`).
|
|
10
|
+
* - Playwright: `playwright-core/types/types.d.ts` (`export interface …`).
|
|
11
|
+
* Playwright's wire protocol description (`protocol.yml`) is not shipped
|
|
12
|
+
* in the npm package, so the client API declarations are used instead.
|
|
13
|
+
*
|
|
14
|
+
* Both files put every member of a class or interface on a line indented by
|
|
15
|
+
* exactly two spaces, which is all this parser relies on.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
|
|
23
|
+
const BLOCK_START =
|
|
24
|
+
/^export (?:declare )?(?:abstract )?(?:class|interface) ([A-Za-z]\w*)/u;
|
|
25
|
+
const MEMBER =
|
|
26
|
+
/^ {2}(?:(?:public|abstract|async|override)\s+)*([A-Za-z$][\w$]*)\??(?:<|\()/u;
|
|
27
|
+
const NOT_A_METHOD = /^ {2}(?:private|protected|static|get|set|readonly)\s/u;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Methods declared directly on each class or interface of a `.d.ts` file.
|
|
31
|
+
* Private, protected and static members, accessors, the constructor and
|
|
32
|
+
* `_`-prefixed names are left out.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} source - The declaration file contents
|
|
35
|
+
* @returns {Map<string, Set<string>>} Type name to method names
|
|
36
|
+
*/
|
|
37
|
+
export function parseDeclaredMethods(source) {
|
|
38
|
+
const types = new Map();
|
|
39
|
+
let current = null;
|
|
40
|
+
for (const line of source.split(/\r?\n/u)) {
|
|
41
|
+
const start = BLOCK_START.exec(line);
|
|
42
|
+
if (start) {
|
|
43
|
+
current = new Set();
|
|
44
|
+
types.set(start[1], current);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (line === '}') {
|
|
48
|
+
current = null;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (!current || NOT_A_METHOD.test(line)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const name = MEMBER.exec(line)?.[1];
|
|
55
|
+
if (name && name !== 'constructor' && !name.startsWith('_')) {
|
|
56
|
+
current.add(name);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return types;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The declaration file of an installed package. */
|
|
63
|
+
function declarationFile(packageName, relative) {
|
|
64
|
+
const root = path.dirname(require.resolve(`${packageName}/package.json`));
|
|
65
|
+
return readFileSync(path.join(root, relative), 'utf8');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Puppeteer's declared classes and their methods. */
|
|
69
|
+
export function puppeteerDeclarations() {
|
|
70
|
+
return parseDeclaredMethods(
|
|
71
|
+
declarationFile('puppeteer-core', 'lib/types.d.ts')
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Playwright's declared interfaces and their methods. */
|
|
76
|
+
export function playwrightDeclarations() {
|
|
77
|
+
return parseDeclaredMethods(
|
|
78
|
+
declarationFile('playwright-core', 'types/types.d.ts')
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -8,12 +8,34 @@
|
|
|
8
8
|
* read as the behaviour they check.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
import os from 'node:os';
|
|
13
|
-
import path from 'node:path';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
14
12
|
|
|
15
13
|
import { launchBrowser } from '../../src/browser/launcher.js';
|
|
16
14
|
|
|
15
|
+
/** The installed Chrome the parity suites compare against. */
|
|
16
|
+
export const PARITY_CHROME =
|
|
17
|
+
process.env.CHROME_PATH || '/usr/bin/google-chrome';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Why a parity suite cannot run here, or `false` when it can.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} [options]
|
|
23
|
+
* @param {boolean} [options.headless=false] - Headless suites need no display
|
|
24
|
+
* @returns {string|false}
|
|
25
|
+
*/
|
|
26
|
+
export function paritySkipReason({ headless = false } = {}) {
|
|
27
|
+
if (!process.env.RUN_E2E) {
|
|
28
|
+
return 'set RUN_E2E=true to run the parity tests';
|
|
29
|
+
}
|
|
30
|
+
if (!existsSync(PARITY_CHROME)) {
|
|
31
|
+
return `no Chrome binary at ${PARITY_CHROME}; set CHROME_PATH`;
|
|
32
|
+
}
|
|
33
|
+
if (!headless && process.platform === 'linux' && !process.env.DISPLAY) {
|
|
34
|
+
return 'headful parity needs a display; run under xvfb-run';
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
17
39
|
/**
|
|
18
40
|
* Extra Chrome arguments for environments without a usable sandbox.
|
|
19
41
|
*
|
|
@@ -24,6 +46,17 @@ import { launchBrowser } from '../../src/browser/launcher.js';
|
|
|
24
46
|
export const SANDBOX_ARGS =
|
|
25
47
|
process.env.CHROME_NO_SANDBOX === 'true' ? ['--no-sandbox'] : [];
|
|
26
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The sandbox arguments plus the CHROME_PATH binary, when one is set: the
|
|
51
|
+
* launch options every e2e suite shares.
|
|
52
|
+
*/
|
|
53
|
+
export const CHROME_LAUNCH_OPTIONS = {
|
|
54
|
+
args: SANDBOX_ARGS,
|
|
55
|
+
...(process.env.CHROME_PATH
|
|
56
|
+
? { executablePath: process.env.CHROME_PATH }
|
|
57
|
+
: {}),
|
|
58
|
+
};
|
|
59
|
+
|
|
27
60
|
/**
|
|
28
61
|
* Launch a real browser for one engine, with its own profile.
|
|
29
62
|
*
|
|
@@ -34,19 +67,13 @@ export const SANDBOX_ARGS =
|
|
|
34
67
|
*/
|
|
35
68
|
export async function launchE2EBrowser(options = {}) {
|
|
36
69
|
const { engine, downloadDirectory } = options;
|
|
37
|
-
const userDataDir = await fs.mkdtemp(
|
|
38
|
-
path.join(os.tmpdir(), 'bc-e2e-profile-')
|
|
39
|
-
);
|
|
40
70
|
|
|
71
|
+
// The default launch creates a fresh temporary profile and deletes it on
|
|
72
|
+
// close, so the helper no longer manages one itself.
|
|
41
73
|
const launched = await launchBrowser({
|
|
42
74
|
engine,
|
|
43
|
-
userDataDir,
|
|
44
75
|
headless: process.env.HEADLESS !== 'false',
|
|
45
|
-
|
|
46
|
-
args: SANDBOX_ARGS,
|
|
47
|
-
...(process.env.CHROME_PATH
|
|
48
|
-
? { executablePath: process.env.CHROME_PATH }
|
|
49
|
-
: {}),
|
|
76
|
+
...CHROME_LAUNCH_OPTIONS,
|
|
50
77
|
...(downloadDirectory
|
|
51
78
|
? { downloads: { directory: downloadDirectory } }
|
|
52
79
|
: {}),
|
|
@@ -60,8 +87,7 @@ export async function launchE2EBrowser(options = {}) {
|
|
|
60
87
|
* @returns {Promise<void>} Resolves once both are gone
|
|
61
88
|
*/
|
|
62
89
|
cleanup: async () => {
|
|
63
|
-
await launched.
|
|
64
|
-
await fs.rm(userDataDir, { recursive: true, force: true });
|
|
90
|
+
await launched.close();
|
|
65
91
|
},
|
|
66
92
|
};
|
|
67
93
|
}
|