@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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { failedResult, serialSkipped, UnitState } from "./pool-units.js";
|
|
2
|
+
import { eligibleCount, nextEligible } from "./pool-schedule.js";
|
|
1
3
|
/** How long a busy worker may take to wind down after an abort before it is killed */
|
|
2
4
|
const GRACE_MS = 2000;
|
|
3
5
|
const SHUTDOWN_MS = 60000;
|
|
@@ -15,6 +17,7 @@ export class WorkerPool {
|
|
|
15
17
|
this.slots = new Map();
|
|
16
18
|
this.calls = new Set();
|
|
17
19
|
this.timers = new Set();
|
|
20
|
+
this.units = new Map();
|
|
18
21
|
this.nextId = 0;
|
|
19
22
|
/** Workers in a row that exited before they were ready */
|
|
20
23
|
this.startupFailures = 0;
|
|
@@ -95,51 +98,23 @@ export class WorkerPool {
|
|
|
95
98
|
if (this.disposed)
|
|
96
99
|
return;
|
|
97
100
|
for (const slot of [...this.slots.values()]) {
|
|
98
|
-
if (!slot.ready || slot.busy)
|
|
101
|
+
if (!slot.ready || slot.busy || slot.retiring)
|
|
99
102
|
continue;
|
|
100
|
-
const at = this.
|
|
103
|
+
const at = nextEligible(this.queue, [...this.slots.values()], slot);
|
|
101
104
|
if (at < 0)
|
|
102
|
-
|
|
105
|
+
continue;
|
|
103
106
|
const [entry] = this.queue.splice(at, 1);
|
|
104
107
|
if (entry)
|
|
105
108
|
this.dispatch(slot, entry);
|
|
106
109
|
}
|
|
107
110
|
let starting = [...this.slots.values()].filter((s) => !s.ready).length;
|
|
108
|
-
const wanted = this.
|
|
111
|
+
const wanted = eligibleCount(this.queue, [...this.slots.values()]);
|
|
109
112
|
while (this.slots.size < this.options.workers && starting < wanted) {
|
|
110
113
|
if (!this.start(this.freeIndex()))
|
|
111
114
|
return;
|
|
112
115
|
starting++;
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
|
-
busyKeys() {
|
|
116
|
-
const keys = new Set();
|
|
117
|
-
for (const { busy } of this.slots.values()) {
|
|
118
|
-
const key = busy?.entry.item.serialKey;
|
|
119
|
-
if (key)
|
|
120
|
-
keys.add(key);
|
|
121
|
-
}
|
|
122
|
-
return keys;
|
|
123
|
-
}
|
|
124
|
-
/** The queue position of the first item whose serial key is not running */
|
|
125
|
-
nextEligible() {
|
|
126
|
-
const busy = this.busyKeys();
|
|
127
|
-
return this.queue.findIndex(({ item }) => !item.serialKey || !busy.has(item.serialKey));
|
|
128
|
-
}
|
|
129
|
-
/** How many queued items could run at the same time right now */
|
|
130
|
-
eligibleCount() {
|
|
131
|
-
const taken = this.busyKeys();
|
|
132
|
-
let count = 0;
|
|
133
|
-
for (const { item } of this.queue) {
|
|
134
|
-
if (item.serialKey) {
|
|
135
|
-
if (taken.has(item.serialKey))
|
|
136
|
-
continue;
|
|
137
|
-
taken.add(item.serialKey);
|
|
138
|
-
}
|
|
139
|
-
count++;
|
|
140
|
-
}
|
|
141
|
-
return count;
|
|
142
|
-
}
|
|
143
118
|
freeIndex() {
|
|
144
119
|
let index = 0;
|
|
145
120
|
while (this.slots.has(index))
|
|
@@ -165,6 +140,13 @@ export class WorkerPool {
|
|
|
165
140
|
}
|
|
166
141
|
dispatch(slot, entry) {
|
|
167
142
|
const id = this.nextId++;
|
|
143
|
+
const { unit, serialKey } = entry.item;
|
|
144
|
+
if (unit) {
|
|
145
|
+
slot.unit = unit;
|
|
146
|
+
slot.unitKey = serialKey;
|
|
147
|
+
if (!this.units.has(unit))
|
|
148
|
+
this.units.set(unit, new UnitState(entry.call));
|
|
149
|
+
}
|
|
168
150
|
slot.busy = { id, entry, startTime: Date.now() };
|
|
169
151
|
slot.handle.send({ type: "run", id, item: entry.item });
|
|
170
152
|
}
|
|
@@ -187,7 +169,7 @@ export class WorkerPool {
|
|
|
187
169
|
type: "test-start",
|
|
188
170
|
item: busy.entry.item,
|
|
189
171
|
worker: slot.index,
|
|
190
|
-
...(
|
|
172
|
+
...this.retryOf(busy.entry, message.retry),
|
|
191
173
|
});
|
|
192
174
|
case "test-end":
|
|
193
175
|
if (busy?.id !== message.id)
|
|
@@ -209,23 +191,84 @@ export class WorkerPool {
|
|
|
209
191
|
const message = call.aborted
|
|
210
192
|
? "Run aborted"
|
|
211
193
|
: `The worker process exited (code ${code})`;
|
|
212
|
-
this.finish(slot, busy.entry,
|
|
194
|
+
this.finish(slot, busy.entry, failedResult(item, message, busy.startTime));
|
|
213
195
|
}
|
|
214
196
|
else {
|
|
215
197
|
this.pump();
|
|
216
198
|
}
|
|
217
199
|
}
|
|
200
|
+
/** The attempt a started item is on: the worker's own retry, or the attempt at its serial block */
|
|
201
|
+
retryOf({ item }, workerRetry) {
|
|
202
|
+
const retry = workerRetry || (item.serial ? this.units.get(item.unit)?.attempt : 0);
|
|
203
|
+
return retry ? { retry } : {};
|
|
204
|
+
}
|
|
218
205
|
/** Records an item's result, tells the listener, and gives the worker its next item */
|
|
219
206
|
finish(slot, entry, result) {
|
|
220
207
|
const stamped = { ...result, worker: slot.index };
|
|
208
|
+
if (entry.item.serial)
|
|
209
|
+
this.finishInBlock(slot, entry, stamped);
|
|
210
|
+
else
|
|
211
|
+
this.report(entry, slot.index, stamped);
|
|
212
|
+
this.pump();
|
|
213
|
+
}
|
|
214
|
+
report(entry, worker, result) {
|
|
221
215
|
this.emit(entry.call, {
|
|
222
216
|
type: "test-end",
|
|
223
217
|
item: entry.item,
|
|
224
|
-
worker
|
|
225
|
-
result
|
|
218
|
+
worker,
|
|
219
|
+
result,
|
|
226
220
|
});
|
|
227
|
-
this.settle(entry,
|
|
228
|
-
|
|
221
|
+
this.settle(entry, result);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* An item of a serial block ended. After a failure the rest of the block is skipped; with retries the block
|
|
225
|
+
* starts over on a new worker first, and what its tests ended with is held until the last attempt is known.
|
|
226
|
+
*/
|
|
227
|
+
finishInBlock(slot, entry, result) {
|
|
228
|
+
const state = this.units.get(entry.item.unit);
|
|
229
|
+
const retries = this.options.init.retries ?? 0;
|
|
230
|
+
if (retries === 0)
|
|
231
|
+
this.report(entry, slot.index, result);
|
|
232
|
+
else
|
|
233
|
+
state.hold(entry, result);
|
|
234
|
+
if (result.status !== "failed") {
|
|
235
|
+
const last = !this.queue.some((e) => e.item.unit === entry.item.unit);
|
|
236
|
+
if (last)
|
|
237
|
+
this.release(state);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const aborted = entry.call.aborted || result.message === "Run aborted";
|
|
241
|
+
if (state.attempt < retries && !aborted && !this.disposed) {
|
|
242
|
+
return this.restartBlock(slot, state, entry, result);
|
|
243
|
+
}
|
|
244
|
+
this.release(state);
|
|
245
|
+
for (let i = 0; i < this.queue.length; i++) {
|
|
246
|
+
const next = this.queue[i];
|
|
247
|
+
if (next.item.unit !== entry.item.unit)
|
|
248
|
+
continue;
|
|
249
|
+
this.queue.splice(i--, 1);
|
|
250
|
+
this.report(next, slot.index, serialSkipped(next.item));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** Reports what a block's tests ended with in its last attempt */
|
|
254
|
+
release(state) {
|
|
255
|
+
for (const { entry, result } of state.release())
|
|
256
|
+
this.report(entry, result.worker ?? 0, result);
|
|
257
|
+
}
|
|
258
|
+
/** Puts the block back in the queue, from its first test, and tells the worker that ran it to leave */
|
|
259
|
+
restartBlock(slot, state, failed, result) {
|
|
260
|
+
this.queue.unshift(...state.restart(failed, result));
|
|
261
|
+
slot.unit = slot.unitKey = undefined;
|
|
262
|
+
if (this.slots.get(slot.index) !== slot)
|
|
263
|
+
return;
|
|
264
|
+
slot.retiring = true;
|
|
265
|
+
slot.handle.send({ type: "exit" });
|
|
266
|
+
const timer = setTimeout(() => {
|
|
267
|
+
this.timers.delete(timer);
|
|
268
|
+
slot.handle.kill();
|
|
269
|
+
}, this.options.shutdownMs ?? SHUTDOWN_MS);
|
|
270
|
+
timer.unref();
|
|
271
|
+
this.timers.add(timer);
|
|
229
272
|
}
|
|
230
273
|
settle({ call, at }, result) {
|
|
231
274
|
call.results[at] = result;
|
|
@@ -246,12 +289,15 @@ export class WorkerPool {
|
|
|
246
289
|
/** Fails the queued items of a call, asks its busy workers to stop, and kills the ones that do not */
|
|
247
290
|
abort(call) {
|
|
248
291
|
call.aborted = true;
|
|
292
|
+
for (const state of this.units.values())
|
|
293
|
+
if (state.call === call)
|
|
294
|
+
this.release(state);
|
|
249
295
|
for (let i = this.queue.length - 1; i >= 0; i--) {
|
|
250
296
|
const entry = this.queue[i];
|
|
251
297
|
if (entry?.call !== call)
|
|
252
298
|
continue;
|
|
253
299
|
this.queue.splice(i, 1);
|
|
254
|
-
this.settle(entry,
|
|
300
|
+
this.settle(entry, failedResult(entry.item, "Run aborted"));
|
|
255
301
|
}
|
|
256
302
|
for (const slot of this.slots.values()) {
|
|
257
303
|
const busy = slot.busy;
|
|
@@ -294,15 +340,3 @@ export class WorkerPool {
|
|
|
294
340
|
}
|
|
295
341
|
}
|
|
296
342
|
}
|
|
297
|
-
function failure(item, message, startTime = Date.now()) {
|
|
298
|
-
return {
|
|
299
|
-
name: item.testName,
|
|
300
|
-
file: item.file,
|
|
301
|
-
browser: item.browser,
|
|
302
|
-
status: "failed",
|
|
303
|
-
type: "error",
|
|
304
|
-
message,
|
|
305
|
-
startTime,
|
|
306
|
-
duration: Date.now() - startTime,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
@@ -9,8 +9,14 @@ export interface WorkItem {
|
|
|
9
9
|
/** The test's full name */
|
|
10
10
|
testName: string;
|
|
11
11
|
browser: SupportedBrowser;
|
|
12
|
+
/** The group this run of the test is for (`--group`): its `use` applies and the result names it */
|
|
13
|
+
group?: string;
|
|
12
14
|
/** Items with the same key never run at the same time */
|
|
13
15
|
serialKey?: string;
|
|
16
|
+
/** Items with the same unit run one after the other on the same worker, in the order they are queued */
|
|
17
|
+
unit?: string;
|
|
18
|
+
/** The unit is a serial block: a failure skips the rest, and `retries` run the whole block again (the pool does it, not the worker) */
|
|
19
|
+
serial?: boolean;
|
|
14
20
|
/** Where this test's screenshots go, instead of the run's `outputDir` (a flow gives each node its own) */
|
|
15
21
|
outputDir?: string;
|
|
16
22
|
/** The values the run supplies for the inputs tests declare (`runTests({ inputs })`) */
|
|
@@ -21,6 +27,8 @@ export interface WorkerInit {
|
|
|
21
27
|
projectDir: string;
|
|
22
28
|
dataDir?: string;
|
|
23
29
|
environment?: string;
|
|
30
|
+
/** The run's base URL when no config sets one (it came from a web server): used where the config has none */
|
|
31
|
+
baseURL?: string;
|
|
24
32
|
timeout?: number;
|
|
25
33
|
expectTimeout?: number;
|
|
26
34
|
headless?: boolean;
|
|
@@ -3,7 +3,8 @@ import logger from "../logger/index.js";
|
|
|
3
3
|
import { withProject } from "./project.js";
|
|
4
4
|
import TestReporter from "./reporter.js";
|
|
5
5
|
import { AllHooks } from "./all-hooks.js";
|
|
6
|
-
import { allHookFixtures
|
|
6
|
+
import { allHookFixtures } from "./fixtures.js";
|
|
7
|
+
import { runOneTest } from "./run-test.js";
|
|
7
8
|
import { runWithRetries } from "./retry.js";
|
|
8
9
|
import { loadSpecs } from "./select.js";
|
|
9
10
|
/**
|
|
@@ -28,6 +29,8 @@ export function openSession(init) {
|
|
|
28
29
|
}),
|
|
29
30
|
...(headless !== undefined && { headless }),
|
|
30
31
|
}, async (run) => {
|
|
32
|
+
if (run.settings.baseURL === undefined && init.baseURL !== undefined)
|
|
33
|
+
run.settings.baseURL = init.baseURL;
|
|
31
34
|
const loaded = await loadSpecs(init.files);
|
|
32
35
|
// The `beforeAll` hooks this worker ran: their `afterAll` hooks run when it closes
|
|
33
36
|
const allHooks = new AllHooks({
|
|
@@ -55,7 +58,7 @@ export function openSession(init) {
|
|
|
55
58
|
port,
|
|
56
59
|
signal,
|
|
57
60
|
};
|
|
58
|
-
await runWithRetries(test, browser, init.retries ?? 0, signal, (sink, attempt) => runOneTest(test, browser, run, options, loaded.srcDir, sink, {
|
|
61
|
+
await runWithRetries(test, browser, perTest.retries ?? init.retries ?? 0, signal, (sink, attempt) => runOneTest(test, browser, run, options, loaded.srcDir, sink, {
|
|
59
62
|
port,
|
|
60
63
|
profileDir: workerProfileDir(browser, workerIndex),
|
|
61
64
|
workerIndex,
|
package/dist/runner/worker.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface WorkerSession {
|
|
|
9
9
|
runTest(test: RegisteredTestCase, browser: SupportedBrowser, signal: AbortSignal, onEvent: (event: RunEvent) => void, options?: {
|
|
10
10
|
outputDir?: string;
|
|
11
11
|
inputs?: Record<string, string>;
|
|
12
|
+
/** Instead of the run's `retries` */
|
|
13
|
+
retries?: number;
|
|
12
14
|
}): Promise<void>;
|
|
13
15
|
/** Ends the project's session */
|
|
14
16
|
close(): Promise<void>;
|
package/dist/runner/worker.js
CHANGED
|
@@ -3,6 +3,7 @@ const failedResult = (item, worker, message) => ({
|
|
|
3
3
|
name: item.testName,
|
|
4
4
|
file: item.file,
|
|
5
5
|
browser: item.browser,
|
|
6
|
+
...(item.group !== undefined && { group: item.group }),
|
|
6
7
|
worker,
|
|
7
8
|
status: "failed",
|
|
8
9
|
startTime: performance.now(),
|
|
@@ -29,7 +30,10 @@ export function handleMessages(channel, { open, exit }) {
|
|
|
29
30
|
if (!session)
|
|
30
31
|
throw new Error("The worker was sent a test before it was initialised");
|
|
31
32
|
channel.send({ type: "test-start", id });
|
|
32
|
-
const
|
|
33
|
+
const found = session.tests.find(({ file, name }) => file === item.file && name === item.testName);
|
|
34
|
+
const test = found && item.group !== undefined
|
|
35
|
+
? { ...found, group: item.group }
|
|
36
|
+
: found;
|
|
33
37
|
if (!test) {
|
|
34
38
|
const message = `Test "${item.testName}" was not found in ${item.file}`;
|
|
35
39
|
channel.send({
|
|
@@ -57,6 +61,8 @@ export function handleMessages(channel, { open, exit }) {
|
|
|
57
61
|
}, {
|
|
58
62
|
...(item.outputDir !== undefined && { outputDir: item.outputDir }),
|
|
59
63
|
...(item.inputs !== undefined && { inputs: item.inputs }),
|
|
64
|
+
// The pool runs a whole serial block again, not the test
|
|
65
|
+
...(item.serial && { retries: 0 }),
|
|
60
66
|
});
|
|
61
67
|
}
|
|
62
68
|
finally {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A reference to a DOM node in the browser. The `sharedId` stays valid across scripts in the
|
|
3
3
|
* same document; after navigation or removal the browser answers `no such node`.
|
|
4
|
-
*
|
|
4
|
+
* What `evaluate` returns for a DOM node; user code keeps using `Locator` to act on elements.
|
|
5
5
|
*/
|
|
6
6
|
export declare class ElementHandle {
|
|
7
7
|
readonly sharedId: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A reference to a DOM node in the browser. The `sharedId` stays valid across scripts in the
|
|
3
3
|
* same document; after navigation or removal the browser answers `no such node`.
|
|
4
|
-
*
|
|
4
|
+
* What `evaluate` returns for a DOM node; user code keeps using `Locator` to act on elements.
|
|
5
5
|
*/
|
|
6
6
|
export class ElementHandle {
|
|
7
7
|
constructor(sharedId) {
|
package/dist/types/config.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { StepDefinition } from "../flows/steps/define.js";
|
|
2
2
|
import type { SupportedBrowser } from "./browser.js";
|
|
3
|
+
import type { UseOptions } from "./use.js";
|
|
4
|
+
import type { ResolvedConfig } from "../config/resolved-config.js";
|
|
3
5
|
|
|
4
6
|
interface SamuraiTest {
|
|
5
7
|
/** Path relative to srcDir, e.g. 'checkout/coupons.spec.ts' */
|
|
@@ -32,8 +34,67 @@ export interface SamuraiGroup {
|
|
|
32
34
|
* nothing but such a group uses one worker.
|
|
33
35
|
*/
|
|
34
36
|
workers?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Options for this group's tests, over the config's `use` and under the `test.use()` of a spec: the same options
|
|
39
|
+
* as config `use` that make sense per group (`locale`, `isMobile`, `viewport`, ...). They apply when the group is
|
|
40
|
+
* selected (`--group`); a run without `--group` ignores them. The way to port a Playwright project.
|
|
41
|
+
*/
|
|
42
|
+
use?: GroupUse;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The options of a group's `use`: those of `test.use()`, and the page's `viewport` */
|
|
46
|
+
export interface GroupUse extends UseOptions {
|
|
47
|
+
/** Size of the page's viewport, instead of config `use.viewport`. `null` keeps the browser's own size */
|
|
48
|
+
viewport?: { width: number; height: number } | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** One server the run starts before the first test (see `SamuraiTestConfig.webServer`). Give either `url` or `port`. */
|
|
52
|
+
export interface WebServerConfig {
|
|
53
|
+
/** Shell command that starts the server. It leads its own process group, so the whole tree is stopped at the end */
|
|
54
|
+
command: string;
|
|
55
|
+
/** Where the server answers: ready once it responds with a status from 200 to 403 */
|
|
56
|
+
url?: string;
|
|
57
|
+
/** Where the server listens: ready once `localhost` accepts TCP connections on it */
|
|
58
|
+
port?: number;
|
|
59
|
+
/** Working directory of the command, relative to the project folder. @default the project folder */
|
|
60
|
+
cwd?: string;
|
|
61
|
+
/** Extra environment variables for the command, on top of this process's. Never printed in errors */
|
|
62
|
+
env?: Record<string, string>;
|
|
63
|
+
/** Time (ms) the server may take to be ready. @default 60000 */
|
|
64
|
+
timeout?: number;
|
|
65
|
+
/** When `url`/`port` already answers, use that server and start nothing (it is not stopped either). @default false */
|
|
66
|
+
reuseExistingServer?: boolean;
|
|
67
|
+
/** Send the command's stdout to this process's stdout, or drop it. @default "ignore" */
|
|
68
|
+
stdout?: "pipe" | "ignore";
|
|
69
|
+
/** Send the command's stderr to this process's stderr, or drop it. @default "pipe" */
|
|
70
|
+
stderr?: "pipe" | "ignore";
|
|
71
|
+
/** Do not fail on a certificate error while waiting for an `https` `url`. @default false */
|
|
72
|
+
ignoreHTTPSErrors?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* How the server is stopped: `signal` goes to the whole process group, and after `timeout` ms what is left is
|
|
75
|
+
* killed. @default { signal: "SIGTERM", timeout: 5000 }
|
|
76
|
+
*/
|
|
77
|
+
gracefulShutdown?: { signal?: "SIGTERM" | "SIGINT"; timeout?: number };
|
|
35
78
|
}
|
|
36
79
|
|
|
80
|
+
/** What `globalSetup` and `globalTeardown` receive: the project's resolved config, with the run's environment and base URL */
|
|
81
|
+
export type GlobalHookConfig = ResolvedConfig & {
|
|
82
|
+
/** The environment the run uses */
|
|
83
|
+
environment: string;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** A run-level hook; `globalSetup` may return a function that runs as teardown too */
|
|
87
|
+
export type GlobalSetupFunction = (
|
|
88
|
+
config: GlobalHookConfig,
|
|
89
|
+
) =>
|
|
90
|
+
| void
|
|
91
|
+
| (() => void | Promise<void>)
|
|
92
|
+
| Promise<void | (() => void | Promise<void>)>;
|
|
93
|
+
|
|
94
|
+
export type GlobalTeardownFunction = (
|
|
95
|
+
config: GlobalHookConfig,
|
|
96
|
+
) => void | Promise<void>;
|
|
97
|
+
|
|
37
98
|
export interface SamuraiEnvironment {
|
|
38
99
|
/** Where page.goto('/…') resolves to in this environment, and where recordings start */
|
|
39
100
|
baseURL?: string;
|
|
@@ -64,6 +125,14 @@ export interface SamuraiTestConfig {
|
|
|
64
125
|
* @default half the CPUs
|
|
65
126
|
*/
|
|
66
127
|
workers?: number | string;
|
|
128
|
+
/**
|
|
129
|
+
* Whether the tests of one file may run on different workers. Not set: they do, one test at a time goes to the
|
|
130
|
+
* next free worker (the same as `true`). `false` keeps the tests of a file together, in order, on one worker;
|
|
131
|
+
* `describe.configure({ mode: 'parallel' })` frees a part of it again. A serial block (`describe.serial`) is
|
|
132
|
+
* never split, whatever this says.
|
|
133
|
+
* @default not set
|
|
134
|
+
*/
|
|
135
|
+
fullyParallel?: boolean;
|
|
67
136
|
/**
|
|
68
137
|
* How many times a failing test is run again, each time with a fresh browser. A test that passes on a retry is
|
|
69
138
|
* reported as flaky and does not fail the run; a run that is aborted is never retried. `--retries` and
|
|
@@ -129,8 +198,8 @@ export interface SamuraiTestConfig {
|
|
|
129
198
|
* @default "files"
|
|
130
199
|
*/
|
|
131
200
|
filesDir?: string;
|
|
132
|
-
/** How pages are opened */
|
|
133
|
-
use?: {
|
|
201
|
+
/** How pages are opened, and what the browser pretends: the options of `test.use()` apply to every test */
|
|
202
|
+
use?: UseOptions & {
|
|
134
203
|
/**
|
|
135
204
|
* Size of the page's viewport, so screenshots and layout are the same on every machine.
|
|
136
205
|
* `null` keeps the browser's own size
|
|
@@ -209,6 +278,21 @@ export interface SamuraiTestConfig {
|
|
|
209
278
|
*/
|
|
210
279
|
maxBodySize?: number;
|
|
211
280
|
};
|
|
281
|
+
/**
|
|
282
|
+
* Servers the run starts before the first test and stops after the last. Each waits until its `url` answers or
|
|
283
|
+
* its `port` accepts connections. When no `baseURL` is configured (project or environment), the first server's
|
|
284
|
+
* `url` origin (or `http://localhost:<port>`) becomes the base URL. Only a run (`samurai run`, `runTests`)
|
|
285
|
+
* starts them, not `list` or the recorder.
|
|
286
|
+
*/
|
|
287
|
+
webServer?: WebServerConfig | WebServerConfig[];
|
|
288
|
+
/**
|
|
289
|
+
* Module (path relative to the project folder) whose default export `async (config) => void | teardown` runs once
|
|
290
|
+
* before the tests, after the web servers are up; or the function itself. Variables it sets in `process.env`
|
|
291
|
+
* reach the tests, workers included. A function it returns runs as teardown.
|
|
292
|
+
*/
|
|
293
|
+
globalSetup?: string | GlobalSetupFunction;
|
|
294
|
+
/** Module (path relative to the project folder) whose default export runs once after the tests; or the function itself */
|
|
295
|
+
globalTeardown?: string | GlobalTeardownFunction;
|
|
212
296
|
/** Keyed by name: 'dev', 'staging', 'production' */
|
|
213
297
|
environments?: Record<string, SamuraiEnvironment>;
|
|
214
298
|
/** Used when no environment is chosen with --env or SAMURAI_ENV */
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TestFixtures } from "./test.js";
|
|
2
|
+
|
|
3
|
+
/** What a test sees when it is built with `test.extend()`: the built-in fixtures, a custom one of the same name replacing it */
|
|
4
|
+
export type FixturesOf<X> = Omit<TestFixtures, keyof X> & X;
|
|
5
|
+
|
|
6
|
+
/** `B` on top of `A`: a key in both takes the type from `B` */
|
|
7
|
+
export type MergeFixtures<A, B> = Omit<A, keyof B> & B;
|
|
8
|
+
|
|
9
|
+
/** The options of a fixture written as `[fixture, options]` */
|
|
10
|
+
export interface FixtureOptions {
|
|
11
|
+
/** Set the fixture up for every test, whether or not the test asks for it */
|
|
12
|
+
auto?: boolean;
|
|
13
|
+
/** Fixtures live as long as one test; worker-scoped fixtures are not supported */
|
|
14
|
+
scope?: "test";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A value, or a function that sets the value up, calls `use(value)` and cleans up once `use` returns */
|
|
18
|
+
export type FixtureValue<R, Args> =
|
|
19
|
+
| Exclude<R, (...args: never[]) => unknown>
|
|
20
|
+
| ((args: Args, use: (value: R) => Promise<void>) => Promise<void>);
|
|
21
|
+
|
|
22
|
+
/** What `test.extend()` takes: each fixture alone, or as `[fixture, options]` */
|
|
23
|
+
export type FixtureDefinitions<T, Existing> = {
|
|
24
|
+
[K in keyof T]:
|
|
25
|
+
| FixtureValue<T[K], FixturesOf<MergeFixtures<Existing, T>>>
|
|
26
|
+
| [
|
|
27
|
+
FixtureValue<T[K], FixturesOf<MergeFixtures<Existing, T>>>,
|
|
28
|
+
FixtureOptions,
|
|
29
|
+
];
|
|
30
|
+
};
|