@itmetsam/samurai-framework 0.1.0 → 0.2.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/README.md +129 -0
- package/bin/samurai.mjs +31 -0
- package/dist/api.js +3 -0
- package/dist/assert/assertion-error.d.ts +23 -0
- package/dist/assert/assertion-error.js +45 -0
- package/dist/assert/expect.d.ts +55 -0
- package/dist/assert/expect.js +188 -0
- package/dist/browser/browser-context.d.ts +91 -0
- package/dist/browser/browser-context.js +146 -0
- package/dist/browser/browser-finder.d.ts +2 -0
- package/dist/browser/browser-finder.js +95 -0
- package/dist/browser/browser.d.ts +95 -0
- package/dist/browser/browser.js +372 -0
- package/dist/browser/context-tree.d.ts +23 -0
- package/dist/browser/context-tree.js +60 -0
- package/dist/browser/dialog.d.ts +37 -0
- package/dist/browser/dialog.js +61 -0
- package/dist/browser/download.d.ts +71 -0
- package/dist/browser/download.js +137 -0
- package/dist/browser/downloads-dir.d.ts +8 -0
- package/dist/browser/downloads-dir.js +38 -0
- package/dist/browser/emulation.d.ts +61 -0
- package/dist/browser/emulation.js +144 -0
- package/dist/browser/file-chooser.d.ts +39 -0
- package/dist/browser/file-chooser.js +86 -0
- package/dist/browser/file-paths.d.ts +4 -0
- package/dist/browser/file-paths.js +18 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/navigation-error.d.ts +13 -0
- package/dist/browser/navigation-error.js +19 -0
- package/dist/browser/page-logs.d.ts +86 -0
- package/dist/browser/page-logs.js +149 -0
- package/dist/browser/page-wait.d.ts +9 -0
- package/dist/browser/page-wait.js +17 -0
- package/dist/browser/page.d.ts +370 -0
- package/dist/browser/page.js +1038 -0
- package/dist/browser/pdf.d.ts +72 -0
- package/dist/browser/pdf.js +37 -0
- package/dist/browser/screenshot.d.ts +29 -0
- package/dist/browser/screenshot.js +42 -0
- package/dist/cli/args.d.ts +35 -0
- package/dist/cli/args.js +69 -0
- package/dist/cli/cli.cli-test.d.ts +1 -0
- package/dist/cli/cli.cli-test.js +170 -0
- package/dist/cli/console-reporter.d.ts +15 -0
- package/dist/cli/console-reporter.js +52 -0
- package/dist/cli/init.d.ts +7 -0
- package/dist/cli/init.js +61 -0
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.js +156 -0
- package/dist/config/config.d.ts +24 -0
- package/dist/config/config.js +42 -0
- package/dist/config/default.d.ts +2 -0
- package/dist/config/default.js +3 -0
- package/dist/config/mask.d.ts +11 -0
- package/dist/config/mask.js +85 -0
- package/dist/config/run-settings.d.ts +28 -0
- package/dist/config/run-settings.js +61 -0
- package/dist/config/secrets.d.ts +10 -0
- package/dist/config/secrets.js +67 -0
- package/dist/config/variables.d.ts +11 -0
- package/dist/config/variables.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/locator/action-timeout-error.d.ts +25 -0
- package/dist/locator/action-timeout-error.js +35 -0
- package/dist/locator/element-state.d.ts +44 -0
- package/dist/locator/element-state.js +109 -0
- package/dist/locator/label-locator.d.ts +13 -0
- package/dist/locator/label-locator.js +49 -0
- package/dist/locator/locator.d.ts +168 -0
- package/dist/locator/locator.js +591 -0
- package/dist/locator/selector-errors.d.ts +9 -0
- package/dist/locator/selector-errors.js +15 -0
- package/dist/locator/selector.d.ts +60 -0
- package/dist/locator/selector.js +113 -0
- package/dist/locator/text-locator.d.ts +13 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +30 -0
- package/dist/network/data-collector.d.ts +26 -0
- package/dist/network/data-collector.js +77 -0
- package/dist/network/network-tracker.d.ts +94 -0
- package/dist/network/network-tracker.js +198 -0
- package/dist/network/network-wait.d.ts +16 -0
- package/dist/network/network-wait.js +24 -0
- package/dist/network/response.d.ts +22 -0
- package/dist/network/response.js +42 -0
- package/dist/network/router.d.ts +120 -0
- package/dist/network/router.js +343 -0
- package/dist/network/url-match.d.ts +8 -0
- package/dist/network/url-match.js +38 -0
- package/dist/recorder/apply.d.ts +3 -0
- package/dist/recorder/apply.js +10 -0
- package/dist/recorder/capture.d.ts +18 -0
- package/dist/recorder/capture.js +198 -0
- package/dist/recorder/index.d.ts +3 -0
- package/dist/recorder/index.js +3 -0
- package/dist/recorder/normalise.d.ts +68 -0
- package/dist/recorder/normalise.js +104 -0
- package/dist/recorder/ranking.d.ts +9 -0
- package/dist/recorder/ranking.js +21 -0
- package/dist/recorder/recorder.d.ts +52 -0
- package/dist/recorder/recorder.js +143 -0
- package/dist/recorder/session.d.ts +49 -0
- package/dist/recorder/session.js +113 -0
- package/dist/runner/module-type.d.ts +6 -0
- package/dist/runner/module-type.js +29 -0
- package/dist/runner/page-logs-report.d.ts +40 -0
- package/dist/runner/page-logs-report.js +44 -0
- package/dist/runner/prepare-run.d.ts +14 -0
- package/dist/runner/prepare-run.js +20 -0
- package/dist/runner/project.d.ts +17 -0
- package/dist/runner/project.js +33 -0
- package/dist/runner/registry.d.ts +7 -0
- package/dist/runner/registry.js +40 -0
- package/dist/runner/reporter.d.ts +43 -0
- package/dist/runner/reporter.js +83 -0
- package/dist/runner/run.d.ts +27 -0
- package/dist/runner/run.js +34 -0
- package/dist/runner/test-error.d.ts +3 -0
- package/dist/runner/test-error.js +18 -0
- package/dist/runner/test-runner.d.ts +44 -0
- package/dist/runner/test-runner.js +205 -0
- package/dist/script/call-function.d.ts +34 -0
- package/dist/script/call-function.js +71 -0
- package/dist/script/element-handle.d.ts +10 -0
- package/{src/script/element-handle.ts → dist/script/element-handle.js} +4 -4
- package/dist/script/helpers.d.ts +28 -0
- package/dist/script/helpers.js +50 -0
- package/dist/script/preload.d.ts +31 -0
- package/dist/script/preload.js +71 -0
- package/dist/script/serialize.d.ts +29 -0
- package/dist/script/serialize.js +207 -0
- package/dist/steps/build.d.ts +5 -0
- package/dist/steps/build.js +38 -0
- package/dist/steps/edit.d.ts +30 -0
- package/dist/steps/edit.js +80 -0
- package/dist/steps/emit.d.ts +6 -0
- package/dist/steps/emit.js +73 -0
- package/dist/steps/index.d.ts +5 -0
- package/dist/steps/index.js +4 -0
- package/dist/steps/model.d.ts +103 -0
- package/dist/steps/model.js +1 -0
- package/dist/steps/parse.d.ts +3 -0
- package/dist/steps/parse.js +272 -0
- package/dist/transport/bidi-connection.d.ts +46 -0
- package/dist/transport/bidi-connection.js +176 -0
- package/dist/transport/bidi-error.d.ts +10 -0
- package/dist/transport/bidi-error.js +10 -0
- package/dist/transport/subscriptions.d.ts +36 -0
- package/dist/transport/subscriptions.js +81 -0
- package/{src → dist}/types/test.d.ts +10 -4
- package/dist/wait/wait-until.d.ts +20 -0
- package/dist/wait/wait-until.js +63 -0
- package/package.json +44 -7
- package/.claude/settings.local.json +0 -7
- package/.env.example +0 -3
- package/.github/actions/setup/action.yml +0 -16
- package/.github/workflows/ci.yml +0 -71
- package/.trunk/configs/.markdownlint.yaml +0 -2
- package/.trunk/configs/.yamllint.yaml +0 -7
- package/.trunk/configs/svgo.config.mjs +0 -14
- package/.trunk/trunk.yaml +0 -34
- package/docs/superpowers/bidi-roadmap.md +0 -60
- package/docs/superpowers/plans/2026-09-29-auto-wait.md +0 -1827
- package/docs/superpowers/plans/2026-09-29-expect.md +0 -1350
- package/docs/superpowers/plans/2026-10-01-ci.md +0 -350
- package/docs/superpowers/plans/2026-10-01-test-api-alignment.md +0 -1821
- package/docs/superpowers/recorder-roadmap.md +0 -52
- package/docs/superpowers/specs/2026-09-29-auto-wait-design.md +0 -185
- package/docs/superpowers/specs/2026-09-29-bidi-foundation-design.md +0 -130
- package/docs/superpowers/specs/2026-09-29-contexts-and-storage-design.md +0 -88
- package/docs/superpowers/specs/2026-09-29-downloads-and-file-dialogs-design.md +0 -94
- package/docs/superpowers/specs/2026-09-29-emulation-design.md +0 -88
- package/docs/superpowers/specs/2026-09-29-expect-design.md +0 -130
- package/docs/superpowers/specs/2026-09-29-locate-nodes-design.md +0 -90
- package/docs/superpowers/specs/2026-09-29-logs-and-errors-design.md +0 -87
- package/docs/superpowers/specs/2026-09-29-navigation-waits-design.md +0 -83
- package/docs/superpowers/specs/2026-09-29-network-data-design.md +0 -78
- package/docs/superpowers/specs/2026-09-29-network-interception-design.md +0 -91
- package/docs/superpowers/specs/2026-09-29-network-tracking-design.md +0 -86
- package/docs/superpowers/specs/2026-09-29-page-api-design.md +0 -90
- package/docs/superpowers/specs/2026-09-29-preload-scripts-design.md +0 -82
- package/docs/superpowers/specs/2026-09-29-script-call-function-design.md +0 -107
- package/docs/superpowers/specs/2026-09-29-set-files-design.md +0 -62
- package/docs/superpowers/specs/2026-09-29-user-prompts-design.md +0 -75
- package/docs/superpowers/specs/2026-10-01-ci-design.md +0 -106
- package/docs/superpowers/specs/2026-10-01-test-api-alignment-design.md +0 -241
- package/public/logo-dark.svg +0 -236
- package/public/logo-light.svg +0 -262
- package/samurai.config.ts +0 -7
- package/src/assert/assertion-error.test.ts +0 -55
- package/src/assert/assertion-error.ts +0 -62
- package/src/assert/expect-locator.test.ts +0 -217
- package/src/assert/expect.test.ts +0 -73
- package/src/assert/expect.ts +0 -299
- package/src/browser/browser-context.browser-test.ts +0 -201
- package/src/browser/browser-context.test.ts +0 -407
- package/src/browser/browser-context.ts +0 -208
- package/src/browser/browser-finder.ts +0 -110
- package/src/browser/browser.test.ts +0 -88
- package/src/browser/browser.ts +0 -515
- package/src/browser/context-tree.test.ts +0 -86
- package/src/browser/context-tree.ts +0 -70
- package/src/browser/dialog.browser-test.ts +0 -144
- package/src/browser/dialog.test.ts +0 -322
- package/src/browser/dialog.ts +0 -83
- package/src/browser/download.test.ts +0 -161
- package/src/browser/download.ts +0 -172
- package/src/browser/downloads-dir.test.ts +0 -75
- package/src/browser/downloads-dir.ts +0 -39
- package/src/browser/emulation.browser-test.ts +0 -186
- package/src/browser/emulation.test.ts +0 -200
- package/src/browser/emulation.ts +0 -204
- package/src/browser/file-chooser.test.ts +0 -142
- package/src/browser/file-chooser.ts +0 -99
- package/src/browser/file-paths.ts +0 -18
- package/src/browser/goto.browser-test.ts +0 -44
- package/src/browser/navigation-error.ts +0 -26
- package/src/browser/navigation.browser-test.ts +0 -172
- package/src/browser/page-api.browser-test.ts +0 -228
- package/src/browser/page-logs.browser-test.ts +0 -119
- package/src/browser/page-logs.test.ts +0 -212
- package/src/browser/page-logs.ts +0 -208
- package/src/browser/page-wait.ts +0 -19
- package/src/browser/page.test.ts +0 -1567
- package/src/browser/page.ts +0 -1176
- package/src/browser/pdf.ts +0 -62
- package/src/browser/screenshot.ts +0 -65
- package/src/browser/transfers.browser-test.ts +0 -180
- package/src/config/config.ts +0 -33
- package/src/config/default.ts +0 -5
- package/src/config/fixtures.test.ts +0 -137
- package/src/config/mask.test.ts +0 -143
- package/src/config/mask.ts +0 -87
- package/src/config/run-settings.test.ts +0 -163
- package/src/config/run-settings.ts +0 -114
- package/src/config/secrets.ts +0 -70
- package/src/config/variables.ts +0 -31
- package/src/index.ts +0 -10
- package/src/locator/action-timeout-error.test.ts +0 -83
- package/src/locator/action-timeout-error.ts +0 -72
- package/src/locator/element-state.test.ts +0 -181
- package/src/locator/element-state.ts +0 -159
- package/src/locator/locate-nodes.browser-test.ts +0 -169
- package/src/locator/locator-actions.test.ts +0 -355
- package/src/locator/locator-chain.test.ts +0 -292
- package/src/locator/locator.browser-test.ts +0 -137
- package/src/locator/locator.test.ts +0 -179
- package/src/locator/locator.ts +0 -656
- package/src/locator/selector-errors.ts +0 -18
- package/src/locator/selector.test.ts +0 -124
- package/src/locator/selector.ts +0 -107
- package/src/locator/set-files.browser-test.ts +0 -141
- package/src/locator/set-files.test.ts +0 -191
- package/src/locator/text-locator.test.ts +0 -118
- package/src/logger/index.test.ts +0 -28
- package/src/logger/index.ts +0 -40
- package/src/network/data-collector.test.ts +0 -111
- package/src/network/data-collector.ts +0 -92
- package/src/network/network-data.browser-test.ts +0 -192
- package/src/network/network-tracker.browser-test.ts +0 -151
- package/src/network/network-tracker.test.ts +0 -307
- package/src/network/network-tracker.ts +0 -286
- package/src/network/network-wait.ts +0 -28
- package/src/network/response.test.ts +0 -93
- package/src/network/response.ts +0 -58
- package/src/network/router.browser-test.ts +0 -260
- package/src/network/router.test.ts +0 -528
- package/src/network/router.ts +0 -420
- package/src/network/url-match.test.ts +0 -69
- package/src/network/url-match.ts +0 -41
- package/src/runner/page-logs-report.test.ts +0 -144
- package/src/runner/page-logs-report.ts +0 -82
- package/src/runner/prepare-run.test.ts +0 -33
- package/src/runner/prepare-run.ts +0 -36
- package/src/runner/registry.test.ts +0 -59
- package/src/runner/registry.ts +0 -46
- package/src/runner/reporter.test.ts +0 -73
- package/src/runner/reporter.ts +0 -109
- package/src/runner/test-error.test.ts +0 -39
- package/src/runner/test-error.ts +0 -20
- package/src/runner/test-runner.ts +0 -184
- package/src/script/call-function.browser-test.ts +0 -112
- package/src/script/call-function.test.ts +0 -174
- package/src/script/call-function.ts +0 -113
- package/src/script/helpers.test.ts +0 -116
- package/src/script/helpers.ts +0 -68
- package/src/script/preload.browser-test.ts +0 -144
- package/src/script/preload.test.ts +0 -180
- package/src/script/preload.ts +0 -102
- package/src/script/serialize.test.ts +0 -182
- package/src/script/serialize.ts +0 -203
- package/src/testing/browser-fixture.ts +0 -18
- package/src/testing/fake-websocket.ts +0 -72
- package/src/testing/stub-connector.ts +0 -154
- package/src/tests/index.spec.ts +0 -30
- package/src/transport/bidi-connection.test.ts +0 -155
- package/src/transport/bidi-connection.ts +0 -256
- package/src/transport/bidi-error.ts +0 -23
- package/src/transport/foundation.browser-test.ts +0 -73
- package/src/transport/subscriptions.test.ts +0 -140
- package/src/transport/subscriptions.ts +0 -110
- package/src/types/bidi-commands.test-d.ts +0 -55
- package/src/wait/wait-until.test.ts +0 -117
- package/src/wait/wait-until.ts +0 -86
- package/tsconfig.json +0 -44
- /package/{src/api.ts → dist/api.d.ts} +0 -0
- /package/{src/locator/text-locator.ts → dist/locator/text-locator.js} +0 -0
- /package/{src → dist}/types/bidi-modules/browser.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/browsing-context.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/emulation.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/input.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/log.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/network.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/permissions.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/script.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/session.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/storage.d.ts +0 -0
- /package/{src → dist}/types/bidi-modules/web-extensions.d.ts +0 -0
- /package/{src → dist}/types/bidi-protocols/command.d.ts +0 -0
- /package/{src → dist}/types/bidi-protocols/error.d.ts +0 -0
- /package/{src → dist}/types/bidi-protocols/event.d.ts +0 -0
- /package/{src → dist}/types/bidi.d.ts +0 -0
- /package/{src → dist}/types/browser.d.ts +0 -0
- /package/{src → dist}/types/config.d.ts +0 -0
- /package/{src → dist}/types/env.d.ts +0 -0
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { afterEach, describe, it } from "node:test";
|
|
3
|
-
import { autoReply, FakeWebSocket, tick } from "../testing/fake-websocket.js";
|
|
4
|
-
import { BiDiConnector } from "../transport/bidi-connection.js";
|
|
5
|
-
import { ContextTree } from "./context-tree.js";
|
|
6
|
-
import { ConsoleMessage, PageError, PageLogs } from "./page-logs.js";
|
|
7
|
-
|
|
8
|
-
const TREE = [
|
|
9
|
-
{ context: "top", parent: null, children: [{ context: "frame", parent: "top", children: [] }] },
|
|
10
|
-
{ context: "other", parent: null, children: [] },
|
|
11
|
-
];
|
|
12
|
-
const stops: Array<() => void> = [];
|
|
13
|
-
afterEach(() => stops.splice(0).forEach((stop) => stop()));
|
|
14
|
-
|
|
15
|
-
async function setup(options?: { maxEntries?: number }) {
|
|
16
|
-
const ws = new FakeWebSocket();
|
|
17
|
-
const connector = new BiDiConnector(ws as unknown as WebSocket);
|
|
18
|
-
stops.push(autoReply(ws, { "browsingContext.getTree": () => ({ contexts: TREE }) }));
|
|
19
|
-
const tree = await ContextTree.create(connector);
|
|
20
|
-
const logs = await PageLogs.start(connector, tree, "top", options);
|
|
21
|
-
return { ws, logs };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const source = (context: string | undefined) => ({ realm: "r", userContext: "default", ...(context && { context }) });
|
|
25
|
-
const str = (value: string) => ({ type: "string", value });
|
|
26
|
-
const consoleEntry = (context: string | undefined, method: string, text: string, extra: object = {}) => ({
|
|
27
|
-
type: "console",
|
|
28
|
-
method,
|
|
29
|
-
level: method === "error" ? "error" : method === "warn" ? "warn" : "info",
|
|
30
|
-
text,
|
|
31
|
-
args: [str(text)],
|
|
32
|
-
timestamp: 1000,
|
|
33
|
-
source: source(context),
|
|
34
|
-
...extra,
|
|
35
|
-
});
|
|
36
|
-
const errorEntry = (context: string | undefined, text: string, extra: object = {}) => ({
|
|
37
|
-
type: "javascript",
|
|
38
|
-
level: "error",
|
|
39
|
-
text,
|
|
40
|
-
timestamp: 2000,
|
|
41
|
-
source: source(context),
|
|
42
|
-
...extra,
|
|
43
|
-
});
|
|
44
|
-
const emit = (ws: FakeWebSocket, entry: object) => ws.emitEvent("log.entryAdded", entry);
|
|
45
|
-
|
|
46
|
-
describe("PageLogs console messages", () => {
|
|
47
|
-
it("turns a console entry into a ConsoleMessage with deserialized arguments", async () => {
|
|
48
|
-
const { ws, logs } = await setup();
|
|
49
|
-
const seen: ConsoleMessage[] = [];
|
|
50
|
-
logs.on("console", (message) => seen.push(message));
|
|
51
|
-
emit(ws, {
|
|
52
|
-
...consoleEntry("top", "log", "a 1 {…}", {
|
|
53
|
-
args: [str("a"), { type: "number", value: 1 }, { type: "object", value: [["k", str("v")]] }],
|
|
54
|
-
stackTrace: { callFrames: [{ functionName: "run", url: "https://x.test/app.js", lineNumber: 4, columnNumber: 2 }] },
|
|
55
|
-
}),
|
|
56
|
-
});
|
|
57
|
-
assert.equal(seen.length, 1);
|
|
58
|
-
const [message] = seen as [ConsoleMessage];
|
|
59
|
-
assert.ok(message instanceof ConsoleMessage);
|
|
60
|
-
assert.equal(message.type(), "log");
|
|
61
|
-
assert.equal(message.text(), "a 1 {…}");
|
|
62
|
-
assert.equal(message.level, "info");
|
|
63
|
-
assert.deepEqual(message.args, ["a", 1, { k: "v" }]);
|
|
64
|
-
assert.equal(message.timestamp, 1000);
|
|
65
|
-
assert.deepEqual(message.location(), { url: "https://x.test/app.js", lineNumber: 4, columnNumber: 2 });
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("has no location without a stack, and an empty text for null", async () => {
|
|
69
|
-
const { ws, logs } = await setup();
|
|
70
|
-
let message!: ConsoleMessage;
|
|
71
|
-
logs.on("console", (m) => (message = m));
|
|
72
|
-
emit(ws, consoleEntry("top", "warn", "x", { text: null }));
|
|
73
|
-
assert.equal(message.location(), undefined);
|
|
74
|
-
assert.equal(message.text(), "");
|
|
75
|
-
assert.equal(message.type(), "warn");
|
|
76
|
-
assert.equal(message.level, "warn");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("console.error is a log entry, not a page error", async () => {
|
|
80
|
-
const { ws, logs } = await setup();
|
|
81
|
-
emit(ws, consoleEntry("top", "error", "handled problem"));
|
|
82
|
-
assert.equal(logs.entries.length, 1);
|
|
83
|
-
assert.equal(logs.entries[0]!.level, "error");
|
|
84
|
-
assert.equal(logs.errors.length, 0);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe("PageLogs page errors", () => {
|
|
89
|
-
it("turns an uncaught exception into a PageError with a readable stack", async () => {
|
|
90
|
-
const { ws, logs } = await setup();
|
|
91
|
-
const seen: PageError[] = [];
|
|
92
|
-
logs.on("pageerror", (error) => seen.push(error));
|
|
93
|
-
emit(ws, errorEntry("top", "TypeError: x is null", {
|
|
94
|
-
stackTrace: {
|
|
95
|
-
callFrames: [
|
|
96
|
-
{ functionName: "click", url: "https://x.test/app.js", lineNumber: 10, columnNumber: 5 },
|
|
97
|
-
{ functionName: "", url: "https://x.test/app.js", lineNumber: 20, columnNumber: 1 },
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
}));
|
|
101
|
-
assert.equal(seen.length, 1);
|
|
102
|
-
const [error] = seen as [PageError];
|
|
103
|
-
assert.ok(error instanceof Error);
|
|
104
|
-
assert.ok(error instanceof PageError);
|
|
105
|
-
assert.equal(error.message, "TypeError: x is null");
|
|
106
|
-
assert.equal(error.name, "PageError");
|
|
107
|
-
assert.equal(error.timestamp, 2000);
|
|
108
|
-
assert.equal(
|
|
109
|
-
error.stack,
|
|
110
|
-
"TypeError: x is null\n at click (https://x.test/app.js:10:5)\n at https://x.test/app.js:20:1",
|
|
111
|
-
);
|
|
112
|
-
assert.deepEqual(logs.errors, [error]);
|
|
113
|
-
assert.deepEqual(logs.entries.map((entry) => entry.type), ["javascript"]);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("a PageError without a stack is just its text", async () => {
|
|
117
|
-
const { ws, logs } = await setup();
|
|
118
|
-
emit(ws, errorEntry("top", "Error: bare"));
|
|
119
|
-
assert.equal(logs.errors[0]!.stack, "Error: bare");
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe("PageLogs scope", () => {
|
|
124
|
-
it("keeps entries of iframes, drops other pages and entries without a context", async () => {
|
|
125
|
-
const { ws, logs } = await setup();
|
|
126
|
-
emit(ws, consoleEntry("frame", "log", "from frame"));
|
|
127
|
-
emit(ws, consoleEntry("other", "log", "from another page"));
|
|
128
|
-
emit(ws, consoleEntry(undefined, "log", "from a worker"));
|
|
129
|
-
assert.deepEqual(logs.entries.map((entry) => entry.text), ["from frame"]);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("records entries of other types without emitting an event", async () => {
|
|
133
|
-
const { ws, logs } = await setup();
|
|
134
|
-
let events = 0;
|
|
135
|
-
logs.on("console", () => events++);
|
|
136
|
-
logs.on("pageerror", () => events++);
|
|
137
|
-
emit(ws, { type: "deprecation", level: "warn", text: "old api", timestamp: 3, source: source("top") });
|
|
138
|
-
assert.equal(events, 0);
|
|
139
|
-
assert.deepEqual(logs.entries.map((entry) => [entry.type, entry.text]), [["deprecation", "old api"]]);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
describe("PageLogs buffer", () => {
|
|
144
|
-
it("keeps the newest entries and counts what it dropped", async () => {
|
|
145
|
-
const { ws, logs } = await setup({ maxEntries: 3 });
|
|
146
|
-
for (let i = 1; i <= 5; i++) emit(ws, consoleEntry("top", "log", `m${i}`));
|
|
147
|
-
assert.deepEqual(logs.entries.map((entry) => entry.text), ["m3", "m4", "m5"]);
|
|
148
|
-
assert.equal(logs.dropped, 2);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("records level, type, method, text and timestamp", async () => {
|
|
152
|
-
const { ws, logs } = await setup();
|
|
153
|
-
emit(ws, consoleEntry("top", "warn", "careful"));
|
|
154
|
-
assert.deepEqual(logs.entries[0], { level: "warn", type: "console", method: "warn", text: "careful", timestamp: 1000 });
|
|
155
|
-
emit(ws, errorEntry("top", "Error: x"));
|
|
156
|
-
assert.deepEqual(logs.entries[1], { level: "error", type: "javascript", text: "Error: x", timestamp: 2000 });
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("clear empties entries, errors and the dropped count", async () => {
|
|
160
|
-
const { ws, logs } = await setup({ maxEntries: 1 });
|
|
161
|
-
emit(ws, consoleEntry("top", "log", "a"));
|
|
162
|
-
emit(ws, consoleEntry("top", "log", "b"));
|
|
163
|
-
emit(ws, errorEntry("top", "Error: x"));
|
|
164
|
-
logs.clear();
|
|
165
|
-
assert.equal(logs.entries.length, 0);
|
|
166
|
-
assert.equal(logs.errors.length, 0);
|
|
167
|
-
assert.equal(logs.dropped, 0);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("entries is a snapshot: later entries do not change what was read", async () => {
|
|
171
|
-
const { ws, logs } = await setup();
|
|
172
|
-
emit(ws, consoleEntry("top", "log", "a"));
|
|
173
|
-
const before = logs.entries;
|
|
174
|
-
emit(ws, consoleEntry("top", "log", "b"));
|
|
175
|
-
assert.equal(before.length, 1);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
describe("PageLogs lifecycle", () => {
|
|
180
|
-
it("a throwing listener does not stop the others or the recording", async () => {
|
|
181
|
-
const { ws, logs } = await setup();
|
|
182
|
-
let second = 0;
|
|
183
|
-
logs.on("console", () => {
|
|
184
|
-
throw new Error("listener failed");
|
|
185
|
-
});
|
|
186
|
-
logs.on("console", () => second++);
|
|
187
|
-
emit(ws, consoleEntry("top", "log", "a"));
|
|
188
|
-
assert.equal(second, 1);
|
|
189
|
-
assert.equal(logs.entries.length, 1);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("off removes a listener", async () => {
|
|
193
|
-
const { ws, logs } = await setup();
|
|
194
|
-
let count = 0;
|
|
195
|
-
const listener = () => count++;
|
|
196
|
-
logs.on("console", listener);
|
|
197
|
-
logs.off("console", listener);
|
|
198
|
-
emit(ws, consoleEntry("top", "log", "a"));
|
|
199
|
-
assert.equal(count, 0);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("subscribes to log.entryAdded and releases it on dispose", async () => {
|
|
203
|
-
const { ws, logs } = await setup();
|
|
204
|
-
assert.ok(ws.sent.some((m) => m.method === "session.subscribe" && (m.params as any).events[0] === "log.entryAdded"));
|
|
205
|
-
await logs.dispose();
|
|
206
|
-
await tick(10);
|
|
207
|
-
emit(ws, consoleEntry("top", "log", "late"));
|
|
208
|
-
assert.equal(logs.entries.length, 0);
|
|
209
|
-
assert.ok(ws.sent.some((m) => m.method === "session.unsubscribe"), "the log subscription was released");
|
|
210
|
-
await assert.doesNotReject(logs.dispose());
|
|
211
|
-
});
|
|
212
|
-
});
|
package/src/browser/page-logs.ts
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import EventEmitter from "node:events";
|
|
2
|
-
import logger from "../logger/index.js";
|
|
3
|
-
import { fromRemoteValue } from "../script/serialize.js";
|
|
4
|
-
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
5
|
-
import type { Subscription } from "../transport/subscriptions.js";
|
|
6
|
-
import type { ConsoleLogEntry, Entry, Level } from "../types/bidi-modules/log.js";
|
|
7
|
-
import type { StackFrame, StackTrace } from "../types/bidi-modules/script.js";
|
|
8
|
-
import type { ContextTree } from "./context-tree.js";
|
|
9
|
-
|
|
10
|
-
/** How many entries a page keeps before the oldest are dropped */
|
|
11
|
-
const DEFAULT_MAX_ENTRIES = 1000;
|
|
12
|
-
|
|
13
|
-
/** One line of the browser's log, as kept for reports */
|
|
14
|
-
export interface LogRecord {
|
|
15
|
-
level: Level;
|
|
16
|
-
/** "console", "javascript" (an uncaught exception), or another type the browser reports */
|
|
17
|
-
type: string;
|
|
18
|
-
/** The console method, for `type: "console"` */
|
|
19
|
-
method?: string;
|
|
20
|
-
text: string;
|
|
21
|
-
timestamp: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface SourceLocation {
|
|
25
|
-
url: string;
|
|
26
|
-
lineNumber: number;
|
|
27
|
-
columnNumber: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** A call to `console.log`, `console.warn`, … in the page */
|
|
31
|
-
export class ConsoleMessage {
|
|
32
|
-
constructor(
|
|
33
|
-
/** "debug" | "info" | "warn" | "error" */
|
|
34
|
-
public readonly level: Level,
|
|
35
|
-
private readonly method: string,
|
|
36
|
-
private readonly message: string,
|
|
37
|
-
/** The arguments as plain values; DOM nodes and functions stay opaque (ElementHandle, RemoteObject) */
|
|
38
|
-
public readonly args: unknown[],
|
|
39
|
-
public readonly timestamp: number,
|
|
40
|
-
public readonly stackTrace?: StackTrace,
|
|
41
|
-
) {}
|
|
42
|
-
|
|
43
|
-
/** The console method: "log", "warn", "error", "info", "debug", … */
|
|
44
|
-
public type(): string {
|
|
45
|
-
return this.method;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** What the browser prints for the call */
|
|
49
|
-
public text(): string {
|
|
50
|
-
return this.message;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Where the call was made, when the browser says */
|
|
54
|
-
public location(): SourceLocation | undefined {
|
|
55
|
-
const frame = this.stackTrace?.callFrames[0];
|
|
56
|
-
return frame && { url: frame.url, lineNumber: frame.lineNumber, columnNumber: frame.columnNumber };
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** An exception nobody caught in the page */
|
|
61
|
-
export class PageError extends Error {
|
|
62
|
-
public readonly timestamp: number;
|
|
63
|
-
|
|
64
|
-
constructor(text: string, timestamp: number, stackTrace?: StackTrace) {
|
|
65
|
-
super(text);
|
|
66
|
-
this.name = "PageError";
|
|
67
|
-
this.timestamp = timestamp;
|
|
68
|
-
this.stack = [text, ...(stackTrace?.callFrames ?? []).map(formatFrame)].join("\n");
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function formatFrame({ functionName, url, lineNumber, columnNumber }: StackFrame): string {
|
|
73
|
-
const where = `${url}:${lineNumber}:${columnNumber}`;
|
|
74
|
-
return functionName ? ` at ${functionName} (${where})` : ` at ${where}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
interface EventMap {
|
|
78
|
-
console: [ConsoleMessage];
|
|
79
|
-
pageerror: [PageError];
|
|
80
|
-
}
|
|
81
|
-
export type LogEventName = keyof EventMap;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Collects the browser's log (`console.*` calls and uncaught exceptions) of one page, frames included,
|
|
85
|
-
* in a ring buffer, and emits `console` and `pageerror` events.
|
|
86
|
-
*/
|
|
87
|
-
export class PageLogs {
|
|
88
|
-
private events = new EventEmitter();
|
|
89
|
-
private buffer: LogRecord[] = [];
|
|
90
|
-
private uncaught: PageError[] = [];
|
|
91
|
-
private droppedCount = 0;
|
|
92
|
-
private disposed = false;
|
|
93
|
-
private subscription: Subscription | undefined;
|
|
94
|
-
|
|
95
|
-
private constructor(
|
|
96
|
-
private connector: BiDiConnector,
|
|
97
|
-
private tree: ContextTree,
|
|
98
|
-
private contextId: string,
|
|
99
|
-
private maxEntries: number,
|
|
100
|
-
) {}
|
|
101
|
-
|
|
102
|
-
public static async start(
|
|
103
|
-
connector: BiDiConnector,
|
|
104
|
-
tree: ContextTree,
|
|
105
|
-
contextId: string,
|
|
106
|
-
options: { maxEntries?: number } = {},
|
|
107
|
-
): Promise<PageLogs> {
|
|
108
|
-
const logs = new PageLogs(connector, tree, contextId, options.maxEntries ?? DEFAULT_MAX_ENTRIES);
|
|
109
|
-
connector.onEvent("log.entryAdded", logs.onEntry);
|
|
110
|
-
try {
|
|
111
|
-
logs.subscription = await connector.subscribe(["log.entryAdded"]);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
connector.offEvent("log.entryAdded", logs.onEntry);
|
|
114
|
-
throw err;
|
|
115
|
-
}
|
|
116
|
-
return logs;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** The newest entries, oldest first (a copy) */
|
|
120
|
-
public get entries(): readonly LogRecord[] {
|
|
121
|
-
return [...this.buffer];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Uncaught exceptions seen since the start or the last `clear()` */
|
|
125
|
-
public get errors(): readonly PageError[] {
|
|
126
|
-
return [...this.uncaught];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** How many entries fell out of the buffer */
|
|
130
|
-
public get dropped(): number {
|
|
131
|
-
return this.droppedCount;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public on<E extends LogEventName>(event: E, listener: (...args: EventMap[E]) => void): void {
|
|
135
|
-
this.events.on(event, listener as (...args: unknown[]) => void);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
public off<E extends LogEventName>(event: E, listener: (...args: EventMap[E]) => void): void {
|
|
139
|
-
this.events.off(event, listener as (...args: unknown[]) => void);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
public clear(): void {
|
|
143
|
-
this.buffer = [];
|
|
144
|
-
this.uncaught = [];
|
|
145
|
-
this.droppedCount = 0;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Idempotent */
|
|
149
|
-
public async dispose(): Promise<void> {
|
|
150
|
-
if (this.disposed) return;
|
|
151
|
-
this.disposed = true;
|
|
152
|
-
this.connector.offEvent("log.entryAdded", this.onEntry);
|
|
153
|
-
this.buffer = [];
|
|
154
|
-
this.uncaught = [];
|
|
155
|
-
await this.subscription?.unsubscribe();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private onEntry = (entry: Entry) => {
|
|
159
|
-
const context = entry.source.context;
|
|
160
|
-
// Worker realms have no context; entries of other pages are not ours
|
|
161
|
-
if (!context || !this.tree.isWithin(context, this.contextId)) return;
|
|
162
|
-
|
|
163
|
-
const text = entry.text ?? "";
|
|
164
|
-
const record: LogRecord = {
|
|
165
|
-
level: entry.level,
|
|
166
|
-
type: entry.type,
|
|
167
|
-
...(entry.type === "console" && { method: (entry as ConsoleLogEntry).method }),
|
|
168
|
-
text,
|
|
169
|
-
timestamp: entry.timestamp,
|
|
170
|
-
};
|
|
171
|
-
this.buffer.push(record);
|
|
172
|
-
if (this.buffer.length > this.maxEntries) {
|
|
173
|
-
this.buffer.shift();
|
|
174
|
-
this.droppedCount++;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (entry.type === "console") {
|
|
178
|
-
const consoleEntry = entry as ConsoleLogEntry;
|
|
179
|
-
this.emit(
|
|
180
|
-
"console",
|
|
181
|
-
new ConsoleMessage(
|
|
182
|
-
entry.level,
|
|
183
|
-
consoleEntry.method,
|
|
184
|
-
text,
|
|
185
|
-
(consoleEntry.args ?? []).map((arg) => fromRemoteValue(arg)),
|
|
186
|
-
entry.timestamp,
|
|
187
|
-
entry.stackTrace,
|
|
188
|
-
),
|
|
189
|
-
);
|
|
190
|
-
} else if (entry.type === "javascript") {
|
|
191
|
-
const error = new PageError(text, entry.timestamp, entry.stackTrace);
|
|
192
|
-
this.uncaught.push(error);
|
|
193
|
-
if (this.uncaught.length > this.maxEntries) this.uncaught.shift();
|
|
194
|
-
this.emit("pageerror", error);
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
/** A listener that throws must not stop the recording or the other listeners */
|
|
199
|
-
private emit<E extends LogEventName>(event: E, ...args: EventMap[E]) {
|
|
200
|
-
for (const listener of this.events.rawListeners(event)) {
|
|
201
|
-
try {
|
|
202
|
-
listener(...args);
|
|
203
|
-
} catch (err) {
|
|
204
|
-
logger.error("A %s listener threw", event, { err });
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
package/src/browser/page-wait.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { WaitTimeoutError } from "../wait/wait-until.js";
|
|
2
|
-
|
|
3
|
-
/** No download started in time */
|
|
4
|
-
export class DownloadWaitTimeoutError extends WaitTimeoutError<undefined> {
|
|
5
|
-
constructor(timeout: number) {
|
|
6
|
-
super(timeout, undefined);
|
|
7
|
-
this.name = "DownloadWaitTimeoutError";
|
|
8
|
-
this.message = `waitForDownload(): no download started within ${timeout}ms`;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** No file picker opened in time */
|
|
13
|
-
export class FileChooserTimeoutError extends WaitTimeoutError<undefined> {
|
|
14
|
-
constructor(timeout: number) {
|
|
15
|
-
super(timeout, undefined);
|
|
16
|
-
this.name = "FileChooserTimeoutError";
|
|
17
|
-
this.message = `waitForFileChooser(): no file chooser opened within ${timeout}ms`;
|
|
18
|
-
}
|
|
19
|
-
}
|