@itmetsam/samurai-framework 0.21.4 → 0.22.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 +8 -1
- package/dist/api.js +1 -0
- package/dist/browser/browser-context.d.ts +18 -6
- package/dist/browser/browser-context.js +25 -7
- package/dist/browser/browser.d.ts +11 -2
- package/dist/browser/browser.js +31 -8
- package/dist/browser/cdp.d.ts +11 -0
- package/dist/browser/cdp.js +28 -0
- package/dist/browser/chrome-launch.d.ts +4 -0
- package/dist/browser/chrome-launch.js +11 -3
- package/dist/browser/context-cookie-store.d.ts +17 -0
- package/dist/browser/context-cookie-store.js +90 -0
- package/dist/browser/index.d.ts +8 -1
- package/dist/browser/index.js +5 -1
- package/dist/browser/keyboard.d.ts +35 -0
- package/dist/browser/keyboard.js +83 -0
- package/dist/browser/launch-firefox.js +1 -1
- package/dist/browser/launch-flags.d.ts +4 -1
- package/dist/browser/launch-flags.js +18 -5
- package/dist/browser/mouse.d.ts +42 -0
- package/dist/browser/mouse.js +66 -0
- package/dist/browser/page-navigation.d.ts +0 -2
- package/dist/browser/page-navigation.js +1 -6
- package/dist/browser/page-settings.d.ts +1 -1
- package/dist/browser/page.d.ts +20 -6
- package/dist/browser/page.js +24 -8
- package/dist/browser/session-capabilities.d.ts +10 -0
- package/dist/browser/session-capabilities.js +11 -0
- package/dist/browser/session.d.ts +2 -1
- package/dist/browser/session.js +3 -3
- package/dist/browser/storage-state.d.ts +4 -0
- package/dist/browser/storage-state.js +17 -0
- package/dist/browser/use-applier.d.ts +38 -0
- package/dist/browser/use-applier.js +121 -0
- package/dist/browser/use-commands.d.ts +36 -0
- package/dist/browser/use-commands.js +120 -0
- package/dist/browser/viewport.d.ts +3 -2
- package/dist/browser/viewport.js +15 -2
- package/dist/cli/args.d.ts +8 -4
- package/dist/cli/args.js +8 -5
- package/dist/cli/console-reporter.js +5 -1
- package/dist/cli/main.js +1 -0
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/usage.js +4 -1
- package/dist/config/base-url.d.ts +4 -0
- package/dist/config/base-url.js +6 -0
- package/dist/config/config.js +8 -0
- package/dist/config/fully-parallel.d.ts +3 -0
- package/dist/config/fully-parallel.js +6 -0
- package/dist/config/group-use.d.ts +8 -0
- package/dist/config/group-use.js +41 -0
- package/dist/config/resolved-config.d.ts +2 -0
- package/dist/config/resolved-config.js +3 -0
- package/dist/config/storage-state-file.d.ts +3 -0
- package/dist/config/storage-state-file.js +20 -0
- package/dist/config/use-options.d.ts +18 -0
- package/dist/config/use-options.js +184 -0
- package/dist/config/web-server-options.d.ts +7 -0
- package/dist/config/web-server-options.js +111 -0
- package/dist/locator/check-actions.js +1 -1
- package/dist/locator/drag-actions.d.ts +17 -0
- package/dist/locator/drag-actions.js +80 -0
- package/dist/locator/element-actions.d.ts +10 -4
- package/dist/locator/element-actions.js +23 -16
- package/dist/locator/element-state.d.ts +5 -0
- package/dist/locator/element-state.js +2 -1
- package/dist/locator/event-actions.d.ts +10 -0
- package/dist/locator/event-actions.js +81 -0
- package/dist/locator/input-actions.d.ts +2 -11
- package/dist/locator/input-actions.js +3 -56
- package/dist/locator/key-actions.d.ts +11 -0
- package/dist/locator/key-actions.js +52 -0
- package/dist/locator/key-map.d.ts +16 -0
- package/dist/locator/key-map.js +76 -0
- package/dist/locator/locator.d.ts +35 -5
- package/dist/locator/locator.js +70 -16
- package/dist/locator/page-scripts.d.ts +2 -0
- package/dist/locator/page-scripts.js +7 -0
- package/dist/locator/pointer-actions.d.ts +44 -0
- package/dist/locator/pointer-actions.js +117 -0
- package/dist/locator/pointer-element-actions.d.ts +21 -0
- package/dist/locator/pointer-element-actions.js +42 -0
- package/dist/logger/warn-once.d.ts +4 -0
- package/dist/logger/warn-once.js +13 -0
- package/dist/request/api-request-context.d.ts +65 -0
- package/dist/request/api-request-context.js +198 -0
- package/dist/request/api-response.d.ts +34 -0
- package/dist/request/api-response.js +57 -0
- package/dist/request/cookie-jar.d.ts +48 -0
- package/dist/request/cookie-jar.js +164 -0
- package/dist/request/http-transport.d.ts +31 -0
- package/dist/request/http-transport.js +113 -0
- package/dist/request/redirect.d.ts +10 -0
- package/dist/request/redirect.js +21 -0
- package/dist/request/request-body.d.ts +27 -0
- package/dist/request/request-body.js +71 -0
- package/dist/request/request.d.ts +16 -0
- package/dist/request/request.js +20 -0
- package/dist/runner/all-hooks.d.ts +7 -1
- package/dist/runner/all-hooks.js +17 -1
- package/dist/runner/attachments.d.ts +16 -0
- package/dist/runner/attachments.js +60 -0
- package/dist/runner/fixture-args.d.ts +15 -0
- package/dist/runner/fixture-args.js +95 -0
- package/dist/runner/fixture-defs.d.ts +22 -0
- package/dist/runner/fixture-defs.js +104 -0
- package/dist/runner/fixture-scope.d.ts +33 -0
- package/dist/runner/fixture-scope.js +156 -0
- package/dist/runner/fixtures.d.ts +37 -0
- package/dist/runner/fixtures.js +40 -0
- package/dist/runner/global-hooks.d.ts +13 -0
- package/dist/runner/global-hooks.js +66 -0
- package/dist/runner/hooks.d.ts +6 -1
- package/dist/runner/hooks.js +15 -3
- package/dist/runner/options.d.ts +13 -2
- package/dist/runner/output-tail.d.ts +14 -0
- package/dist/runner/output-tail.js +30 -0
- package/dist/runner/pool-schedule.d.ts +22 -0
- package/dist/runner/pool-schedule.js +55 -0
- package/dist/runner/pool-units.d.ts +34 -0
- package/dist/runner/pool-units.js +85 -0
- package/dist/runner/pooled-run.d.ts +7 -5
- package/dist/runner/pooled-run.js +19 -9
- package/dist/runner/registry.d.ts +3 -39
- package/dist/runner/registry.js +99 -21
- package/dist/runner/reporter.d.ts +17 -1
- package/dist/runner/reporter.js +15 -2
- package/dist/runner/request-fixture.d.ts +14 -0
- package/dist/runner/request-fixture.js +25 -0
- package/dist/runner/run-hooks.d.ts +16 -0
- package/dist/runner/run-hooks.js +52 -0
- package/dist/runner/run-test.d.ts +1 -3
- package/dist/runner/run-test.js +98 -35
- package/dist/runner/run.d.ts +1 -0
- package/dist/runner/run.js +1 -0
- package/dist/runner/select.d.ts +15 -4
- package/dist/runner/select.js +38 -12
- package/dist/runner/serial-run.d.ts +23 -0
- package/dist/runner/serial-run.js +88 -0
- package/dist/runner/serial.d.ts +34 -0
- package/dist/runner/serial.js +63 -0
- package/dist/runner/shard.d.ts +22 -0
- package/dist/runner/shard.js +73 -0
- package/dist/runner/steps.d.ts +13 -0
- package/dist/runner/steps.js +67 -0
- package/dist/runner/suite.d.ts +8 -3
- package/dist/runner/suite.js +12 -3
- package/dist/runner/test-annotations.d.ts +16 -0
- package/dist/runner/test-annotations.js +47 -0
- package/dist/runner/test-function.d.ts +115 -0
- package/dist/runner/test-function.js +1 -0
- package/dist/runner/test-info.d.ts +104 -0
- package/dist/runner/test-info.js +170 -0
- package/dist/runner/test-runner.d.ts +1 -0
- package/dist/runner/test-runner.js +64 -19
- package/dist/runner/test-timeout.d.ts +22 -0
- package/dist/runner/test-timeout.js +45 -0
- package/dist/runner/use-options.d.ts +7 -0
- package/dist/runner/use-options.js +25 -0
- package/dist/runner/wait-for-url.d.ts +20 -0
- package/dist/runner/wait-for-url.js +65 -0
- package/dist/runner/web-server.d.ts +16 -0
- package/dist/runner/web-server.js +165 -0
- package/dist/runner/worker-pool.d.ts +13 -5
- package/dist/runner/worker-pool.js +85 -51
- package/dist/runner/worker-protocol.d.ts +8 -0
- package/dist/runner/worker-session.js +5 -2
- package/dist/runner/worker.d.ts +2 -0
- package/dist/runner/worker.js +7 -1
- package/dist/script/element-handle.d.ts +1 -1
- package/dist/script/element-handle.js +1 -1
- package/dist/types/config.d.ts +86 -2
- package/dist/types/extend.d.ts +30 -0
- package/dist/types/test.d.ts +138 -0
- package/dist/types/use.d.ts +85 -0
- package/package.json +1 -1
- package/dist/browser/emulation.d.ts +0 -61
- package/dist/browser/emulation.js +0 -180
package/dist/types/test.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Browser } from "../browser/browser.js";
|
|
2
2
|
import type Page from "../browser/page.js";
|
|
3
|
+
import type { BrowserContext } from "../browser/browser-context.js";
|
|
3
4
|
import type Locator from "../locator/locator.js";
|
|
5
|
+
import type { APIRequestContext } from "../request/api-request-context.js";
|
|
4
6
|
import type { SupportedBrowser } from "./browser.js";
|
|
7
|
+
import type { UseOptions } from "./use.js";
|
|
5
8
|
|
|
6
9
|
/** What a test function receives */
|
|
7
10
|
export interface TestFixtures {
|
|
@@ -19,6 +22,106 @@ export interface TestFixtures {
|
|
|
19
22
|
inputs: Readonly<Record<string, string>>;
|
|
20
23
|
/** Saves the text values the test declared as outputs */
|
|
21
24
|
outputs: TestOutputs;
|
|
25
|
+
/** The browser context (cookies, storage) of `page`; same as `page.context()` */
|
|
26
|
+
context: BrowserContext;
|
|
27
|
+
/** The run's `baseURL` (the environment's, else the project's); `undefined` when none is set */
|
|
28
|
+
baseURL: string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Makes HTTP requests without a browser: its own cookies, the run's `baseURL`, `use.extraHTTPHeaders` and
|
|
31
|
+
* `use.acceptInsecureCerts`. For requests that share the page's cookies use `page.request`.
|
|
32
|
+
*/
|
|
33
|
+
request: APIRequestContext;
|
|
34
|
+
/** About the running test: its title, retry, attachments; same as `test.info()` */
|
|
35
|
+
testInfo: TestInfo;
|
|
36
|
+
/** Whether the page runs as a phone (`use.isMobile`); always false on Firefox, which cannot do it */
|
|
37
|
+
isMobile: boolean;
|
|
38
|
+
/** Whether the page has touch input (`use.hasTouch`); always false on Firefox, which cannot do it */
|
|
39
|
+
hasTouch: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A file or text a test attached to its result */
|
|
43
|
+
export interface TestAttachment {
|
|
44
|
+
name: string;
|
|
45
|
+
contentType: string;
|
|
46
|
+
/** Absolute path of the saved file */
|
|
47
|
+
path: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** An attachment as the report lists it */
|
|
51
|
+
export interface TestAttachmentEntry {
|
|
52
|
+
name: string;
|
|
53
|
+
contentType: string;
|
|
54
|
+
/** Relative to the result folder */
|
|
55
|
+
path: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A note on a test (`{ type: "issue", description: "PROJ-1" }`) that the report lists */
|
|
59
|
+
export interface TestAnnotation {
|
|
60
|
+
type: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A step the test ran with `test.step`, as the report lists it */
|
|
65
|
+
export interface TestStepEntry {
|
|
66
|
+
title: string;
|
|
67
|
+
/** `"skipped"`: the step ended the test with `test.skip(...)` */
|
|
68
|
+
status: "passed" | "failed" | "skipped";
|
|
69
|
+
/** Milliseconds */
|
|
70
|
+
duration: number;
|
|
71
|
+
/** The message of what the step threw, or that the test ended before it finished */
|
|
72
|
+
error?: string;
|
|
73
|
+
/** The steps started inside this one; absent when there are none */
|
|
74
|
+
steps?: TestStepEntry[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** What `testInfo` and `test.info()` tell about the running test (hooks included) */
|
|
78
|
+
export interface TestInfo {
|
|
79
|
+
/** The test's own title */
|
|
80
|
+
readonly title: string;
|
|
81
|
+
/** The `describe` titles, then the title */
|
|
82
|
+
readonly titlePath: string[];
|
|
83
|
+
/** Absolute path of the spec file */
|
|
84
|
+
readonly file: string;
|
|
85
|
+
/** Where the test is written; absent when unknown */
|
|
86
|
+
readonly line?: number;
|
|
87
|
+
readonly column?: number;
|
|
88
|
+
/** The attempt, counting from 0: 1 is the first run again */
|
|
89
|
+
readonly retry: number;
|
|
90
|
+
readonly workerIndex: number;
|
|
91
|
+
/** The browser this run is in. There is no `projects` matrix yet, so `name` is the browser name */
|
|
92
|
+
readonly project: { name: string };
|
|
93
|
+
/** The run settings that apply to the test */
|
|
94
|
+
readonly config: { baseURL: string | undefined; timeout: number };
|
|
95
|
+
/** Push notes here; they are listed in the report entry */
|
|
96
|
+
annotations: TestAnnotation[];
|
|
97
|
+
/** Undefined while the test body runs; set before the `afterEach` hooks */
|
|
98
|
+
readonly status: "passed" | "failed" | "skipped" | undefined;
|
|
99
|
+
/** What the test is expected to end as: `"failed"` after `test.fail()`, else `"passed"` */
|
|
100
|
+
readonly expectedStatus: "passed" | "failed";
|
|
101
|
+
/** The time limit of the test in milliseconds (0: none); see `setTimeout` */
|
|
102
|
+
readonly timeout: number;
|
|
103
|
+
/** Sets the limit of this test, counted from its start; `0` removes it */
|
|
104
|
+
setTimeout(ms: number): void;
|
|
105
|
+
/** Triples the timeout, once, and adds a `slow` annotation; nothing happens when `condition` is false */
|
|
106
|
+
slow(condition?: boolean, description?: string): void;
|
|
107
|
+
/** Expects the test to fail (`expectedStatus` is `"failed"`) and adds a `fail` annotation */
|
|
108
|
+
fail(condition?: boolean, description?: string): void;
|
|
109
|
+
/** Ends the test as skipped when `condition` is not false */
|
|
110
|
+
skip(condition?: boolean, description?: string): void;
|
|
111
|
+
/** Ends the test as skipped (known to be broken) when `condition` is not false */
|
|
112
|
+
fixme(condition?: boolean, description?: string): void;
|
|
113
|
+
/** Milliseconds since the attempt started; fixed once the body is done */
|
|
114
|
+
readonly duration: number;
|
|
115
|
+
/** The errors of the test body or of its `beforeEach` hooks; filled before the `afterEach` hooks */
|
|
116
|
+
readonly errors: TestError[];
|
|
117
|
+
/** Folder for the files this attempt attaches (created when the first one is saved) */
|
|
118
|
+
readonly outputDir: string;
|
|
119
|
+
readonly attachments: readonly TestAttachment[];
|
|
120
|
+
/** Saves a file in `outputDir` and lists it in the report entry; give `body` or `path`, not both */
|
|
121
|
+
attach(
|
|
122
|
+
name: string,
|
|
123
|
+
options: { body?: string | Buffer; path?: string; contentType?: string },
|
|
124
|
+
): Promise<void>;
|
|
22
125
|
}
|
|
23
126
|
|
|
24
127
|
/** The `outputs` fixture */
|
|
@@ -73,13 +176,25 @@ export interface Suite {
|
|
|
73
176
|
afterEach?: EachHook[];
|
|
74
177
|
beforeAll?: AllHook[];
|
|
75
178
|
afterAll?: AllHook[];
|
|
179
|
+
/** `test.fail(true)` and `test.slow(true)` written in this scope: they apply to every test below */
|
|
180
|
+
marks?: { type: "fail" | "slow"; description?: string }[];
|
|
181
|
+
/** The `test.use()` calls written in this scope, in order; they apply to every test below */
|
|
182
|
+
use?: UseOptions[];
|
|
183
|
+
/** Set by `describe.configure({ mode })`, `describe.serial` or `describe.parallel`; absent: `"default"` */
|
|
184
|
+
mode?: SuiteMode;
|
|
76
185
|
}
|
|
77
186
|
|
|
187
|
+
/** How the tests of a suite run: `"serial"` in order on one worker, the rest stops at the first failure */
|
|
188
|
+
export type SuiteMode = "default" | "parallel" | "serial";
|
|
189
|
+
|
|
78
190
|
export interface RegisteredTestCase extends TestCaseBase {
|
|
79
191
|
/** Path of the spec file that registered the test */
|
|
80
192
|
file: string;
|
|
81
193
|
/** describe titles, then the test title */
|
|
82
194
|
titlePath: string[];
|
|
195
|
+
/** Where the test is written, when known */
|
|
196
|
+
line?: number;
|
|
197
|
+
column?: number;
|
|
83
198
|
/** The `describe` block (or the file) the test is written in */
|
|
84
199
|
suite: Suite;
|
|
85
200
|
/** What applies to the test: its own modifier, or the one of a suite around it */
|
|
@@ -90,6 +205,12 @@ export interface RegisteredTestCase extends TestCaseBase {
|
|
|
90
205
|
inputs?: Record<string, string>;
|
|
91
206
|
/** The names of the outputs the test declared */
|
|
92
207
|
outputs?: string[];
|
|
208
|
+
/** Registered with `test.fail("title", fn)`: expected to fail; `description` is the `{ reason }` given */
|
|
209
|
+
expectedFail?: { description?: string };
|
|
210
|
+
/** The custom fixtures of the `test` it was written with (`test.extend()`); absent for the plain `test` */
|
|
211
|
+
extension?: ReadonlyMap<string, readonly unknown[]>;
|
|
212
|
+
/** The group this run of the test is for, when groups were selected; the same test runs once per selected group */
|
|
213
|
+
group?: string;
|
|
93
214
|
}
|
|
94
215
|
|
|
95
216
|
export interface TestCaseBase {
|
|
@@ -118,6 +239,12 @@ interface TestResultSuccess extends TestResultIdentity, TestRetries {
|
|
|
118
239
|
inputs?: Record<string, string>;
|
|
119
240
|
/** The outputs the test saved; absent when it declares none */
|
|
120
241
|
outputs?: Record<string, string>;
|
|
242
|
+
/** Files the test attached, relative to the result folder; absent when it attached none */
|
|
243
|
+
attachments?: TestAttachmentEntry[];
|
|
244
|
+
/** The notes the test pushed to `testInfo.annotations`; absent when there are none */
|
|
245
|
+
annotations?: TestAnnotation[];
|
|
246
|
+
/** The steps the test ran with `test.step`; absent when it ran none */
|
|
247
|
+
steps?: TestStepEntry[];
|
|
121
248
|
}
|
|
122
249
|
|
|
123
250
|
/** The error sits flat on the entry, as in the report: `message`, `type`, `stack`, and for assertions `expected` and `actual` */
|
|
@@ -132,6 +259,12 @@ interface TestResultFailed extends TestResultIdentity, TestError, TestRetries {
|
|
|
132
259
|
inputs?: Record<string, string>;
|
|
133
260
|
/** The outputs the test saved; absent when it declares none */
|
|
134
261
|
outputs?: Record<string, string>;
|
|
262
|
+
/** Files the test attached, relative to the result folder; absent when it attached none */
|
|
263
|
+
attachments?: TestAttachmentEntry[];
|
|
264
|
+
/** The notes the test pushed to `testInfo.annotations`; absent when there are none */
|
|
265
|
+
annotations?: TestAnnotation[];
|
|
266
|
+
/** The steps the test ran with `test.step`; absent when it ran none */
|
|
267
|
+
steps?: TestStepEntry[];
|
|
135
268
|
}
|
|
136
269
|
|
|
137
270
|
/** What a result says about the retries of its test; absent when it passed or failed on the first attempt */
|
|
@@ -168,6 +301,8 @@ interface TestResultIdentity {
|
|
|
168
301
|
file: string;
|
|
169
302
|
/** The browser the test ran in */
|
|
170
303
|
browser: SupportedBrowser;
|
|
304
|
+
/** The group the test ran for, when the run selected groups (`--group`); absent otherwise */
|
|
305
|
+
group?: string;
|
|
171
306
|
/** The worker process that ran the test; absent when the test ran in the main process */
|
|
172
307
|
worker?: number;
|
|
173
308
|
startTime: number;
|
|
@@ -177,6 +312,7 @@ interface TestResultBase {
|
|
|
177
312
|
name: string;
|
|
178
313
|
file: string;
|
|
179
314
|
browser: SupportedBrowser;
|
|
315
|
+
group?: string;
|
|
180
316
|
worker?: number;
|
|
181
317
|
startTime: number;
|
|
182
318
|
status: "started";
|
|
@@ -211,4 +347,6 @@ interface TestSummaryBase {
|
|
|
211
347
|
environment: string;
|
|
212
348
|
/** The browsers the run used, in the order they ran */
|
|
213
349
|
browsers: SupportedBrowser[];
|
|
350
|
+
/** The slice of the selected tests the run held (`--shard`); absent for a whole run */
|
|
351
|
+
shard?: { current: number; total: number };
|
|
214
352
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the page looks to the site, and what the browser starts with. One vocabulary for every level: config `use`,
|
|
3
|
+
* a group's `use`, `test.use()` (fixed before the test starts) and `page.use()` / `context.use()` (applied now).
|
|
4
|
+
* At run time `null` puts the real value back; in config a later layer's `null` cancels an earlier value.
|
|
5
|
+
*/
|
|
6
|
+
export interface UseOptions {
|
|
7
|
+
/** A BCP 47 tag, e.g. "nl-NL": `navigator.language`, `Intl` formatting and the `Accept-Language` header */
|
|
8
|
+
locale?: string | null;
|
|
9
|
+
/** An IANA timezone, e.g. "Europe/Amsterdam" */
|
|
10
|
+
timezone?: string | null;
|
|
11
|
+
/** The `User-Agent` header and `navigator.userAgent` */
|
|
12
|
+
userAgent?: string | null;
|
|
13
|
+
/** Needs the geolocation permission: see `page.grantPermission("geolocation")` */
|
|
14
|
+
geolocation?: {
|
|
15
|
+
latitude: number;
|
|
16
|
+
longitude: number;
|
|
17
|
+
accuracy?: number;
|
|
18
|
+
} | null;
|
|
19
|
+
/** `true`: no network (`navigator.onLine` is false and requests fail); `false`: back online */
|
|
20
|
+
offline?: boolean | null;
|
|
21
|
+
/** "portrait" and "landscape" mean the primary orientation of that kind */
|
|
22
|
+
orientation?: Orientation | null;
|
|
23
|
+
/** The size `screen.width` and `screen.height` report */
|
|
24
|
+
screen?: { width: number; height: number } | null;
|
|
25
|
+
/** `false` runs pages without JavaScript */
|
|
26
|
+
javaScriptEnabled?: boolean | null;
|
|
27
|
+
/** Touch input: `navigator.maxTouchPoints` is 1. Chrome only. */
|
|
28
|
+
hasTouch?: boolean | null;
|
|
29
|
+
/** Accept certificates that are self-signed, expired or for another host. Fixed when the browser starts. */
|
|
30
|
+
acceptInsecureCerts?: boolean;
|
|
31
|
+
/** A phone-like browser (the layout viewport follows the page's `<meta name="viewport">`). Chrome only. */
|
|
32
|
+
isMobile?: boolean;
|
|
33
|
+
/** Cookies and local storage to start with: a JSON file (relative to the project folder) or the object itself */
|
|
34
|
+
storageState?: string | StorageState;
|
|
35
|
+
/** Headers added to every request of the page */
|
|
36
|
+
extraHTTPHeaders?: Record<string, string>;
|
|
37
|
+
/** What `prefers-color-scheme` reports */
|
|
38
|
+
colorScheme?: ColorScheme;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The options `page.use()` and `context.use()` take: those that can change while the browser runs */
|
|
42
|
+
export type RuntimeUseOptions = Pick<
|
|
43
|
+
UseOptions,
|
|
44
|
+
| "locale"
|
|
45
|
+
| "timezone"
|
|
46
|
+
| "userAgent"
|
|
47
|
+
| "geolocation"
|
|
48
|
+
| "offline"
|
|
49
|
+
| "orientation"
|
|
50
|
+
| "screen"
|
|
51
|
+
| "javaScriptEnabled"
|
|
52
|
+
| "hasTouch"
|
|
53
|
+
>;
|
|
54
|
+
|
|
55
|
+
export type Orientation =
|
|
56
|
+
| "portrait"
|
|
57
|
+
| "landscape"
|
|
58
|
+
| "portrait-primary"
|
|
59
|
+
| "portrait-secondary"
|
|
60
|
+
| "landscape-primary"
|
|
61
|
+
| "landscape-secondary";
|
|
62
|
+
|
|
63
|
+
export type ColorScheme = "light" | "dark" | "no-preference";
|
|
64
|
+
|
|
65
|
+
/** Cookies and local storage, in the format Playwright's `storageState` files use */
|
|
66
|
+
export interface StorageState {
|
|
67
|
+
cookies?: StorageStateCookie[];
|
|
68
|
+
origins?: {
|
|
69
|
+
origin: string;
|
|
70
|
+
localStorage: { name: string; value: string }[];
|
|
71
|
+
}[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface StorageStateCookie {
|
|
75
|
+
name: string;
|
|
76
|
+
value: string;
|
|
77
|
+
domain: string;
|
|
78
|
+
/** @default "/" */
|
|
79
|
+
path?: string;
|
|
80
|
+
/** Seconds since the epoch; -1 or left out is a session cookie */
|
|
81
|
+
expires?: number;
|
|
82
|
+
httpOnly?: boolean;
|
|
83
|
+
secure?: boolean;
|
|
84
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
85
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itmetsam/samurai-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Browser test framework over WebDriver BiDi: auto-waiting locators, environments and secrets, a recorder, and a command line.",
|
|
5
5
|
"main": "dist/api.js",
|
|
6
6
|
"exports": {
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { UnsupportedOperationError } from "../transport/unsupported.js";
|
|
2
|
-
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
3
|
-
declare const ORIENTATIONS: readonly ["portrait-primary", "portrait-secondary", "landscape-primary", "landscape-secondary"];
|
|
4
|
-
type OrientationType = (typeof ORIENTATIONS)[number];
|
|
5
|
-
/**
|
|
6
|
-
* What to pretend about the browser. `undefined` leaves an option as it is, `null` puts the real value back.
|
|
7
|
-
* Which options a browser supports differs: an unsupported one throws UnsupportedOperationError.
|
|
8
|
-
*/
|
|
9
|
-
export interface EmulationOptions {
|
|
10
|
-
/** A BCP 47 tag, e.g. "nl-NL": `navigator.language`, `Intl` formatting */
|
|
11
|
-
locale?: string | null;
|
|
12
|
-
/** An IANA timezone, e.g. "Europe/Amsterdam" */
|
|
13
|
-
timezone?: string | null;
|
|
14
|
-
userAgent?: string | null;
|
|
15
|
-
/** Needs the geolocation permission: see `page.grantPermission("geolocation")` */
|
|
16
|
-
geolocation?: {
|
|
17
|
-
latitude: number;
|
|
18
|
-
longitude: number;
|
|
19
|
-
accuracy?: number;
|
|
20
|
-
} | null;
|
|
21
|
-
/** `true`: no network (`navigator.onLine` is false and requests fail); `false` or `null`: back online */
|
|
22
|
-
offline?: boolean | null;
|
|
23
|
-
/** "portrait" and "landscape" mean the primary orientation of that kind */
|
|
24
|
-
orientation?: "portrait" | "landscape" | OrientationType | null;
|
|
25
|
-
/** The size `screen.width` and `screen.height` report */
|
|
26
|
-
screen?: {
|
|
27
|
-
width: number;
|
|
28
|
-
height: number;
|
|
29
|
-
} | null;
|
|
30
|
-
/** How many touch points `navigator.maxTouchPoints` reports */
|
|
31
|
-
touch?: number | null;
|
|
32
|
-
/** `false` turns JavaScript off; `true` or `null` on again */
|
|
33
|
-
javaScriptEnabled?: boolean | null;
|
|
34
|
-
}
|
|
35
|
-
export type EmulationTarget = {
|
|
36
|
-
contexts: string[];
|
|
37
|
-
} | {
|
|
38
|
-
userContexts: string[];
|
|
39
|
-
};
|
|
40
|
-
/** A part of the options could not be applied; what was applied before it stays applied */
|
|
41
|
-
export declare class EmulationError extends Error {
|
|
42
|
-
/** The options that were applied before the failure, in the order they were applied */
|
|
43
|
-
readonly applied: string[];
|
|
44
|
-
readonly failed: string;
|
|
45
|
-
readonly cause: unknown;
|
|
46
|
-
constructor(failed: string, applied: string[], cause: unknown);
|
|
47
|
-
}
|
|
48
|
-
/** The browser does not implement an emulation command */
|
|
49
|
-
export declare class EmulationUnsupportedError extends UnsupportedOperationError {
|
|
50
|
-
readonly option: string;
|
|
51
|
-
readonly applied: string[];
|
|
52
|
-
constructor(option: string, applied: string[]);
|
|
53
|
-
}
|
|
54
|
-
/** Throws RangeError when a value is invalid, without sending anything */
|
|
55
|
-
export declare function validateEmulation(options: EmulationOptions): void;
|
|
56
|
-
/**
|
|
57
|
-
* Applies the options to `target`, one command after the other. Nothing is rolled back when one fails:
|
|
58
|
-
* the error says what was applied.
|
|
59
|
-
*/
|
|
60
|
-
export declare function applyEmulation(connector: BiDiConnector, target: EmulationTarget, options: EmulationOptions): Promise<void>;
|
|
61
|
-
export {};
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { isUnsupported, UnsupportedOperationError, } from "../transport/unsupported.js";
|
|
2
|
-
const ORIENTATIONS = [
|
|
3
|
-
"portrait-primary",
|
|
4
|
-
"portrait-secondary",
|
|
5
|
-
"landscape-primary",
|
|
6
|
-
"landscape-secondary",
|
|
7
|
-
];
|
|
8
|
-
/** A part of the options could not be applied; what was applied before it stays applied */
|
|
9
|
-
export class EmulationError extends Error {
|
|
10
|
-
constructor(failed, applied, cause) {
|
|
11
|
-
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
12
|
-
super(`emulate(): ${failed} failed${applied.length ? ` after applying ${applied.join(", ")}` : ""}: ${detail}`);
|
|
13
|
-
this.name = "EmulationError";
|
|
14
|
-
this.failed = failed;
|
|
15
|
-
this.applied = applied;
|
|
16
|
-
this.cause = cause;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
/** The browser does not implement an emulation command */
|
|
20
|
-
export class EmulationUnsupportedError extends UnsupportedOperationError {
|
|
21
|
-
constructor(option, applied) {
|
|
22
|
-
super(`emulate(): ${option} is not supported by the browser`);
|
|
23
|
-
this.name = "EmulationUnsupportedError";
|
|
24
|
-
this.option = option;
|
|
25
|
-
this.applied = applied;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const isNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
29
|
-
const isPositiveInteger = (value) => Number.isInteger(value) && value > 0;
|
|
30
|
-
/** Turns the options into commands, in a fixed order; throws RangeError for an invalid value, before anything is sent */
|
|
31
|
-
function plan(options) {
|
|
32
|
-
const steps = [];
|
|
33
|
-
if (options.locale !== undefined) {
|
|
34
|
-
if (options.locale !== null) {
|
|
35
|
-
try {
|
|
36
|
-
if (options.locale === "" ||
|
|
37
|
-
Intl.getCanonicalLocales(options.locale).length !== 1)
|
|
38
|
-
throw new RangeError();
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
throw new RangeError(`emulate(): invalid locale "${options.locale}"`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
steps.push({
|
|
45
|
-
option: "locale",
|
|
46
|
-
method: "emulation.setLocaleOverride",
|
|
47
|
-
params: { locale: options.locale },
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
if (options.timezone !== undefined) {
|
|
51
|
-
if (options.timezone !== null) {
|
|
52
|
-
try {
|
|
53
|
-
new Intl.DateTimeFormat(undefined, { timeZone: options.timezone });
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
throw new RangeError(`emulate(): unknown timezone "${options.timezone}"`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
steps.push({
|
|
60
|
-
option: "timezone",
|
|
61
|
-
method: "emulation.setTimezoneOverride",
|
|
62
|
-
params: { timezone: options.timezone },
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
if (options.userAgent !== undefined) {
|
|
66
|
-
if (options.userAgent === "")
|
|
67
|
-
throw new RangeError("emulate(): userAgent must not be empty");
|
|
68
|
-
steps.push({
|
|
69
|
-
option: "userAgent",
|
|
70
|
-
method: "emulation.setUserAgentOverride",
|
|
71
|
-
params: { userAgent: options.userAgent },
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
if (options.geolocation !== undefined) {
|
|
75
|
-
const place = options.geolocation;
|
|
76
|
-
if (place !== null) {
|
|
77
|
-
if (!isNumber(place.latitude) ||
|
|
78
|
-
place.latitude < -90 ||
|
|
79
|
-
place.latitude > 90) {
|
|
80
|
-
throw new RangeError("emulate(): latitude must be between -90 and 90");
|
|
81
|
-
}
|
|
82
|
-
if (!isNumber(place.longitude) ||
|
|
83
|
-
place.longitude < -180 ||
|
|
84
|
-
place.longitude > 180) {
|
|
85
|
-
throw new RangeError("emulate(): longitude must be between -180 and 180");
|
|
86
|
-
}
|
|
87
|
-
if (place.accuracy !== undefined &&
|
|
88
|
-
(!isNumber(place.accuracy) || place.accuracy < 0)) {
|
|
89
|
-
throw new RangeError("emulate(): accuracy must be 0 or more");
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
steps.push({
|
|
93
|
-
option: "geolocation",
|
|
94
|
-
method: "emulation.setGeolocationOverride",
|
|
95
|
-
params: { coordinates: place },
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
if (options.offline !== undefined) {
|
|
99
|
-
steps.push({
|
|
100
|
-
option: "offline",
|
|
101
|
-
method: "emulation.setNetworkConditions",
|
|
102
|
-
params: {
|
|
103
|
-
networkConditions: options.offline ? { type: "offline" } : null,
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
if (options.orientation !== undefined) {
|
|
108
|
-
let screenOrientation = null;
|
|
109
|
-
if (options.orientation !== null) {
|
|
110
|
-
const type = (options.orientation === "portrait" ||
|
|
111
|
-
options.orientation === "landscape"
|
|
112
|
-
? `${options.orientation}-primary`
|
|
113
|
-
: options.orientation);
|
|
114
|
-
if (!ORIENTATIONS.includes(type))
|
|
115
|
-
throw new RangeError(`emulate(): unknown orientation "${options.orientation}"`);
|
|
116
|
-
screenOrientation = {
|
|
117
|
-
natural: type.startsWith("portrait") ? "portrait" : "landscape",
|
|
118
|
-
type,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
steps.push({
|
|
122
|
-
option: "orientation",
|
|
123
|
-
method: "emulation.setScreenOrientationOverride",
|
|
124
|
-
params: { screenOrientation },
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
if (options.screen !== undefined) {
|
|
128
|
-
const area = options.screen;
|
|
129
|
-
if (area !== null &&
|
|
130
|
-
!(isPositiveInteger(area.width) && isPositiveInteger(area.height))) {
|
|
131
|
-
throw new RangeError("emulate(): screen width and height must be positive integers");
|
|
132
|
-
}
|
|
133
|
-
steps.push({
|
|
134
|
-
option: "screen",
|
|
135
|
-
method: "emulation.setScreenSettingsOverride",
|
|
136
|
-
params: { screenArea: area },
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
if (options.touch !== undefined) {
|
|
140
|
-
if (options.touch !== null && !isPositiveInteger(options.touch)) {
|
|
141
|
-
throw new RangeError("emulate(): touch must be a positive integer");
|
|
142
|
-
}
|
|
143
|
-
steps.push({
|
|
144
|
-
option: "touch",
|
|
145
|
-
method: "emulation.setTouchOverride",
|
|
146
|
-
params: { maxTouchPoints: options.touch },
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
if (options.javaScriptEnabled !== undefined) {
|
|
150
|
-
steps.push({
|
|
151
|
-
option: "javaScriptEnabled",
|
|
152
|
-
method: "emulation.setScriptingEnabled",
|
|
153
|
-
params: { enabled: options.javaScriptEnabled === false ? false : null },
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
return steps;
|
|
157
|
-
}
|
|
158
|
-
/** Throws RangeError when a value is invalid, without sending anything */
|
|
159
|
-
export function validateEmulation(options) {
|
|
160
|
-
plan(options);
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Applies the options to `target`, one command after the other. Nothing is rolled back when one fails:
|
|
164
|
-
* the error says what was applied.
|
|
165
|
-
*/
|
|
166
|
-
export async function applyEmulation(connector, target, options) {
|
|
167
|
-
const applied = [];
|
|
168
|
-
for (const step of plan(options)) {
|
|
169
|
-
try {
|
|
170
|
-
await connector.send(step.method, { ...step.params, ...target });
|
|
171
|
-
}
|
|
172
|
-
catch (err) {
|
|
173
|
-
if (isUnsupported(err)) {
|
|
174
|
-
throw new EmulationUnsupportedError(step.option, applied);
|
|
175
|
-
}
|
|
176
|
-
throw new EmulationError(step.option, applied, err);
|
|
177
|
-
}
|
|
178
|
-
applied.push(step.option);
|
|
179
|
-
}
|
|
180
|
-
}
|