@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
package/src/logger/index.test.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { test } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { Writable } from "node:stream";
|
|
4
|
-
import { transports } from "winston";
|
|
5
|
-
import { MASK, registerSecret, resetSecrets } from "../config/mask.js";
|
|
6
|
-
import logger from "./index.js";
|
|
7
|
-
|
|
8
|
-
test("the real logger masks registered secrets in messages and metadata", () => {
|
|
9
|
-
let out = "";
|
|
10
|
-
const transport = new transports.Stream({
|
|
11
|
-
stream: new Writable({
|
|
12
|
-
write(chunk, _enc, done) {
|
|
13
|
-
out += String(chunk);
|
|
14
|
-
done();
|
|
15
|
-
},
|
|
16
|
-
}),
|
|
17
|
-
});
|
|
18
|
-
logger.add(transport);
|
|
19
|
-
registerSecret("TEST_CARD", "4242424242424242");
|
|
20
|
-
try {
|
|
21
|
-
logger.info("paying with 4242424242424242", { card: "4242424242424242" });
|
|
22
|
-
} finally {
|
|
23
|
-
logger.remove(transport);
|
|
24
|
-
resetSecrets();
|
|
25
|
-
}
|
|
26
|
-
assert.ok(out.includes(MASK), out);
|
|
27
|
-
assert.ok(!out.includes("4242424242424242"), out);
|
|
28
|
-
});
|
package/src/logger/index.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { createLogger, format, transports } from "winston";
|
|
2
|
-
import { maskFormat } from "../config/mask.js";
|
|
3
|
-
|
|
4
|
-
const logger = createLogger({
|
|
5
|
-
level: "debug",
|
|
6
|
-
format: format.combine(
|
|
7
|
-
format.timestamp({
|
|
8
|
-
format: "DD-MM-YYYY HH:mm:ss",
|
|
9
|
-
}),
|
|
10
|
-
format.errors({ stack: true }),
|
|
11
|
-
format.splat(),
|
|
12
|
-
format.metadata({ key: "meta" }),
|
|
13
|
-
maskFormat(),
|
|
14
|
-
format.json({ space: 2 }),
|
|
15
|
-
),
|
|
16
|
-
defaultMeta: { service: "samurai-framework" },
|
|
17
|
-
transports: [
|
|
18
|
-
//
|
|
19
|
-
// - Write to all logs with level `info` and below to `quick-start-combined.log`.
|
|
20
|
-
// - Write all logs error (and below) to `quick-start-error.log`.
|
|
21
|
-
//
|
|
22
|
-
new transports.Console({
|
|
23
|
-
level: "debug",
|
|
24
|
-
format: format.combine(
|
|
25
|
-
format.timestamp(),
|
|
26
|
-
format.errors({ stack: true }),
|
|
27
|
-
format.printf(({ level, message, meta }) => {
|
|
28
|
-
return `${level.toUpperCase()}: ${message}${meta ? `\n${JSON.stringify(meta)}` : null}`;
|
|
29
|
-
}),
|
|
30
|
-
),
|
|
31
|
-
}),
|
|
32
|
-
new transports.File({
|
|
33
|
-
filename: `logs/error.log`,
|
|
34
|
-
level: "error",
|
|
35
|
-
}),
|
|
36
|
-
new transports.File({ filename: `logs/combined.log` }),
|
|
37
|
-
],
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
export default logger;
|
|
@@ -1,111 +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 { DataCollector, ResponseBodyUnavailableError } from "./data-collector.js";
|
|
6
|
-
|
|
7
|
-
const stops: Array<() => void> = [];
|
|
8
|
-
afterEach(() => stops.splice(0).forEach((stop) => stop()));
|
|
9
|
-
|
|
10
|
-
const failing = (code: string, message = "nope") => Object.assign(new Error(message), { code });
|
|
11
|
-
|
|
12
|
-
function setup(handlers: Record<string, (params: any) => object | Error> = {}) {
|
|
13
|
-
const ws = new FakeWebSocket();
|
|
14
|
-
const connector = new BiDiConnector(ws as unknown as WebSocket);
|
|
15
|
-
stops.push(autoReply(ws, { "network.addDataCollector": () => ({ collector: "c1" }), ...handlers }));
|
|
16
|
-
const commands = (method: string) => ws.sent.filter((m) => m.method === method).map((m) => m.params as any);
|
|
17
|
-
return { ws, connector, commands };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("DataCollector.start", () => {
|
|
21
|
-
it("collects response bodies of this page, up to maxBodySize", async () => {
|
|
22
|
-
const { connector, commands } = await setup();
|
|
23
|
-
await DataCollector.start(connector, "top", { maxBodySize: 5000 });
|
|
24
|
-
assert.deepEqual(commands("network.addDataCollector"), [
|
|
25
|
-
{ dataTypes: ["response"], maxEncodedDataSize: 5000, contexts: ["top"] },
|
|
26
|
-
]);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("defaults to 10 MiB", async () => {
|
|
30
|
-
const { connector, commands } = await setup();
|
|
31
|
-
await DataCollector.start(connector, "top");
|
|
32
|
-
assert.equal(commands("network.addDataCollector")[0].maxEncodedDataSize, 10 * 1024 * 1024);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe("DataCollector.read", () => {
|
|
37
|
-
it("returns a text body as its UTF-8 bytes", async () => {
|
|
38
|
-
const { connector, commands } = await setup({
|
|
39
|
-
"network.getData": () => ({ bytes: { type: "string", value: "héllo" } }),
|
|
40
|
-
});
|
|
41
|
-
const collector = await DataCollector.start(connector, "top");
|
|
42
|
-
const body = await collector.read("r1", "https://e.test/x");
|
|
43
|
-
assert.deepEqual([...body], [...Buffer.from("héllo", "utf8")]);
|
|
44
|
-
assert.deepEqual(commands("network.getData"), [{ dataType: "response", request: "r1", collector: "c1" }]);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("decodes a base64 body byte for byte", async () => {
|
|
48
|
-
const bytes = Buffer.from([0, 1, 2, 250, 255]);
|
|
49
|
-
const { connector } = await setup({
|
|
50
|
-
"network.getData": () => ({ bytes: { type: "base64", value: bytes.toString("base64") } }),
|
|
51
|
-
});
|
|
52
|
-
const collector = await DataCollector.start(connector, "top");
|
|
53
|
-
assert.deepEqual([...(await collector.read("r1", "u"))], [...bytes]);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("explains missing data: collected too late, or larger than the limit", async () => {
|
|
57
|
-
const { connector } = await setup({ "network.getData": () => failing("no such network data") });
|
|
58
|
-
const collector = await DataCollector.start(connector, "top");
|
|
59
|
-
await assert.rejects(collector.read("r1", "https://e.test/x"), (err: unknown) => {
|
|
60
|
-
assert.ok(err instanceof ResponseBodyUnavailableError);
|
|
61
|
-
assert.equal(err.url, "https://e.test/x");
|
|
62
|
-
assert.match(err.reason, /no body was collected/);
|
|
63
|
-
assert.match(err.reason, /started after the request/);
|
|
64
|
-
assert.match(err.reason, /larger than maxBodySize/);
|
|
65
|
-
assert.match(err.message, /^Response body of https:\/\/e\.test\/x is unavailable: /);
|
|
66
|
-
return true;
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("says when the data is not there yet", async () => {
|
|
71
|
-
const { connector } = await setup({ "network.getData": () => failing("unavailable network data") });
|
|
72
|
-
const collector = await DataCollector.start(connector, "top");
|
|
73
|
-
await assert.rejects(collector.read("r1", "u"), /not available yet/);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("other browser errors are rethrown as they are", async () => {
|
|
77
|
-
const { connector } = await setup({ "network.getData": () => failing("unknown error") });
|
|
78
|
-
const collector = await DataCollector.start(connector, "top");
|
|
79
|
-
await assert.rejects(collector.read("r1", "u"), { name: "BiDiError", code: "unknown error" });
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe("DataCollector.release and dispose", () => {
|
|
84
|
-
it("release frees the browser's copy", async () => {
|
|
85
|
-
const { connector, commands } = await setup();
|
|
86
|
-
const collector = await DataCollector.start(connector, "top");
|
|
87
|
-
await collector.release("r1");
|
|
88
|
-
assert.deepEqual(commands("network.disownData"), [{ dataType: "response", collector: "c1", request: "r1" }]);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("release never throws: the copy may be gone already", async () => {
|
|
92
|
-
const { connector } = await setup({ "network.disownData": () => failing("no such network data") });
|
|
93
|
-
const collector = await DataCollector.start(connector, "top");
|
|
94
|
-
await assert.doesNotReject(collector.release("r1"));
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("dispose removes the collector once", async () => {
|
|
98
|
-
const { connector, commands } = await setup();
|
|
99
|
-
const collector = await DataCollector.start(connector, "top");
|
|
100
|
-
await collector.dispose();
|
|
101
|
-
await collector.dispose();
|
|
102
|
-
await tick(5);
|
|
103
|
-
assert.deepEqual(commands("network.removeDataCollector"), [{ collector: "c1" }]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("dispose ignores a collector the browser no longer knows", async () => {
|
|
107
|
-
const { connector } = await setup({ "network.removeDataCollector": () => failing("no such network collector") });
|
|
108
|
-
const collector = await DataCollector.start(connector, "top");
|
|
109
|
-
await assert.doesNotReject(collector.dispose());
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
-
import { BiDiError } from "../transport/bidi-error.js";
|
|
3
|
-
import type { Collector } from "../types/bidi-modules/network.js";
|
|
4
|
-
|
|
5
|
-
/** How large a response body may be to be kept: 10 MiB */
|
|
6
|
-
const DEFAULT_MAX_BODY_SIZE = 10 * 1024 * 1024;
|
|
7
|
-
|
|
8
|
-
/** The body of a response cannot be read */
|
|
9
|
-
export class ResponseBodyUnavailableError extends Error {
|
|
10
|
-
public readonly url: string;
|
|
11
|
-
public readonly reason: string;
|
|
12
|
-
|
|
13
|
-
constructor(url: string, reason: string) {
|
|
14
|
-
super(`Response body of ${url} is unavailable: ${reason}`);
|
|
15
|
-
this.name = "ResponseBodyUnavailableError";
|
|
16
|
-
this.url = url;
|
|
17
|
-
this.reason = reason;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* A browser only keeps response bodies for a request when a data collector was registered before the request
|
|
23
|
-
* started. This registers one for a page and reads bodies through it.
|
|
24
|
-
*/
|
|
25
|
-
export class DataCollector {
|
|
26
|
-
private disposed = false;
|
|
27
|
-
|
|
28
|
-
private constructor(
|
|
29
|
-
private connector: BiDiConnector,
|
|
30
|
-
private id: Collector,
|
|
31
|
-
) {}
|
|
32
|
-
|
|
33
|
-
public static async start(
|
|
34
|
-
connector: BiDiConnector,
|
|
35
|
-
contextId: string,
|
|
36
|
-
options: { maxBodySize?: number } = {},
|
|
37
|
-
): Promise<DataCollector> {
|
|
38
|
-
const { collector } = await connector.send("network.addDataCollector", {
|
|
39
|
-
dataTypes: ["response"],
|
|
40
|
-
maxEncodedDataSize: options.maxBodySize ?? DEFAULT_MAX_BODY_SIZE,
|
|
41
|
-
contexts: [contextId],
|
|
42
|
-
});
|
|
43
|
-
return new DataCollector(connector, collector);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** The body of the response to request `requestId`; `url` only names it in errors */
|
|
47
|
-
public async read(requestId: string, url: string): Promise<Buffer> {
|
|
48
|
-
try {
|
|
49
|
-
const { bytes } = await this.connector.send("network.getData", {
|
|
50
|
-
dataType: "response",
|
|
51
|
-
request: requestId,
|
|
52
|
-
collector: this.id,
|
|
53
|
-
});
|
|
54
|
-
return bytes.type === "base64" ? Buffer.from(bytes.value, "base64") : Buffer.from(bytes.value, "utf8");
|
|
55
|
-
} catch (err) {
|
|
56
|
-
if (err instanceof BiDiError && err.code === "no such network data") {
|
|
57
|
-
throw new ResponseBodyUnavailableError(
|
|
58
|
-
url,
|
|
59
|
-
"no body was collected: the collector started after the request, or the response is larger than maxBodySize",
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
if (err instanceof BiDiError && err.code === "unavailable network data") {
|
|
63
|
-
throw new ResponseBodyUnavailableError(url, "the body is not available yet");
|
|
64
|
-
}
|
|
65
|
-
throw err;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Lets the browser drop its copy of a body that has been read. Never throws */
|
|
70
|
-
public async release(requestId: string): Promise<void> {
|
|
71
|
-
try {
|
|
72
|
-
await this.connector.send("network.disownData", {
|
|
73
|
-
dataType: "response",
|
|
74
|
-
collector: this.id,
|
|
75
|
-
request: requestId,
|
|
76
|
-
});
|
|
77
|
-
} catch {
|
|
78
|
-
// Already gone: nothing left to free
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Idempotent */
|
|
83
|
-
public async dispose(): Promise<void> {
|
|
84
|
-
if (this.disposed) return;
|
|
85
|
-
this.disposed = true;
|
|
86
|
-
try {
|
|
87
|
-
await this.connector.send("network.removeDataCollector", { collector: this.id });
|
|
88
|
-
} catch (err) {
|
|
89
|
-
if (!(err instanceof BiDiError && err.code === "no such network collector")) throw err;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { createServer, type Server } from "node:http";
|
|
3
|
-
import type { AddressInfo, Socket } from "node:net";
|
|
4
|
-
import { after, before, beforeEach, test } from "node:test";
|
|
5
|
-
import { Browser } from "../browser/browser.js";
|
|
6
|
-
import Page from "../browser/page.js";
|
|
7
|
-
import { callFunction } from "../script/call-function.js";
|
|
8
|
-
import { setContent } from "../testing/browser-fixture.js";
|
|
9
|
-
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
10
|
-
import { ResponseBodyUnavailableError } from "./data-collector.js";
|
|
11
|
-
import { ResponseTimeoutError } from "./network-wait.js";
|
|
12
|
-
|
|
13
|
-
let browser: Browser;
|
|
14
|
-
let page: Page;
|
|
15
|
-
let server: Server;
|
|
16
|
-
let base: string;
|
|
17
|
-
const sockets = new Set<Socket>();
|
|
18
|
-
const BINARY = Buffer.from([0, 1, 2, 3, 128, 200, 254, 255]);
|
|
19
|
-
let cachedHits = 0;
|
|
20
|
-
|
|
21
|
-
before(async () => {
|
|
22
|
-
server = createServer((req, res) => {
|
|
23
|
-
const path = (req.url ?? "").split("?")[0];
|
|
24
|
-
switch (path) {
|
|
25
|
-
case "/real":
|
|
26
|
-
res.setHeader("content-type", "text/html; charset=utf-8").end("<!doctype html><body id='b'>héllo document</body>");
|
|
27
|
-
break;
|
|
28
|
-
case "/json":
|
|
29
|
-
res.setHeader("content-type", "application/json").end(JSON.stringify({ users: [{ id: 1, name: "Zoë" }] }));
|
|
30
|
-
break;
|
|
31
|
-
case "/text":
|
|
32
|
-
res.setHeader("content-type", "text/plain; charset=utf-8").end("plain ✓ text");
|
|
33
|
-
break;
|
|
34
|
-
case "/binary":
|
|
35
|
-
res.setHeader("content-type", "application/octet-stream").end(BINARY);
|
|
36
|
-
break;
|
|
37
|
-
case "/big":
|
|
38
|
-
res.setHeader("content-type", "text/plain").end("x".repeat(5000));
|
|
39
|
-
break;
|
|
40
|
-
case "/redirect":
|
|
41
|
-
res.writeHead(302, { location: "/json" }).end();
|
|
42
|
-
break;
|
|
43
|
-
case "/cached":
|
|
44
|
-
cachedHits++;
|
|
45
|
-
res.setHeader("content-type", "text/plain").setHeader("cache-control", "max-age=600").end(`hit ${cachedHits}`);
|
|
46
|
-
break;
|
|
47
|
-
case "/empty":
|
|
48
|
-
res.writeHead(204).end();
|
|
49
|
-
break;
|
|
50
|
-
default:
|
|
51
|
-
res.setHeader("content-type", "text/plain").end("ok");
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
server.on("connection", (socket) => {
|
|
55
|
-
sockets.add(socket);
|
|
56
|
-
socket.on("close", () => sockets.delete(socket));
|
|
57
|
-
});
|
|
58
|
-
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
59
|
-
base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
|
|
60
|
-
|
|
61
|
-
({ browser, page } = await Browser.launch("firefox", { port: 9243, headless: true }));
|
|
62
|
-
await page.navigateTo(`${base}/real`);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
after(async () => {
|
|
66
|
-
await page.dispose();
|
|
67
|
-
await browser?.close();
|
|
68
|
-
sockets.forEach((socket) => socket.destroy());
|
|
69
|
-
await new Promise((resolve) => server.close(resolve));
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
beforeEach(async () => {
|
|
73
|
-
await page.setCacheDisabled(false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const OPTIONS = { timeout: 30000 };
|
|
77
|
-
const connector = () => (page as unknown as { biDiConnector: BiDiConnector }).biDiConnector;
|
|
78
|
-
const run = <T>(source: string) => callFunction<T>(connector(), page.contextId, source);
|
|
79
|
-
const fetchInPage = (path: string) => run(`() => { fetch(${JSON.stringify(base + path)}).catch(() => {}); }`);
|
|
80
|
-
|
|
81
|
-
test("json() reads a JSON body", OPTIONS, async () => {
|
|
82
|
-
const response = await page.waitForResponse("**/json", { trigger: () => fetchInPage("/json") });
|
|
83
|
-
assert.equal(response.status, 200);
|
|
84
|
-
assert.equal(response.headers["content-type"], "application/json");
|
|
85
|
-
assert.deepEqual(await response.json(), { users: [{ id: 1, name: "Zoë" }] });
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("text() decodes UTF-8", OPTIONS, async () => {
|
|
89
|
-
const response = await page.waitForResponse("**/text", { trigger: () => fetchInPage("/text") });
|
|
90
|
-
assert.equal(await response.text(), "plain ✓ text");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("body() returns binary content byte for byte", OPTIONS, async () => {
|
|
94
|
-
const response = await page.waitForResponse("**/binary", { trigger: () => fetchInPage("/binary") });
|
|
95
|
-
assert.deepEqual([...(await response.body())], [...BINARY]);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("the body can be read again and again", OPTIONS, async () => {
|
|
99
|
-
const response = await page.waitForResponse("**/json", { trigger: () => fetchInPage("/json") });
|
|
100
|
-
const first = await response.text();
|
|
101
|
-
assert.equal(await response.text(), first);
|
|
102
|
-
assert.deepEqual(await response.json(), JSON.parse(first));
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("an empty response has an empty body", OPTIONS, async () => {
|
|
106
|
-
const response = await page.waitForResponse("**/empty", { trigger: () => fetchInPage("/empty") });
|
|
107
|
-
assert.equal(response.status, 204);
|
|
108
|
-
assert.equal((await response.body()).length, 0);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("the document of a navigation has a body too", OPTIONS, async () => {
|
|
112
|
-
const document = await page.waitForResponse((r) => r.request.resourceType === "document" && r.url.endsWith("/real?doc"), {
|
|
113
|
-
trigger: async () => {
|
|
114
|
-
await page.navigateTo(`${base}/real?doc`);
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
assert.match(await document.text(), /héllo document/);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("a redirect response has no body, the target has", OPTIONS, async () => {
|
|
121
|
-
const seen: string[] = [];
|
|
122
|
-
const onResponse = (response: { status: number; url: string }) => {
|
|
123
|
-
if (/\/(redirect|json)$/.test(response.url)) seen.push(`${response.status} ${new URL(response.url).pathname}`);
|
|
124
|
-
};
|
|
125
|
-
page.on("response", onResponse);
|
|
126
|
-
const first = await page.waitForResponse("**/redirect", { trigger: () => fetchInPage("/redirect") });
|
|
127
|
-
await page.waitForNetworkIdle({ idleTime: 100, timeout: 5000 });
|
|
128
|
-
page.off("response", onResponse);
|
|
129
|
-
assert.equal(first.status, 302);
|
|
130
|
-
await assert.rejects(first.body(), (err: unknown) => {
|
|
131
|
-
assert.ok(err instanceof ResponseBodyUnavailableError);
|
|
132
|
-
assert.equal(err.reason, "redirect response");
|
|
133
|
-
return true;
|
|
134
|
-
});
|
|
135
|
-
assert.deepEqual(seen, ["302 /redirect", "200 /json"]);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("waitForResponse with a click as its trigger", OPTIONS, async () => {
|
|
139
|
-
await setContent(
|
|
140
|
-
page,
|
|
141
|
-
`<button id="load">Load</button>`,
|
|
142
|
-
`document.getElementById("load").addEventListener("click", () => fetch(${JSON.stringify(base + "/json?click")}));`,
|
|
143
|
-
);
|
|
144
|
-
const response = await page.waitForResponse((r) => r.url.endsWith("/json?click"), {
|
|
145
|
-
trigger: () => page.locator("button[@id='load']").click({ timeout: 5000 }),
|
|
146
|
-
});
|
|
147
|
-
assert.equal((await response.json<{ users: unknown[] }>()).users.length, 1);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test("waitForRequest sees the request before its response", OPTIONS, async () => {
|
|
151
|
-
const request = await page.waitForRequest("**/text?req", { trigger: () => fetchInPage("/text?req") });
|
|
152
|
-
assert.equal(request.method, "GET");
|
|
153
|
-
assert.equal(request.url, `${base}/text?req`);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("a body over maxBodySize is reported as unavailable, with the reason", OPTIONS, async () => {
|
|
157
|
-
// A second view of the same page, with a small limit
|
|
158
|
-
const small = new Page(connector(), page.contextId, { network: { maxBodySize: 100 } });
|
|
159
|
-
try {
|
|
160
|
-
const response = await small.waitForResponse("**/big", { trigger: () => fetchInPage("/big") });
|
|
161
|
-
await assert.rejects(response.text(), (err: unknown) => {
|
|
162
|
-
assert.ok(err instanceof ResponseBodyUnavailableError);
|
|
163
|
-
assert.match(err.reason, /larger than maxBodySize/);
|
|
164
|
-
return true;
|
|
165
|
-
});
|
|
166
|
-
const fits = await small.waitForResponse("**/text", { trigger: () => fetchInPage("/text") });
|
|
167
|
-
assert.equal(await fits.text(), "plain ✓ text");
|
|
168
|
-
} finally {
|
|
169
|
-
await small.dispose();
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("waitForResponse times out naming what it waited for", OPTIONS, async () => {
|
|
174
|
-
await assert.rejects(page.waitForResponse("**/never", { timeout: 300 }), (err: unknown) => {
|
|
175
|
-
assert.ok(err instanceof ResponseTimeoutError);
|
|
176
|
-
assert.equal(err.message, 'waitForResponse(): no response matching "**/never" within 300ms');
|
|
177
|
-
return true;
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test("setCacheDisabled makes every request reach the server", OPTIONS, async () => {
|
|
182
|
-
const twice = async (path: string) => {
|
|
183
|
-
cachedHits = 0;
|
|
184
|
-
await run(`async () => { await (await fetch(${JSON.stringify(base + path)})).text(); await (await fetch(${JSON.stringify(base + path)})).text(); }`);
|
|
185
|
-
return cachedHits;
|
|
186
|
-
};
|
|
187
|
-
assert.equal(await twice("/cached?default"), 1, "the second fetch came from the cache");
|
|
188
|
-
await page.setCacheDisabled(true);
|
|
189
|
-
assert.equal(await twice("/cached?bypass"), 2, "with the cache off both fetches hit the server");
|
|
190
|
-
await page.setCacheDisabled(false);
|
|
191
|
-
assert.equal(await twice("/cached?again"), 1, "back on");
|
|
192
|
-
});
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { createServer, type Server } from "node:http";
|
|
3
|
-
import type { AddressInfo, Socket } from "node:net";
|
|
4
|
-
import { after, before, test } from "node:test";
|
|
5
|
-
import { Browser } from "../browser/browser.js";
|
|
6
|
-
import type Page from "../browser/page.js";
|
|
7
|
-
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
8
|
-
import { callFunction } from "../script/call-function.js";
|
|
9
|
-
import { NetworkIdleTimeoutError } from "./network-tracker.js";
|
|
10
|
-
import type { FailedRequest, NetworkRequest, NetworkResponse } from "./network-tracker.js";
|
|
11
|
-
|
|
12
|
-
let browser: Browser;
|
|
13
|
-
let page: Page;
|
|
14
|
-
let server: Server;
|
|
15
|
-
let base: string;
|
|
16
|
-
const sockets = new Set<Socket>();
|
|
17
|
-
const hits: string[] = [];
|
|
18
|
-
|
|
19
|
-
before(async () => {
|
|
20
|
-
server = createServer((req, res) => {
|
|
21
|
-
hits.push(req.url ?? "");
|
|
22
|
-
switch (req.url?.split("?")[0]) {
|
|
23
|
-
case "/":
|
|
24
|
-
res.setHeader("content-type", "text/html").end("<!doctype html><title>t</title><body>home</body>");
|
|
25
|
-
break;
|
|
26
|
-
case "/slow":
|
|
27
|
-
setTimeout(() => res.setHeader("content-type", "text/plain").end("slow"), 300);
|
|
28
|
-
break;
|
|
29
|
-
case "/redirect":
|
|
30
|
-
res.writeHead(302, { location: "/slow" }).end();
|
|
31
|
-
break;
|
|
32
|
-
case "/fail":
|
|
33
|
-
req.socket.destroy();
|
|
34
|
-
break;
|
|
35
|
-
case "/hang":
|
|
36
|
-
break; // never answers
|
|
37
|
-
case "/frame":
|
|
38
|
-
res.setHeader("content-type", "text/html").end('<body>frame<script>fetch("/slow")</script></body>');
|
|
39
|
-
break;
|
|
40
|
-
default:
|
|
41
|
-
res.setHeader("content-type", "text/plain").end("ok");
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
server.on("connection", (socket) => {
|
|
45
|
-
sockets.add(socket);
|
|
46
|
-
socket.on("close", () => sockets.delete(socket));
|
|
47
|
-
});
|
|
48
|
-
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
49
|
-
base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
|
|
50
|
-
|
|
51
|
-
({ browser, page } = await Browser.launch("firefox", { port: 9237, headless: true }));
|
|
52
|
-
await page.navigateTo(`${base}/`, "complete");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
after(async () => {
|
|
56
|
-
await page.dispose();
|
|
57
|
-
await browser?.close();
|
|
58
|
-
sockets.forEach((socket) => socket.destroy());
|
|
59
|
-
await new Promise((resolve) => server.close(resolve));
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const OPTIONS = { timeout: 30000 };
|
|
63
|
-
const connector = () => (page as unknown as { biDiConnector: BiDiConnector }).biDiConnector;
|
|
64
|
-
const run = (source: string) => callFunction(connector(), page.contextId, source);
|
|
65
|
-
const IDLE = { idleTime: 200, timeout: 10000 };
|
|
66
|
-
|
|
67
|
-
test("idle waits for the last of several timed requests, then for idleTime", OPTIONS, async () => {
|
|
68
|
-
await run(`() => {
|
|
69
|
-
fetch("/slow?a");
|
|
70
|
-
setTimeout(() => fetch("/slow?b"), 200);
|
|
71
|
-
setTimeout(() => fetch("/slow?c"), 400);
|
|
72
|
-
}`);
|
|
73
|
-
const started = Date.now();
|
|
74
|
-
await page.waitForNetworkIdle(IDLE);
|
|
75
|
-
const took = Date.now() - started;
|
|
76
|
-
// last request starts at ~400ms, takes 300ms, then 200ms of quiet
|
|
77
|
-
assert.ok(took >= 850, `resolved too early: ${took}ms`);
|
|
78
|
-
assert.ok(hits.includes("/slow?c"));
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("an already quiet page is idle after idleTime", OPTIONS, async () => {
|
|
82
|
-
await page.waitForNetworkIdle({ idleTime: 100, timeout: 5000 });
|
|
83
|
-
const started = Date.now();
|
|
84
|
-
await page.waitForNetworkIdle({ idleTime: 150, timeout: 5000 });
|
|
85
|
-
const took = Date.now() - started;
|
|
86
|
-
assert.ok(took >= 140 && took < 2000, `took ${took}ms`);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("a redirect chain is followed to its end and linked", OPTIONS, async () => {
|
|
90
|
-
const requests: NetworkRequest[] = [];
|
|
91
|
-
const responses: NetworkResponse[] = [];
|
|
92
|
-
const onRequest = (request: NetworkRequest) => requests.push(request);
|
|
93
|
-
const onResponse = (response: NetworkResponse) => responses.push(response);
|
|
94
|
-
page.on("request", onRequest);
|
|
95
|
-
page.on("response", onResponse);
|
|
96
|
-
await run(`() => { fetch("/redirect"); }`);
|
|
97
|
-
await page.waitForNetworkIdle(IDLE);
|
|
98
|
-
page.off("request", onRequest);
|
|
99
|
-
page.off("response", onResponse);
|
|
100
|
-
|
|
101
|
-
const hops = requests.filter((request) => request.url.includes("/redirect") || request.url.endsWith("/slow"));
|
|
102
|
-
assert.equal(hops.length, 2);
|
|
103
|
-
assert.equal(hops[0]!.url, `${base}/redirect`);
|
|
104
|
-
assert.equal(hops[1]!.url, `${base}/slow`);
|
|
105
|
-
assert.equal(hops[1]!.redirectedFrom, hops[0]);
|
|
106
|
-
assert.deepEqual(responses.map((response) => response.status), [302, 200]);
|
|
107
|
-
assert.equal(responses[1]!.headers["content-type"], "text/plain");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("a failed request is reported and does not keep the page busy", OPTIONS, async () => {
|
|
111
|
-
const failed: FailedRequest[] = [];
|
|
112
|
-
const onFailed = (request: FailedRequest) => failed.push(request);
|
|
113
|
-
page.on("requestfailed", onFailed);
|
|
114
|
-
await run(`() => { fetch("/fail").catch(() => {}); }`);
|
|
115
|
-
await page.waitForNetworkIdle(IDLE);
|
|
116
|
-
page.off("requestfailed", onFailed);
|
|
117
|
-
assert.equal(failed.length, 1);
|
|
118
|
-
assert.equal(failed[0]!.url, `${base}/fail`);
|
|
119
|
-
assert.ok(failed[0]!.errorText.length > 0);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("requests of an iframe count towards the page", OPTIONS, async () => {
|
|
123
|
-
await run(`() => {
|
|
124
|
-
const frame = document.createElement("iframe");
|
|
125
|
-
frame.src = "/frame";
|
|
126
|
-
document.body.appendChild(frame);
|
|
127
|
-
}`);
|
|
128
|
-
const started = Date.now();
|
|
129
|
-
await page.waitForNetworkIdle(IDLE);
|
|
130
|
-
// frame document, then the frame's own slow fetch (300ms)
|
|
131
|
-
assert.ok(Date.now() - started >= 450, `resolved before the frame's request finished: ${Date.now() - started}ms`);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("a request that never ends is named when the wait times out", OPTIONS, async () => {
|
|
135
|
-
await run(`() => { fetch("/hang").catch(() => {}); }`);
|
|
136
|
-
await assert.rejects(page.waitForNetworkIdle({ idleTime: 100, timeout: 1000 }), (err) => {
|
|
137
|
-
assert.ok(err instanceof NetworkIdleTimeoutError);
|
|
138
|
-
assert.deepEqual(err.last, [`${base}/hang`]);
|
|
139
|
-
return true;
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("navigation requests are tracked from the start of the browser", OPTIONS, async () => {
|
|
144
|
-
const requests: string[] = [];
|
|
145
|
-
const onRequest = (request: NetworkRequest) => requests.push(`${request.resourceType} ${request.url}`);
|
|
146
|
-
page.on("request", onRequest);
|
|
147
|
-
await page.navigateTo(`${base}/?nav`, "complete");
|
|
148
|
-
await page.waitForNetworkIdle(IDLE);
|
|
149
|
-
page.off("request", onRequest);
|
|
150
|
-
assert.ok(requests.includes(`document ${base}/?nav`), requests.join("\n"));
|
|
151
|
-
});
|