@itmetsam/samurai-framework 0.15.1 → 0.16.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/dist/api.d.ts +1 -1
- package/dist/browser/browser.js +2 -1
- package/dist/browser/page-network.js +3 -2
- package/dist/browser/page-settings.js +2 -5
- package/dist/browser/page.js +2 -1
- package/dist/browser/session.d.ts +1 -1
- package/dist/browser/session.js +1 -1
- package/dist/browser/viewport.d.ts +0 -5
- package/dist/browser/viewport.js +0 -2
- package/dist/capabilities.d.ts +32 -0
- package/dist/capabilities.js +30 -0
- package/dist/cli/main.js +1 -1
- package/dist/{runner/browsers.js → config/browsers-for.js} +1 -2
- package/dist/config/config.d.ts +3 -1
- package/dist/config/config.js +12 -11
- package/dist/config/defaults.d.ts +41 -0
- package/dist/config/defaults.js +38 -0
- package/dist/config/resolved-config.d.ts +106 -0
- package/dist/config/resolved-config.js +112 -0
- package/dist/config/run-settings.d.ts +2 -0
- package/dist/config/run-settings.js +2 -3
- package/dist/config/timeouts.js +1 -2
- package/dist/{runner → config}/workers-option.js +1 -1
- package/dist/flows/run/project.js +5 -4
- package/dist/network/data-collector.js +1 -2
- package/dist/network/router.js +1 -2
- package/dist/project/files-store.js +2 -1
- package/dist/project/index.d.ts +1 -0
- package/dist/project/index.js +1 -0
- package/dist/recorder/session-browser.js +1 -1
- package/dist/recorder/session.d.ts +5 -2
- package/dist/recorder/session.js +18 -3
- package/dist/runner/groups.d.ts +4 -5
- package/dist/runner/groups.js +2 -5
- package/dist/runner/page-logs-report.js +6 -3
- package/dist/runner/pooled-run.js +3 -3
- package/dist/runner/project.js +2 -2
- package/dist/runner/registry.d.ts +13 -4
- package/dist/runner/registry.js +26 -9
- package/dist/runner/retries-option.js +2 -1
- package/dist/runner/run-test.js +1 -1
- package/dist/runner/run.d.ts +14 -2
- package/dist/runner/run.js +21 -6
- package/dist/runner/select.js +4 -4
- package/dist/runner/skip.d.ts +2 -1
- package/dist/runner/skip.js +3 -2
- package/dist/runner/suite.d.ts +10 -4
- package/dist/runner/suite.js +19 -10
- package/dist/runner/test-dir.js +2 -1
- package/dist/runner/test-runner.d.ts +8 -2
- package/dist/runner/test-runner.js +14 -10
- package/dist/steps/edit.d.ts +10 -0
- package/dist/steps/edit.js +4 -0
- package/dist/steps/index.d.ts +2 -2
- package/dist/steps/index.js +1 -1
- package/dist/steps/model.d.ts +19 -0
- package/dist/steps/parse.d.ts +6 -1
- package/dist/steps/parse.js +89 -19
- package/dist/transport/bidi-connection.d.ts +0 -2
- package/dist/transport/bidi-connection.js +1 -2
- package/dist/types/test.d.ts +10 -0
- package/package.json +6 -2
- /package/dist/{runner/browsers.d.ts → config/browsers-for.d.ts} +0 -0
- /package/dist/{runner → config}/workers-option.d.ts +0 -0
package/dist/api.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { test, describe, beforeEach, afterEach, beforeAll, afterAll, } from "./r
|
|
|
2
2
|
export { expect } from "./assert/expect.js";
|
|
3
3
|
export { defineConfig } from "./config/config.js";
|
|
4
4
|
export { defineStep, type StepDefinition, type StepResult, type StepRunInput, } from "./flows/steps/index.js";
|
|
5
|
-
export type { AllHookFixtures, TestFixtures } from "./types/test.js";
|
|
5
|
+
export type { AllHookFixtures, TestDetails, TestFixtures, } from "./types/test.js";
|
|
6
6
|
export { generatedFile, type GeneratedFile, type FileInput, } from "./browser/generated-files.js";
|
package/dist/browser/browser.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { DEFAULT_DOWNLOADS_DIR } from "../config/defaults.js";
|
|
3
4
|
import { projectBrowser } from "../browsers/resolve.js";
|
|
4
5
|
import Page from "./page.js";
|
|
5
6
|
import { launchChrome } from "./chrome-launch.js";
|
|
@@ -77,7 +78,7 @@ export class Browser {
|
|
|
77
78
|
/** Sets up a browser whose BiDi session is open: downloads folder, the first page and what it tracks */
|
|
78
79
|
static async attach(browserProc, biDiConnector, browserName) {
|
|
79
80
|
biDiConnector.browserName = browserName;
|
|
80
|
-
const downloadsDir = await prepareDownloadsDir(biDiConnector, path.resolve(projectDir(), (await readDownloadsDir()) ??
|
|
81
|
+
const downloadsDir = await prepareDownloadsDir(biDiConnector, path.resolve(projectDir(), (await readDownloadsDir()) ?? DEFAULT_DOWNLOADS_DIR));
|
|
81
82
|
// Chrome replaces a file that has the name of a download; this keeps every download
|
|
82
83
|
const downloadFolders = browserName === "chrome" && downloadsDir
|
|
83
84
|
? new ChromeDownloadFolders(biDiConnector, downloadsDir)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_COLLECT_BODIES, DEFAULT_TRACK_NETWORK, } from "../config/defaults.js";
|
|
1
2
|
import { DataCollector, ResponseBodyUnavailableError, } from "../network/data-collector.js";
|
|
2
3
|
import { NetworkTracker } from "../network/network-tracker.js";
|
|
3
4
|
import { RequestTimeoutError, ResponseTimeoutError, toPredicate, } from "../network/network-wait.js";
|
|
@@ -36,14 +37,14 @@ export class PageNetwork {
|
|
|
36
37
|
tracker() {
|
|
37
38
|
return this.lazyTracker.get(async () => {
|
|
38
39
|
const settings = await this.deps.config.network();
|
|
39
|
-
if (settings.track === false)
|
|
40
|
+
if ((settings.track ?? DEFAULT_TRACK_NETWORK) === false)
|
|
40
41
|
throw new NetworkTrackingDisabledError();
|
|
41
42
|
const tracker = await NetworkTracker.start(this.connector, await this.deps.tree.ensure(), this.contextId);
|
|
42
43
|
const { emitter } = this.deps;
|
|
43
44
|
tracker.on("request", (request) => emitter.emit("request", request));
|
|
44
45
|
tracker.on("response", (response) => emitter.emit("response", this.responseFor(response)));
|
|
45
46
|
tracker.on("requestfailed", (failed) => emitter.emit("requestfailed", failed));
|
|
46
|
-
if (settings.collectBodies)
|
|
47
|
+
if (settings.collectBodies ?? DEFAULT_COLLECT_BODIES)
|
|
47
48
|
await this.startBodyCollection();
|
|
48
49
|
return tracker;
|
|
49
50
|
}, (err) => err instanceof NetworkTrackingDisabledError);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { readConfig } from "../config/config.js";
|
|
2
|
+
import { DEFAULT_IDLE_TIME, DEFAULT_NAVIGATION_TIMEOUT, DEFAULT_WAIT_UNTIL, } from "../config/defaults.js";
|
|
2
3
|
import logger from "../logger/index.js";
|
|
3
|
-
/** Time (ms) a navigation or a wait for a load state may take */
|
|
4
|
-
const DEFAULT_NAVIGATION_TIMEOUT = 30000;
|
|
5
|
-
/** Time (ms) without requests in flight that counts as idle */
|
|
6
|
-
const DEFAULT_IDLE_TIME = 500;
|
|
7
4
|
/** A section of the config, or `{}` when it cannot be read (a page can be used without a project) */
|
|
8
5
|
async function readSection(key) {
|
|
9
6
|
try {
|
|
@@ -42,7 +39,7 @@ export class PageConfig {
|
|
|
42
39
|
}
|
|
43
40
|
/** What a navigation waits for: the call's, else config `navigation.waitUntil`, else the load event */
|
|
44
41
|
async navigationWait(override) {
|
|
45
|
-
return override ?? (await this.navigation()).waitUntil ??
|
|
42
|
+
return (override ?? (await this.navigation()).waitUntil ?? DEFAULT_WAIT_UNTIL);
|
|
46
43
|
}
|
|
47
44
|
/** Time (ms) without requests in flight that counts as idle: the call's, else config `network.idleTime`, else 500 */
|
|
48
45
|
async idleTime(override) {
|
package/dist/browser/page.js
CHANGED
|
@@ -3,6 +3,7 @@ import { cssSelector, labelSelector, roleSelector, testIdSelector, textSelector,
|
|
|
3
3
|
import EventEmitter from "node:events";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { projectDir } from "../config/config.js";
|
|
6
|
+
import { DEFAULT_VIEWPORT } from "../config/defaults.js";
|
|
6
7
|
import logger from "../logger/index.js";
|
|
7
8
|
import { ContextTree } from "./context-tree.js";
|
|
8
9
|
import { printPdf } from "./pdf.js";
|
|
@@ -21,7 +22,7 @@ import { PageTransfers } from "./page-transfers.js";
|
|
|
21
22
|
import { initScriptSource } from "./init-script.js";
|
|
22
23
|
import { sourcesFor, waitForPageEvent, } from "./page-events.js";
|
|
23
24
|
import { PageConfig, } from "./page-settings.js";
|
|
24
|
-
import { applyViewport
|
|
25
|
+
import { applyViewport } from "./viewport.js";
|
|
25
26
|
import { HelperRealm } from "../locator/helper-realm.js";
|
|
26
27
|
import { addPreload } from "../script/preload.js";
|
|
27
28
|
import { BiDiError } from "../transport/bidi-error.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
2
|
/** session.new got no reply in time */
|
|
3
3
|
export declare class BrowserStartTimeoutError extends Error {
|
|
4
4
|
constructor(label: string, timeout: number);
|
package/dist/browser/session.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_COMMAND_TIMEOUT
|
|
1
|
+
import { DEFAULT_COMMAND_TIMEOUT } from "../config/defaults.js";
|
|
2
2
|
import { BiDiError } from "../transport/bidi-error.js";
|
|
3
3
|
import { SESSION_CAPABILITIES } from "./session-capabilities.js";
|
|
4
4
|
/** session.new waits at least this long (ms): the first start after an install can take a while (macOS checks the new app) */
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
|
-
/** The size a page gets unless config `use.viewport` says otherwise */
|
|
3
|
-
export declare const DEFAULT_VIEWPORT: {
|
|
4
|
-
width: number;
|
|
5
|
-
height: number;
|
|
6
|
-
};
|
|
7
2
|
/** A viewport as `page.setViewport` takes it: a size and optionally a device pixel ratio */
|
|
8
3
|
export interface Viewport {
|
|
9
4
|
width: number;
|
package/dist/browser/viewport.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { callFunction } from "../script/call-function.js";
|
|
2
|
-
/** The size a page gets unless config `use.viewport` says otherwise */
|
|
3
|
-
export const DEFAULT_VIEWPORT = { width: 1280, height: 720 };
|
|
4
2
|
/** Time (ms) without a `resize` event after which a viewport reset counts as done */
|
|
5
3
|
const RESIZE_QUIET_TIME = 50;
|
|
6
4
|
/** Time (ms) a viewport reset waits for a `resize` event that may never come (the size did not change) */
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What this framework can do, as plain data. The SAMURAI app reads it from a project's own copy
|
|
3
|
+
* of the framework to know which features that copy supports, so it asks instead of guessing.
|
|
4
|
+
* Imports nothing, so it is cheap to load; a key that is missing means the copy is too old to
|
|
5
|
+
* support it. Every value must survive `JSON.stringify`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CAPABILITIES: {
|
|
8
|
+
/** Test groups: a group name on a test and running one group. */
|
|
9
|
+
readonly groups: true;
|
|
10
|
+
/** The toolbar the recorder shows in the page it records. */
|
|
11
|
+
readonly recorderToolbar: true;
|
|
12
|
+
/** The recorder writing its recording to files in the project. */
|
|
13
|
+
readonly recorderFiles: true;
|
|
14
|
+
/** The flows contract version; a test keeps it equal to `FLOWS_API`. */
|
|
15
|
+
readonly flowsApi: 2;
|
|
16
|
+
/** Steps that flows can run, including project steps made with `defineStep`. */
|
|
17
|
+
readonly flowSteps: true;
|
|
18
|
+
/** Browsers the framework downloads and manages itself. */
|
|
19
|
+
readonly managedBrowsers: true;
|
|
20
|
+
/** Modifiers on tests, such as `skip`, `only` and `fixme`. */
|
|
21
|
+
readonly modifiers: true;
|
|
22
|
+
/** A reason given with a skipped test, shown in the results. */
|
|
23
|
+
readonly skipReason: true;
|
|
24
|
+
/** An id and a relative file on each test `listTests` finds, the same ids the groups use. */
|
|
25
|
+
readonly testIds: true;
|
|
26
|
+
/** `loadConfig` from `./project`: the config with every default applied, the way a run uses it. */
|
|
27
|
+
readonly resolvedConfig: true;
|
|
28
|
+
/** The recorder's first `onStatus` call, `{ state: "recording", toolbar }`, right after it starts, and a `RecordResult` with every field always present. */
|
|
29
|
+
readonly recorderStartStatus: true;
|
|
30
|
+
};
|
|
31
|
+
/** The shape of {@link CAPABILITIES}. */
|
|
32
|
+
export type Capabilities = typeof CAPABILITIES;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What this framework can do, as plain data. The SAMURAI app reads it from a project's own copy
|
|
3
|
+
* of the framework to know which features that copy supports, so it asks instead of guessing.
|
|
4
|
+
* Imports nothing, so it is cheap to load; a key that is missing means the copy is too old to
|
|
5
|
+
* support it. Every value must survive `JSON.stringify`.
|
|
6
|
+
*/
|
|
7
|
+
export const CAPABILITIES = {
|
|
8
|
+
/** Test groups: a group name on a test and running one group. */
|
|
9
|
+
groups: true,
|
|
10
|
+
/** The toolbar the recorder shows in the page it records. */
|
|
11
|
+
recorderToolbar: true,
|
|
12
|
+
/** The recorder writing its recording to files in the project. */
|
|
13
|
+
recorderFiles: true,
|
|
14
|
+
/** The flows contract version; a test keeps it equal to `FLOWS_API`. */
|
|
15
|
+
flowsApi: 2,
|
|
16
|
+
/** Steps that flows can run, including project steps made with `defineStep`. */
|
|
17
|
+
flowSteps: true,
|
|
18
|
+
/** Browsers the framework downloads and manages itself. */
|
|
19
|
+
managedBrowsers: true,
|
|
20
|
+
/** Modifiers on tests, such as `skip`, `only` and `fixme`. */
|
|
21
|
+
modifiers: true,
|
|
22
|
+
/** A reason given with a skipped test, shown in the results. */
|
|
23
|
+
skipReason: true,
|
|
24
|
+
/** An id and a relative file on each test `listTests` finds, the same ids the groups use. */
|
|
25
|
+
testIds: true,
|
|
26
|
+
/** `loadConfig` from `./project`: the config with every default applied, the way a run uses it. */
|
|
27
|
+
resolvedConfig: true,
|
|
28
|
+
/** The recorder's first `onStatus` call, `{ state: "recording", toolbar }`, right after it starts, and a `RecordResult` with every field always present. */
|
|
29
|
+
recorderStartStatus: true,
|
|
30
|
+
};
|
package/dist/cli/main.js
CHANGED
|
@@ -60,7 +60,7 @@ export async function main(argv, io) {
|
|
|
60
60
|
io.out(`${JSON.stringify(tests, null, 2)}\n`);
|
|
61
61
|
else
|
|
62
62
|
for (const found of tests)
|
|
63
|
-
io.out(`${found.name}${found.modifier ? ` [${found.modifier}]` : ""}\n ${found.file}\n`);
|
|
63
|
+
io.out(`${found.name}${found.modifier ? ` [${found.modifier}${found.reason ? `: ${found.reason}` : ""}]` : ""}\n ${found.file}\n`);
|
|
64
64
|
return 0;
|
|
65
65
|
}
|
|
66
66
|
const human = values.json
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const DEFAULT_BROWSERS = ["firefox"];
|
|
1
|
+
import { DEFAULT_BROWSERS } from "./defaults.js";
|
|
3
2
|
/**
|
|
4
3
|
* The browsers a run uses, the first that is set winning: the command line, the group being run, the project, then
|
|
5
4
|
* Firefox. An empty list counts as not given.
|
package/dist/config/config.d.ts
CHANGED
|
@@ -17,8 +17,10 @@ export declare function projectDir(): string;
|
|
|
17
17
|
* An embedding app points it at a folder it may write to.
|
|
18
18
|
*/
|
|
19
19
|
export declare function browsersDir(): string;
|
|
20
|
+
/** Throws the error a run throws for a config with a bad key */
|
|
21
|
+
export declare function validateConfig(config: SamuraiTestConfig): void;
|
|
20
22
|
/** The whole `samurai.config.ts` of the project (the working directory unless a project is active) */
|
|
21
|
-
export declare function
|
|
23
|
+
export declare function loadRawConfig(): Promise<SamuraiTestConfig>;
|
|
22
24
|
/** A project's config without keeping the project active (`withProject` is one at a time): the given one, else its samurai.config.ts */
|
|
23
25
|
export declare function projectConfig(options: {
|
|
24
26
|
projectDir?: string;
|
package/dist/config/config.js
CHANGED
|
@@ -24,13 +24,17 @@ export function browsersDir() {
|
|
|
24
24
|
activeProject?.dataDir ??
|
|
25
25
|
path.join(projectDir(), "browsers"));
|
|
26
26
|
}
|
|
27
|
+
/** Throws the error a run throws for a config with a bad key */
|
|
28
|
+
export function validateConfig(config) {
|
|
29
|
+
validateBrowserConfig(config);
|
|
30
|
+
validateWorkersConfig(config);
|
|
31
|
+
validateForbidOnlyConfig(config);
|
|
32
|
+
validateRetriesConfig(config);
|
|
33
|
+
}
|
|
27
34
|
/** The whole `samurai.config.ts` of the project (the working directory unless a project is active) */
|
|
28
|
-
export async function
|
|
35
|
+
export async function loadRawConfig() {
|
|
29
36
|
if (activeProject?.config) {
|
|
30
|
-
|
|
31
|
-
validateWorkersConfig(activeProject.config);
|
|
32
|
-
validateForbidOnlyConfig(activeProject.config);
|
|
33
|
-
validateRetriesConfig(activeProject.config);
|
|
37
|
+
validateConfig(activeProject.config);
|
|
34
38
|
return activeProject.config;
|
|
35
39
|
}
|
|
36
40
|
const configUrl = path.join(projectDir(), "samurai.config.ts");
|
|
@@ -39,10 +43,7 @@ export async function loadConfig() {
|
|
|
39
43
|
if (!config.default) {
|
|
40
44
|
throw new Error("samurai.config.ts does not export defineConfig as default!");
|
|
41
45
|
}
|
|
42
|
-
|
|
43
|
-
validateWorkersConfig(config.default);
|
|
44
|
-
validateForbidOnlyConfig(config.default);
|
|
45
|
-
validateRetriesConfig(config.default);
|
|
46
|
+
validateConfig(config.default);
|
|
46
47
|
return config.default;
|
|
47
48
|
}
|
|
48
49
|
/** A project's config without keeping the project active (`withProject` is one at a time): the given one, else its samurai.config.ts */
|
|
@@ -52,14 +53,14 @@ export async function projectConfig(options) {
|
|
|
52
53
|
const previous = activeProject;
|
|
53
54
|
setActiveProject({ dir: path.resolve(options.projectDir ?? process.cwd()) });
|
|
54
55
|
try {
|
|
55
|
-
return await
|
|
56
|
+
return await loadRawConfig();
|
|
56
57
|
}
|
|
57
58
|
finally {
|
|
58
59
|
setActiveProject(previous);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
export async function readConfig(key) {
|
|
62
|
-
const value = (await
|
|
63
|
+
const value = (await loadRawConfig())[key];
|
|
63
64
|
logger.debug("Resolved config value: ", { value });
|
|
64
65
|
return value;
|
|
65
66
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** The test folder when the config names none */
|
|
2
|
+
export declare const DEFAULT_SRC_DIR = "./src";
|
|
3
|
+
/** The spec pattern when a group names none */
|
|
4
|
+
export declare const DEFAULT_TEST_MATCH = "**/*.spec.ts";
|
|
5
|
+
/** The browsers of a run that names none */
|
|
6
|
+
export declare const DEFAULT_BROWSERS: readonly ["firefox"];
|
|
7
|
+
/** Time (ms) a test may take */
|
|
8
|
+
export declare const DEFAULT_TIMEOUT = 30000;
|
|
9
|
+
/** The retry time of actions and assertions when nothing sets one (ms) */
|
|
10
|
+
export declare const DEFAULT_EXPECT_TIMEOUT = 5000;
|
|
11
|
+
/** Time (ms) a navigation or a wait for a load state may take */
|
|
12
|
+
export declare const DEFAULT_NAVIGATION_TIMEOUT = 30000;
|
|
13
|
+
/** What a navigation waits for when nothing says otherwise: the load event */
|
|
14
|
+
export declare const DEFAULT_WAIT_UNTIL = "complete";
|
|
15
|
+
/** Time (ms) a command may wait for its reply before it fails with code "timeout" */
|
|
16
|
+
export declare const DEFAULT_COMMAND_TIMEOUT = 30000;
|
|
17
|
+
/** Time (ms) without requests in flight that counts as idle */
|
|
18
|
+
export declare const DEFAULT_IDLE_TIME = 500;
|
|
19
|
+
/** Time (ms) a handler may take to answer a request before the router lets it continue */
|
|
20
|
+
export declare const DEFAULT_ROUTE_TIMEOUT = 30000;
|
|
21
|
+
/** How large a response body may be to be kept: 10 MiB */
|
|
22
|
+
export declare const DEFAULT_MAX_BODY_SIZE: number;
|
|
23
|
+
/** Whether response bodies are collected for every request */
|
|
24
|
+
export declare const DEFAULT_COLLECT_BODIES = false;
|
|
25
|
+
/** Whether the network of a page is tracked */
|
|
26
|
+
export declare const DEFAULT_TRACK_NETWORK = true;
|
|
27
|
+
/** The size a page gets unless config `use.viewport` says otherwise */
|
|
28
|
+
export declare const DEFAULT_VIEWPORT: {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
};
|
|
32
|
+
/** Where downloads are saved, relative to the project */
|
|
33
|
+
export declare const DEFAULT_DOWNLOADS_DIR = "result/downloads";
|
|
34
|
+
/** The files folder, relative to the project */
|
|
35
|
+
export declare const DEFAULT_FILES_DIR = "files";
|
|
36
|
+
/** How many times a failing test is run again */
|
|
37
|
+
export declare const DEFAULT_RETRIES = 0;
|
|
38
|
+
/** Which page log entries go in the report: those of failed tests */
|
|
39
|
+
export declare const DEFAULT_LOGS_CAPTURE = "failures";
|
|
40
|
+
/** Whether uncaught page errors fail a test */
|
|
41
|
+
export declare const DEFAULT_FAIL_ON_PAGE_ERROR = false;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** The test folder when the config names none */
|
|
2
|
+
export const DEFAULT_SRC_DIR = "./src";
|
|
3
|
+
/** The spec pattern when a group names none */
|
|
4
|
+
export const DEFAULT_TEST_MATCH = "**/*.spec.ts";
|
|
5
|
+
/** The browsers of a run that names none */
|
|
6
|
+
export const DEFAULT_BROWSERS = ["firefox"];
|
|
7
|
+
/** Time (ms) a test may take */
|
|
8
|
+
export const DEFAULT_TIMEOUT = 30000;
|
|
9
|
+
/** The retry time of actions and assertions when nothing sets one (ms) */
|
|
10
|
+
export const DEFAULT_EXPECT_TIMEOUT = 5000;
|
|
11
|
+
/** Time (ms) a navigation or a wait for a load state may take */
|
|
12
|
+
export const DEFAULT_NAVIGATION_TIMEOUT = 30000;
|
|
13
|
+
/** What a navigation waits for when nothing says otherwise: the load event */
|
|
14
|
+
export const DEFAULT_WAIT_UNTIL = "complete";
|
|
15
|
+
/** Time (ms) a command may wait for its reply before it fails with code "timeout" */
|
|
16
|
+
export const DEFAULT_COMMAND_TIMEOUT = 30000;
|
|
17
|
+
/** Time (ms) without requests in flight that counts as idle */
|
|
18
|
+
export const DEFAULT_IDLE_TIME = 500;
|
|
19
|
+
/** Time (ms) a handler may take to answer a request before the router lets it continue */
|
|
20
|
+
export const DEFAULT_ROUTE_TIMEOUT = 30000;
|
|
21
|
+
/** How large a response body may be to be kept: 10 MiB */
|
|
22
|
+
export const DEFAULT_MAX_BODY_SIZE = 10 * 1024 * 1024;
|
|
23
|
+
/** Whether response bodies are collected for every request */
|
|
24
|
+
export const DEFAULT_COLLECT_BODIES = false;
|
|
25
|
+
/** Whether the network of a page is tracked */
|
|
26
|
+
export const DEFAULT_TRACK_NETWORK = true;
|
|
27
|
+
/** The size a page gets unless config `use.viewport` says otherwise */
|
|
28
|
+
export const DEFAULT_VIEWPORT = { width: 1280, height: 720 };
|
|
29
|
+
/** Where downloads are saved, relative to the project */
|
|
30
|
+
export const DEFAULT_DOWNLOADS_DIR = "result/downloads";
|
|
31
|
+
/** The files folder, relative to the project */
|
|
32
|
+
export const DEFAULT_FILES_DIR = "files";
|
|
33
|
+
/** How many times a failing test is run again */
|
|
34
|
+
export const DEFAULT_RETRIES = 0;
|
|
35
|
+
/** Which page log entries go in the report: those of failed tests */
|
|
36
|
+
export const DEFAULT_LOGS_CAPTURE = "failures";
|
|
37
|
+
/** Whether uncaught page errors fail a test */
|
|
38
|
+
export const DEFAULT_FAIL_ON_PAGE_ERROR = false;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { SupportedBrowser } from "../types/browser.js";
|
|
2
|
+
import type { SamuraiGroup, SamuraiTestConfig } from "../types/config.js";
|
|
3
|
+
import { type Variables } from "./run-settings.js";
|
|
4
|
+
/** Where the environment variables come from: `CI` and the like */
|
|
5
|
+
type Env = Record<string, string | undefined>;
|
|
6
|
+
/** One environment with the project's values and the built-in defaults merged in, as a run without overrides uses it */
|
|
7
|
+
export interface ResolvedEnvironment {
|
|
8
|
+
baseURL?: string;
|
|
9
|
+
/** Time (ms) one test may take */
|
|
10
|
+
timeout: number;
|
|
11
|
+
/** Time (ms) actions and assertions retry */
|
|
12
|
+
expectTimeout: number;
|
|
13
|
+
/** Whether the browser runs without a window */
|
|
14
|
+
headless: boolean;
|
|
15
|
+
variables: Variables;
|
|
16
|
+
}
|
|
17
|
+
/** An entry of `logs.ignoreErrors` as data: a `RegExp` keeps its source and flags */
|
|
18
|
+
export interface ResolvedIgnoreError {
|
|
19
|
+
text: string;
|
|
20
|
+
regexp: boolean;
|
|
21
|
+
flags?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A project's config with every default applied and the run-level precedence resolved: what a run without command
|
|
25
|
+
* line flags uses. Plain JSON data, so it survives `process.send`.
|
|
26
|
+
*/
|
|
27
|
+
export interface ResolvedConfig {
|
|
28
|
+
/** The project folder, absolute */
|
|
29
|
+
projectDir: string;
|
|
30
|
+
/** The test folder, absolute and not resolved through symlinks: use `FoundTest.relativeFile` rather than computing paths against it */
|
|
31
|
+
srcDir: string;
|
|
32
|
+
/** The test folder as the config writes it */
|
|
33
|
+
srcDirRelative: string;
|
|
34
|
+
browsers: SupportedBrowser[];
|
|
35
|
+
browserPaths: {
|
|
36
|
+
firefox?: string;
|
|
37
|
+
chrome?: string;
|
|
38
|
+
};
|
|
39
|
+
timeout: number;
|
|
40
|
+
expectTimeout: number;
|
|
41
|
+
navigationTimeout: number;
|
|
42
|
+
waitUntil: "none" | "interactive" | "complete";
|
|
43
|
+
bidiCommandTimeout: number;
|
|
44
|
+
/** The config's `workers` as written */
|
|
45
|
+
workers?: number | string;
|
|
46
|
+
/** The workers a run without `--workers` and `SAMURAI_WORKERS` uses on this machine */
|
|
47
|
+
workersResolved: number;
|
|
48
|
+
retries: number;
|
|
49
|
+
forbidOnly: boolean;
|
|
50
|
+
headless: boolean;
|
|
51
|
+
/** `null` keeps the browser's own size */
|
|
52
|
+
viewport: {
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
} | null;
|
|
56
|
+
downloadsDir: string;
|
|
57
|
+
filesDir: string;
|
|
58
|
+
logs: {
|
|
59
|
+
capture: "off" | "failures" | "all";
|
|
60
|
+
failOnPageError: boolean;
|
|
61
|
+
ignoreErrors: ResolvedIgnoreError[];
|
|
62
|
+
};
|
|
63
|
+
network: {
|
|
64
|
+
track: boolean;
|
|
65
|
+
idleTime: number;
|
|
66
|
+
routeTimeout: number;
|
|
67
|
+
collectBodies: boolean;
|
|
68
|
+
maxBodySize: number;
|
|
69
|
+
};
|
|
70
|
+
/** The project's `baseURL` */
|
|
71
|
+
baseURL?: string;
|
|
72
|
+
/** The config's `defaultEnvironment` as written */
|
|
73
|
+
defaultEnvironment?: string;
|
|
74
|
+
/** Why a run without `--env` would stop: no environment chosen, or an unknown `defaultEnvironment` */
|
|
75
|
+
environmentProblem?: string;
|
|
76
|
+
/** Every environment by name; a config without `environments` has one, `"default"` */
|
|
77
|
+
environments: Record<string, ResolvedEnvironment>;
|
|
78
|
+
/** The groups as written, each with the browsers it runs in */
|
|
79
|
+
groups: (SamuraiGroup & {
|
|
80
|
+
browsers: SupportedBrowser[];
|
|
81
|
+
})[];
|
|
82
|
+
/** The kinds of the project's own flow steps */
|
|
83
|
+
steps: string[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The config with every default applied, from the same constants and functions a run uses. Throws the errors a run
|
|
87
|
+
* throws for a bad config; an environment a run could not choose is reported in `environmentProblem` instead.
|
|
88
|
+
* Pure: reads no files.
|
|
89
|
+
*/
|
|
90
|
+
export declare function resolveConfig(config: SamuraiTestConfig, options: {
|
|
91
|
+
projectDir: string;
|
|
92
|
+
env: Env;
|
|
93
|
+
}): ResolvedConfig;
|
|
94
|
+
/**
|
|
95
|
+
* A project's `samurai.config.ts` with every default applied, the way a run uses it (see {@link ResolvedConfig}).
|
|
96
|
+
* Leaves no project active. The caller registers `tsx` first (the framework does not), so the config file can be
|
|
97
|
+
* imported; an app does that in the child process that calls this. The config module is imported once per process
|
|
98
|
+
* (Node caches it): a later edit to `samurai.config.ts` is seen only in a new process.
|
|
99
|
+
* @param options.projectDir The project folder. @default the working directory
|
|
100
|
+
* @param options.env Where `CI` is read from. @default `process.env`
|
|
101
|
+
*/
|
|
102
|
+
export declare function loadConfig(options?: {
|
|
103
|
+
projectDir?: string;
|
|
104
|
+
env?: Env;
|
|
105
|
+
}): Promise<ResolvedConfig>;
|
|
106
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { browsersFor } from "./browsers-for.js";
|
|
3
|
+
import { projectConfig, validateConfig } from "./config.js";
|
|
4
|
+
import { DEFAULT_COLLECT_BODIES, DEFAULT_COMMAND_TIMEOUT, DEFAULT_DOWNLOADS_DIR, DEFAULT_FAIL_ON_PAGE_ERROR, DEFAULT_FILES_DIR, DEFAULT_IDLE_TIME, DEFAULT_LOGS_CAPTURE, DEFAULT_MAX_BODY_SIZE, DEFAULT_NAVIGATION_TIMEOUT, DEFAULT_RETRIES, DEFAULT_ROUTE_TIMEOUT, DEFAULT_SRC_DIR, DEFAULT_TRACK_NETWORK, DEFAULT_VIEWPORT, DEFAULT_WAIT_UNTIL, } from "./defaults.js";
|
|
5
|
+
import { forbidsOnly } from "./forbid-only.js";
|
|
6
|
+
import { IMPLICIT_ENVIRONMENT, resolveRunSettings, UnknownEnvironmentError, } from "./run-settings.js";
|
|
7
|
+
import { resolveWorkers } from "./workers-option.js";
|
|
8
|
+
/** The message a run without `--env` throws for this config, if any */
|
|
9
|
+
function environmentProblem(config, env) {
|
|
10
|
+
try {
|
|
11
|
+
resolveRunSettings(config, {}, env);
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error instanceof UnknownEnvironmentError)
|
|
16
|
+
return error.message;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Every environment of the config merged the way `resolveRunSettings` merges it */
|
|
21
|
+
function resolveEnvironments(config, env) {
|
|
22
|
+
const names = Object.keys(config.environments ?? {});
|
|
23
|
+
return Object.fromEntries((names.length > 0 ? names : [IMPLICIT_ENVIRONMENT]).map((name) => {
|
|
24
|
+
const { environment, ...settings } = resolveRunSettings(config, { environment: name }, env);
|
|
25
|
+
return [environment, settings];
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
/** `logs.ignoreErrors` as JSON-safe data */
|
|
29
|
+
function ignoreErrorsOf(list = []) {
|
|
30
|
+
return list.map((entry) => typeof entry === "string"
|
|
31
|
+
? { text: entry, regexp: false }
|
|
32
|
+
: {
|
|
33
|
+
text: entry.source,
|
|
34
|
+
regexp: true,
|
|
35
|
+
...(entry.flags !== "" && { flags: entry.flags }),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The config with every default applied, from the same constants and functions a run uses. Throws the errors a run
|
|
40
|
+
* throws for a bad config; an environment a run could not choose is reported in `environmentProblem` instead.
|
|
41
|
+
* Pure: reads no files.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveConfig(config, options) {
|
|
44
|
+
validateConfig(config);
|
|
45
|
+
const { env } = options;
|
|
46
|
+
const projectDir = path.resolve(options.projectDir);
|
|
47
|
+
const srcDirRelative = config.srcDir ?? DEFAULT_SRC_DIR;
|
|
48
|
+
// The project level: the same precedence without an environment
|
|
49
|
+
const project = resolveRunSettings({ ...config, environments: undefined, defaultEnvironment: undefined }, {}, env);
|
|
50
|
+
const problem = environmentProblem(config, env);
|
|
51
|
+
const viewport = config.use?.viewport;
|
|
52
|
+
return {
|
|
53
|
+
projectDir,
|
|
54
|
+
srcDir: path.resolve(projectDir, srcDirRelative),
|
|
55
|
+
srcDirRelative,
|
|
56
|
+
browsers: browsersFor({ project: config.browsers }),
|
|
57
|
+
browserPaths: { ...config.browserPaths },
|
|
58
|
+
timeout: project.timeout,
|
|
59
|
+
expectTimeout: project.expectTimeout,
|
|
60
|
+
navigationTimeout: config.navigation?.timeout ?? DEFAULT_NAVIGATION_TIMEOUT,
|
|
61
|
+
waitUntil: config.navigation?.waitUntil ?? DEFAULT_WAIT_UNTIL,
|
|
62
|
+
bidiCommandTimeout: config.bidi?.commandTimeout ?? DEFAULT_COMMAND_TIMEOUT,
|
|
63
|
+
...(config.workers !== undefined && { workers: config.workers }),
|
|
64
|
+
workersResolved: resolveWorkers({ config: config.workers }),
|
|
65
|
+
// Matches the runner's `resolveRetries` without the command line flag and the environment (config/ cannot import runner/)
|
|
66
|
+
retries: config.retries ?? DEFAULT_RETRIES,
|
|
67
|
+
forbidOnly: forbidsOnly(config, env),
|
|
68
|
+
headless: project.headless,
|
|
69
|
+
viewport: viewport === null ? null : { ...(viewport ?? DEFAULT_VIEWPORT) },
|
|
70
|
+
downloadsDir: config.downloadsDir ?? DEFAULT_DOWNLOADS_DIR,
|
|
71
|
+
filesDir: config.filesDir ?? DEFAULT_FILES_DIR,
|
|
72
|
+
logs: {
|
|
73
|
+
capture: config.logs?.capture ?? DEFAULT_LOGS_CAPTURE,
|
|
74
|
+
failOnPageError: config.logs?.failOnPageError ?? DEFAULT_FAIL_ON_PAGE_ERROR,
|
|
75
|
+
ignoreErrors: ignoreErrorsOf(config.logs?.ignoreErrors),
|
|
76
|
+
},
|
|
77
|
+
network: {
|
|
78
|
+
track: config.network?.track ?? DEFAULT_TRACK_NETWORK,
|
|
79
|
+
idleTime: config.network?.idleTime ?? DEFAULT_IDLE_TIME,
|
|
80
|
+
routeTimeout: config.network?.routeTimeout ?? DEFAULT_ROUTE_TIMEOUT,
|
|
81
|
+
collectBodies: config.network?.collectBodies ?? DEFAULT_COLLECT_BODIES,
|
|
82
|
+
maxBodySize: config.network?.maxBodySize ?? DEFAULT_MAX_BODY_SIZE,
|
|
83
|
+
},
|
|
84
|
+
...(config.baseURL !== undefined && { baseURL: config.baseURL }),
|
|
85
|
+
...(config.defaultEnvironment !== undefined && {
|
|
86
|
+
defaultEnvironment: config.defaultEnvironment,
|
|
87
|
+
}),
|
|
88
|
+
...(problem !== undefined && { environmentProblem: problem }),
|
|
89
|
+
environments: resolveEnvironments(config, env),
|
|
90
|
+
groups: (config.groups ?? []).map((group) => ({
|
|
91
|
+
...group,
|
|
92
|
+
browsers: browsersFor({
|
|
93
|
+
group: group.browsers,
|
|
94
|
+
project: config.browsers,
|
|
95
|
+
}),
|
|
96
|
+
})),
|
|
97
|
+
steps: (config.steps ?? []).map((step) => step.kind),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A project's `samurai.config.ts` with every default applied, the way a run uses it (see {@link ResolvedConfig}).
|
|
102
|
+
* Leaves no project active. The caller registers `tsx` first (the framework does not), so the config file can be
|
|
103
|
+
* imported; an app does that in the child process that calls this. The config module is imported once per process
|
|
104
|
+
* (Node caches it): a later edit to `samurai.config.ts` is seen only in a new process.
|
|
105
|
+
* @param options.projectDir The project folder. @default the working directory
|
|
106
|
+
* @param options.env Where `CI` is read from. @default `process.env`
|
|
107
|
+
*/
|
|
108
|
+
export async function loadConfig(options = {}) {
|
|
109
|
+
const projectDir = path.resolve(options.projectDir ?? process.cwd());
|
|
110
|
+
const config = await projectConfig({ projectDir });
|
|
111
|
+
return resolveConfig(config, { projectDir, env: options.env ?? process.env });
|
|
112
|
+
}
|
|
@@ -20,6 +20,8 @@ export interface RunSettings {
|
|
|
20
20
|
/** Whether the browser runs without a window */
|
|
21
21
|
headless: boolean;
|
|
22
22
|
}
|
|
23
|
+
/** The environment of a config without `environments` */
|
|
24
|
+
export declare const IMPLICIT_ENVIRONMENT = "default";
|
|
23
25
|
export declare class UnknownEnvironmentError extends Error {
|
|
24
26
|
constructor(message: string);
|
|
25
27
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const DEFAULT_EXPECT_TIMEOUT = 5000;
|
|
1
|
+
import { DEFAULT_EXPECT_TIMEOUT, DEFAULT_TIMEOUT } from "./defaults.js";
|
|
3
2
|
/** The environment of a config without `environments` */
|
|
4
|
-
const IMPLICIT_ENVIRONMENT = "default";
|
|
3
|
+
export const IMPLICIT_ENVIRONMENT = "default";
|
|
5
4
|
export class UnknownEnvironmentError extends Error {
|
|
6
5
|
constructor(message) {
|
|
7
6
|
super(message);
|
package/dist/config/timeouts.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readConfig } from "./config.js";
|
|
2
|
+
import { DEFAULT_EXPECT_TIMEOUT } from "./defaults.js";
|
|
2
3
|
import { peekRunSettings } from "./run-settings.js";
|
|
3
|
-
/** The retry time of actions and assertions when nothing sets one (ms) */
|
|
4
|
-
const DEFAULT_EXPECT_TIMEOUT = 5000;
|
|
5
4
|
/** Per-call timeout, else the active run's expect timeout, else config `expect.timeout`, else 5000 */
|
|
6
5
|
export async function resolveTimeout(perCall) {
|
|
7
6
|
if (perCall !== undefined)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { availableParallelism } from "node:os";
|
|
2
|
-
import { parseWorkers } from "
|
|
2
|
+
import { parseWorkers } from "./workers.js";
|
|
3
3
|
/**
|
|
4
4
|
* How many workers a run uses: the flag, else `SAMURAI_WORKERS`, else the config's `workers`, else half the CPUs.
|
|
5
5
|
* A percentage is a share of the CPUs. At least 1.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { DEFAULT_SRC_DIR } from "../../config/defaults.js";
|
|
2
3
|
import { projectConfig } from "../../config/config.js";
|
|
3
4
|
import { checkFlow, withKeys, } from "../core/index.js";
|
|
4
|
-
import {
|
|
5
|
+
import { realPath, resolveGroups, testId, toPosix, } from "../../runner/groups.js";
|
|
5
6
|
import logger, { keepLogFiles } from "../../logger/index.js";
|
|
6
7
|
import { resolveRetries } from "../../runner/retries-option.js";
|
|
7
|
-
import { resolveWorkers } from "../../
|
|
8
|
+
import { resolveWorkers } from "../../config/workers-option.js";
|
|
8
9
|
import { withProject } from "../../runner/project.js";
|
|
9
|
-
import { browsersFor } from "../../
|
|
10
|
+
import { browsersFor } from "../../config/browsers-for.js";
|
|
10
11
|
import { DEFAULT_PORT } from "../../runner/ports.js";
|
|
11
12
|
import { runTests } from "../../runner/run.js";
|
|
12
13
|
import TestRunner from "../../runner/test-runner.js";
|
|
@@ -84,7 +85,7 @@ async function prepare(options) {
|
|
|
84
85
|
...(options.dataDir !== undefined && { dataDir: options.dataDir }),
|
|
85
86
|
environment: name,
|
|
86
87
|
}, async (run) => {
|
|
87
|
-
const found = await (await TestRunner.init(run)).
|
|
88
|
+
const { tests: found } = await (await TestRunner.init(run)).list();
|
|
88
89
|
const tests = found.map((t) => ({
|
|
89
90
|
id: testId(toPosix(path.relative(srcDir, t.file)), t.name),
|
|
90
91
|
file: t.file,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
+
import { DEFAULT_MAX_BODY_SIZE } from "../config/defaults.js";
|
|
1
2
|
import { BiDiError } from "../transport/bidi-error.js";
|
|
2
|
-
/** How large a response body may be to be kept: 10 MiB */
|
|
3
|
-
const DEFAULT_MAX_BODY_SIZE = 10 * 1024 * 1024;
|
|
4
3
|
/**
|
|
5
4
|
* Chrome counts the headers of a response in the size it compares with the collector's limit (a 14-byte body
|
|
6
5
|
* arrives as about 200 bytes), Firefox counts the body. Chrome's limit is raised by this much and the body is
|