@noego/wood 0.4.8 → 0.4.10
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/bin/axe.js +313 -98
- package/bin/wood.js +7 -1
- package/dist/automation/actions.cjs +17 -0
- package/dist/automation/actions.cjs.map +1 -0
- package/dist/automation/actions.d.cts +57 -0
- package/dist/automation/actions.d.ts +57 -0
- package/dist/automation/actions.js +1 -0
- package/dist/automation/actions.js.map +1 -0
- package/dist/automation/actions_executor.cjs +101 -0
- package/dist/automation/actions_executor.cjs.map +1 -0
- package/dist/automation/actions_executor.d.cts +16 -0
- package/dist/automation/actions_executor.d.ts +16 -0
- package/dist/automation/actions_executor.js +76 -0
- package/dist/automation/actions_executor.js.map +1 -0
- package/dist/automation/connection.cjs +86 -0
- package/dist/automation/connection.cjs.map +1 -0
- package/dist/automation/connection.d.cts +18 -0
- package/dist/automation/connection.d.ts +18 -0
- package/dist/automation/connection.js +51 -0
- package/dist/automation/connection.js.map +1 -0
- package/dist/automation/errors.cjs +50 -0
- package/dist/automation/errors.cjs.map +1 -0
- package/dist/automation/errors.d.cts +31 -0
- package/dist/automation/errors.d.ts +31 -0
- package/dist/automation/errors.js +24 -0
- package/dist/automation/errors.js.map +1 -0
- package/dist/automation/index.cjs +49 -0
- package/dist/automation/index.cjs.map +1 -0
- package/dist/automation/index.d.cts +16 -0
- package/dist/automation/index.d.ts +16 -0
- package/dist/automation/index.js +15 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/introspection_reader.cjs +75 -0
- package/dist/automation/introspection_reader.cjs.map +1 -0
- package/dist/automation/introspection_reader.d.cts +20 -0
- package/dist/automation/introspection_reader.d.ts +20 -0
- package/dist/automation/introspection_reader.js +48 -0
- package/dist/automation/introspection_reader.js.map +1 -0
- package/dist/automation/locator.cjs +102 -0
- package/dist/automation/locator.cjs.map +1 -0
- package/dist/automation/locator.d.cts +51 -0
- package/dist/automation/locator.d.ts +51 -0
- package/dist/automation/locator.js +75 -0
- package/dist/automation/locator.js.map +1 -0
- package/dist/automation/observation.cjs +17 -0
- package/dist/automation/observation.cjs.map +1 -0
- package/dist/automation/observation.d.cts +44 -0
- package/dist/automation/observation.d.ts +44 -0
- package/dist/automation/observation.js +1 -0
- package/dist/automation/observation.js.map +1 -0
- package/dist/automation/receipts.cjs +17 -0
- package/dist/automation/receipts.cjs.map +1 -0
- package/dist/automation/receipts.d.cts +27 -0
- package/dist/automation/receipts.d.ts +27 -0
- package/dist/automation/receipts.js +1 -0
- package/dist/automation/receipts.js.map +1 -0
- package/dist/automation/scenario.cjs +696 -0
- package/dist/automation/scenario.cjs.map +1 -0
- package/dist/automation/scenario.d.cts +89 -0
- package/dist/automation/scenario.d.ts +89 -0
- package/dist/automation/scenario.js +660 -0
- package/dist/automation/scenario.js.map +1 -0
- package/dist/automation/scrolling.cjs +17 -0
- package/dist/automation/scrolling.cjs.map +1 -0
- package/dist/automation/scrolling.d.cts +51 -0
- package/dist/automation/scrolling.d.ts +51 -0
- package/dist/automation/scrolling.js +1 -0
- package/dist/automation/scrolling.js.map +1 -0
- package/dist/automation/scrolling_executor.cjs +109 -0
- package/dist/automation/scrolling_executor.cjs.map +1 -0
- package/dist/automation/scrolling_executor.d.cts +13 -0
- package/dist/automation/scrolling_executor.d.ts +13 -0
- package/dist/automation/scrolling_executor.js +84 -0
- package/dist/automation/scrolling_executor.js.map +1 -0
- package/dist/automation/target_identity.cjs +17 -0
- package/dist/automation/target_identity.cjs.map +1 -0
- package/dist/automation/target_identity.d.cts +28 -0
- package/dist/automation/target_identity.d.ts +28 -0
- package/dist/automation/target_identity.js +1 -0
- package/dist/automation/target_identity.js.map +1 -0
- package/dist/automation/target_registry.cjs +148 -0
- package/dist/automation/target_registry.cjs.map +1 -0
- package/dist/automation/target_registry.d.cts +32 -0
- package/dist/automation/target_registry.d.ts +32 -0
- package/dist/automation/target_registry.js +122 -0
- package/dist/automation/target_registry.js.map +1 -0
- package/dist/automation/waits.cjs +17 -0
- package/dist/automation/waits.cjs.map +1 -0
- package/dist/automation/waits.d.cts +44 -0
- package/dist/automation/waits.d.ts +44 -0
- package/dist/automation/waits.js +1 -0
- package/dist/automation/waits.js.map +1 -0
- package/dist/automation/waits_executor.cjs +104 -0
- package/dist/automation/waits_executor.cjs.map +1 -0
- package/dist/automation/waits_executor.d.cts +14 -0
- package/dist/automation/waits_executor.d.ts +14 -0
- package/dist/automation/waits_executor.js +79 -0
- package/dist/automation/waits_executor.js.map +1 -0
- package/dist/client/index.d.cts +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/codegen/renderer_app_generator.cjs +16 -1
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +16 -1
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/navigation/automation_introspection.cjs +110 -0
- package/dist/navigation/automation_introspection.cjs.map +1 -0
- package/dist/navigation/automation_introspection.d.cts +60 -0
- package/dist/navigation/automation_introspection.d.ts +60 -0
- package/dist/navigation/automation_introspection.js +85 -0
- package/dist/navigation/automation_introspection.js.map +1 -0
- package/dist/navigation/index.cjs +7 -2
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +2 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.js +4 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/testing/automation_assertions.cjs +417 -0
- package/dist/testing/automation_assertions.cjs.map +1 -0
- package/dist/testing/automation_assertions.d.cts +44 -0
- package/dist/testing/automation_assertions.d.ts +44 -0
- package/dist/testing/automation_assertions.js +385 -0
- package/dist/testing/automation_assertions.js.map +1 -0
- package/dist/testing/browser.cjs +26 -34
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +2 -1
- package/dist/testing/browser.d.ts +2 -1
- package/dist/testing/browser.js +30 -31
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +1 -0
- package/dist/testing/create_controller_harness.d.ts +1 -0
- package/dist/testing/geometry.cjs +109 -0
- package/dist/testing/geometry.cjs.map +1 -0
- package/dist/testing/geometry.d.cts +45 -0
- package/dist/testing/geometry.d.ts +45 -0
- package/dist/testing/geometry.js +79 -0
- package/dist/testing/geometry.js.map +1 -0
- package/dist/testing/index.cjs +31 -2
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +4 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +31 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/tracing/trace_hub.cjs +3 -0
- package/dist/tracing/trace_hub.cjs.map +1 -1
- package/dist/tracing/trace_hub.js +3 -0
- package/dist/tracing/trace_hub.js.map +1 -1
- package/package.json +7 -1
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var scenario_exports = {};
|
|
30
|
+
__export(scenario_exports, {
|
|
31
|
+
parseScenario: () => parseScenario,
|
|
32
|
+
runScenario: () => runScenario,
|
|
33
|
+
runScenarioOnPage: () => runScenarioOnPage
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(scenario_exports);
|
|
36
|
+
var import_promises = require("node:fs/promises");
|
|
37
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
38
|
+
var import_actions_executor = require("./actions_executor.js");
|
|
39
|
+
var import_errors = require("./errors.js");
|
|
40
|
+
var import_target_registry = require("./target_registry.js");
|
|
41
|
+
var import_waits_executor = require("./waits_executor.js");
|
|
42
|
+
const EXPECT_TIMEOUT_MS = 2e3;
|
|
43
|
+
const ACTION_KINDS = [
|
|
44
|
+
"click",
|
|
45
|
+
"double_click",
|
|
46
|
+
"fill",
|
|
47
|
+
"type",
|
|
48
|
+
"press",
|
|
49
|
+
"select",
|
|
50
|
+
"toggle",
|
|
51
|
+
"scroll",
|
|
52
|
+
"back",
|
|
53
|
+
"forward",
|
|
54
|
+
"reload",
|
|
55
|
+
"wait"
|
|
56
|
+
];
|
|
57
|
+
const WAIT_KINDS = [
|
|
58
|
+
"route",
|
|
59
|
+
"url",
|
|
60
|
+
"target",
|
|
61
|
+
"visible",
|
|
62
|
+
"hidden",
|
|
63
|
+
"attached",
|
|
64
|
+
"detached",
|
|
65
|
+
"enabled",
|
|
66
|
+
"trace",
|
|
67
|
+
"scroll_edge",
|
|
68
|
+
"visual_stable",
|
|
69
|
+
"app_idle"
|
|
70
|
+
];
|
|
71
|
+
function valid(value) {
|
|
72
|
+
return { ok: true, value };
|
|
73
|
+
}
|
|
74
|
+
function invalid(message) {
|
|
75
|
+
return { ok: false, message };
|
|
76
|
+
}
|
|
77
|
+
function isRecord(value) {
|
|
78
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
+
}
|
|
80
|
+
function requireRecord(value, at) {
|
|
81
|
+
return isRecord(value) ? valid(value) : invalid(`${at} must be an object.`);
|
|
82
|
+
}
|
|
83
|
+
function requireString(value, at) {
|
|
84
|
+
return typeof value === "string" && value.length > 0 ? valid(value) : invalid(`${at} must be a non-empty string.`);
|
|
85
|
+
}
|
|
86
|
+
function optionalBoolean(value, at) {
|
|
87
|
+
return value === void 0 || typeof value === "boolean" ? valid(value) : invalid(`${at} must be a boolean when provided.`);
|
|
88
|
+
}
|
|
89
|
+
function optionalNumber(value, at) {
|
|
90
|
+
return value === void 0 || typeof value === "number" && Number.isFinite(value) ? valid(value) : invalid(`${at} must be a finite number when provided.`);
|
|
91
|
+
}
|
|
92
|
+
function stringRecord(value, at) {
|
|
93
|
+
if (value === void 0) return valid(void 0);
|
|
94
|
+
if (!isRecord(value)) return invalid(`${at} must be an object of string values.`);
|
|
95
|
+
const result = {};
|
|
96
|
+
for (const [key, item] of Object.entries(value)) {
|
|
97
|
+
if (typeof item !== "string") return invalid(`${at}.${key} must be a string.`);
|
|
98
|
+
result[key] = item;
|
|
99
|
+
}
|
|
100
|
+
return valid(result);
|
|
101
|
+
}
|
|
102
|
+
function parseLocator(value, at) {
|
|
103
|
+
const object = requireRecord(value, at);
|
|
104
|
+
if (!object.ok) return object;
|
|
105
|
+
const kind = object.value.kind;
|
|
106
|
+
if (kind === "role") {
|
|
107
|
+
const role = requireString(object.value.role, `${at}.role`);
|
|
108
|
+
if (!role.ok) return role;
|
|
109
|
+
if (object.value.name !== void 0 && typeof object.value.name !== "string") {
|
|
110
|
+
return invalid(`${at}.name must be a string when provided.`);
|
|
111
|
+
}
|
|
112
|
+
const exact2 = optionalBoolean(object.value.exact, `${at}.exact`);
|
|
113
|
+
if (!exact2.ok) return exact2;
|
|
114
|
+
return valid({
|
|
115
|
+
kind,
|
|
116
|
+
role: role.value,
|
|
117
|
+
...object.value.name !== void 0 ? { name: object.value.name } : {},
|
|
118
|
+
...exact2.value !== void 0 ? { exact: exact2.value } : {}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (!["test_id", "label", "placeholder", "text", "css"].includes(String(kind))) {
|
|
122
|
+
return invalid(`${at}.kind must be one of test_id, role, label, placeholder, text, css.`);
|
|
123
|
+
}
|
|
124
|
+
const text = requireString(object.value.value, `${at}.value`);
|
|
125
|
+
if (!text.ok) return text;
|
|
126
|
+
if (kind === "css" || kind === "test_id") return valid({ kind, value: text.value });
|
|
127
|
+
const exact = optionalBoolean(object.value.exact, `${at}.exact`);
|
|
128
|
+
if (!exact.ok) return exact;
|
|
129
|
+
return valid({
|
|
130
|
+
kind,
|
|
131
|
+
value: text.value,
|
|
132
|
+
...exact.value !== void 0 ? { exact: exact.value } : {}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function parseContainer(value, at) {
|
|
136
|
+
const object = requireRecord(value, at);
|
|
137
|
+
if (!object.ok) return object;
|
|
138
|
+
if (object.value.kind === "viewport") return valid({ kind: "viewport" });
|
|
139
|
+
if (object.value.kind !== "locator") {
|
|
140
|
+
return invalid(`${at}.kind must be viewport or locator.`);
|
|
141
|
+
}
|
|
142
|
+
const target = parseLocator(object.value.target, `${at}.target`);
|
|
143
|
+
return target.ok ? valid({ kind: "locator", target: target.value }) : target;
|
|
144
|
+
}
|
|
145
|
+
function parseScroll(value, at) {
|
|
146
|
+
const object = requireRecord(value, at);
|
|
147
|
+
if (!object.ok) return object;
|
|
148
|
+
const mode = object.value.mode;
|
|
149
|
+
if (mode === "into_view") {
|
|
150
|
+
const target2 = parseLocator(object.value.target, `${at}.target`);
|
|
151
|
+
if (!target2.ok) return target2;
|
|
152
|
+
const block = object.value.block;
|
|
153
|
+
if (block !== void 0 && !["start", "center", "end", "nearest"].includes(String(block))) {
|
|
154
|
+
return invalid(`${at}.block must be start, center, end, or nearest.`);
|
|
155
|
+
}
|
|
156
|
+
return valid({
|
|
157
|
+
mode,
|
|
158
|
+
target: target2.value,
|
|
159
|
+
...block !== void 0 ? { block } : {}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (!["wheel", "position", "until"].includes(String(mode))) {
|
|
163
|
+
return invalid(`${at}.mode must be wheel, position, into_view, or until.`);
|
|
164
|
+
}
|
|
165
|
+
const container = parseContainer(object.value.container, `${at}.container`);
|
|
166
|
+
if (!container.ok) return container;
|
|
167
|
+
if (mode === "wheel") {
|
|
168
|
+
const deltaX = optionalNumber(object.value.deltaX, `${at}.deltaX`);
|
|
169
|
+
if (!deltaX.ok) return deltaX;
|
|
170
|
+
const deltaY = optionalNumber(object.value.deltaY, `${at}.deltaY`);
|
|
171
|
+
if (!deltaY.ok) return deltaY;
|
|
172
|
+
return valid({
|
|
173
|
+
mode,
|
|
174
|
+
container: container.value,
|
|
175
|
+
...deltaX.value !== void 0 ? { deltaX: deltaX.value } : {},
|
|
176
|
+
...deltaY.value !== void 0 ? { deltaY: deltaY.value } : {}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (mode === "position") {
|
|
180
|
+
const x = optionalNumber(object.value.x, `${at}.x`);
|
|
181
|
+
if (!x.ok) return x;
|
|
182
|
+
const y = optionalNumber(object.value.y, `${at}.y`);
|
|
183
|
+
if (!y.ok) return y;
|
|
184
|
+
const edge = object.value.edge;
|
|
185
|
+
if (edge !== void 0 && !["top", "bottom", "left", "right"].includes(String(edge))) {
|
|
186
|
+
return invalid(`${at}.edge must be top, bottom, left, or right.`);
|
|
187
|
+
}
|
|
188
|
+
return valid({
|
|
189
|
+
mode,
|
|
190
|
+
container: container.value,
|
|
191
|
+
...x.value !== void 0 ? { x: x.value } : {},
|
|
192
|
+
...y.value !== void 0 ? { y: y.value } : {},
|
|
193
|
+
...edge !== void 0 ? { edge } : {}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const target = parseLocator(object.value.target, `${at}.target`);
|
|
197
|
+
if (!target.ok) return target;
|
|
198
|
+
const direction = object.value.direction;
|
|
199
|
+
if (!["up", "down", "left", "right"].includes(String(direction))) {
|
|
200
|
+
return invalid(`${at}.direction must be up, down, left, or right.`);
|
|
201
|
+
}
|
|
202
|
+
const stepPx = object.value.stepPx;
|
|
203
|
+
if (typeof stepPx !== "number" || !Number.isFinite(stepPx) || stepPx <= 0) {
|
|
204
|
+
return invalid(`${at}.stepPx must be a positive number.`);
|
|
205
|
+
}
|
|
206
|
+
const maxSteps = object.value.maxSteps;
|
|
207
|
+
if (!Number.isInteger(maxSteps) || maxSteps <= 0) {
|
|
208
|
+
return invalid(`${at}.maxSteps must be a positive integer.`);
|
|
209
|
+
}
|
|
210
|
+
return valid({
|
|
211
|
+
mode: "until",
|
|
212
|
+
container: container.value,
|
|
213
|
+
target: target.value,
|
|
214
|
+
direction,
|
|
215
|
+
stepPx,
|
|
216
|
+
maxSteps
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function parseWait(value, at) {
|
|
220
|
+
const object = requireRecord(value, at);
|
|
221
|
+
if (!object.ok) return object;
|
|
222
|
+
const kind = object.value.kind;
|
|
223
|
+
if (!WAIT_KINDS.includes(kind)) {
|
|
224
|
+
return invalid(`${at}.kind must be one of ${WAIT_KINDS.join(", ")}.`);
|
|
225
|
+
}
|
|
226
|
+
if (kind === "route") {
|
|
227
|
+
const page = requireString(object.value.page, `${at}.page`);
|
|
228
|
+
if (!page.ok) return page;
|
|
229
|
+
const params = stringRecord(object.value.params, `${at}.params`);
|
|
230
|
+
if (!params.ok) return params;
|
|
231
|
+
const query = stringRecord(object.value.query, `${at}.query`);
|
|
232
|
+
if (!query.ok) return query;
|
|
233
|
+
return valid({
|
|
234
|
+
kind,
|
|
235
|
+
page: page.value,
|
|
236
|
+
...params.value ? { params: params.value } : {},
|
|
237
|
+
...query.value ? { query: query.value } : {}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (kind === "url") {
|
|
241
|
+
const text = requireString(object.value.value, `${at}.value`);
|
|
242
|
+
if (!text.ok) return text;
|
|
243
|
+
const match = object.value.match;
|
|
244
|
+
if (match !== void 0 && !["exact", "glob", "regex"].includes(String(match))) {
|
|
245
|
+
return invalid(`${at}.match must be exact, glob, or regex.`);
|
|
246
|
+
}
|
|
247
|
+
return valid({
|
|
248
|
+
kind,
|
|
249
|
+
value: text.value,
|
|
250
|
+
...match !== void 0 ? { match } : {}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (kind === "target") {
|
|
254
|
+
const windowId = requireString(object.value.windowId, `${at}.windowId`);
|
|
255
|
+
if (!windowId.ok) return windowId;
|
|
256
|
+
const state = object.value.state;
|
|
257
|
+
if (!["present", "active", "closed"].includes(String(state))) {
|
|
258
|
+
return invalid(`${at}.state must be present, active, or closed.`);
|
|
259
|
+
}
|
|
260
|
+
return valid({ kind, windowId: windowId.value, state });
|
|
261
|
+
}
|
|
262
|
+
if (["visible", "hidden", "attached", "detached", "enabled"].includes(String(kind))) {
|
|
263
|
+
const target = parseLocator(object.value.target, `${at}.target`);
|
|
264
|
+
return target.ok ? valid({ kind, target: target.value }) : target;
|
|
265
|
+
}
|
|
266
|
+
if (kind === "trace") {
|
|
267
|
+
const type = requireString(object.value.type, `${at}.type`);
|
|
268
|
+
if (!type.ok) return type;
|
|
269
|
+
if (object.value.source !== void 0 && typeof object.value.source !== "string") {
|
|
270
|
+
return invalid(`${at}.source must be a string when provided.`);
|
|
271
|
+
}
|
|
272
|
+
if (object.value.count !== void 0 && (!Number.isInteger(object.value.count) || object.value.count < 1)) {
|
|
273
|
+
return invalid(`${at}.count must be a positive integer when provided.`);
|
|
274
|
+
}
|
|
275
|
+
return valid({
|
|
276
|
+
kind,
|
|
277
|
+
type: type.value,
|
|
278
|
+
...object.value.source !== void 0 ? { source: object.value.source } : {},
|
|
279
|
+
...object.value.count !== void 0 ? { count: object.value.count } : {}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
if (kind === "scroll_edge") {
|
|
283
|
+
const container = parseContainer(object.value.container, `${at}.container`);
|
|
284
|
+
if (!container.ok) return container;
|
|
285
|
+
const edge = object.value.edge;
|
|
286
|
+
if (!["top", "bottom", "left", "right"].includes(String(edge))) {
|
|
287
|
+
return invalid(`${at}.edge must be top, bottom, left, or right.`);
|
|
288
|
+
}
|
|
289
|
+
return valid({ kind, container: container.value, edge });
|
|
290
|
+
}
|
|
291
|
+
if (kind === "visual_stable") {
|
|
292
|
+
const target = object.value.target === void 0 ? valid(void 0) : parseLocator(object.value.target, `${at}.target`);
|
|
293
|
+
if (!target.ok) return target;
|
|
294
|
+
if (object.value.frames !== void 0 && (!Number.isInteger(object.value.frames) || object.value.frames < 1)) {
|
|
295
|
+
return invalid(`${at}.frames must be a positive integer when provided.`);
|
|
296
|
+
}
|
|
297
|
+
return valid({
|
|
298
|
+
kind,
|
|
299
|
+
...target.value ? { target: target.value } : {},
|
|
300
|
+
...object.value.frames !== void 0 ? { frames: object.value.frames } : {}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return valid({ kind: "app_idle" });
|
|
304
|
+
}
|
|
305
|
+
function parseAction(value, at) {
|
|
306
|
+
const object = requireRecord(value, at);
|
|
307
|
+
if (!object.ok) return object;
|
|
308
|
+
const kind = object.value.kind;
|
|
309
|
+
if (!ACTION_KINDS.includes(kind)) {
|
|
310
|
+
return invalid(`${at}.kind must be one of ${ACTION_KINDS.join(", ")}.`);
|
|
311
|
+
}
|
|
312
|
+
if (kind === "back" || kind === "forward" || kind === "reload") return valid({ kind });
|
|
313
|
+
if (kind === "scroll") {
|
|
314
|
+
const command = parseScroll(object.value.command, `${at}.command`);
|
|
315
|
+
return command.ok ? valid({ kind, command: command.value }) : command;
|
|
316
|
+
}
|
|
317
|
+
if (kind === "wait") {
|
|
318
|
+
const condition = parseWait(object.value.condition, `${at}.condition`);
|
|
319
|
+
return condition.ok ? valid({ kind, condition: condition.value }) : condition;
|
|
320
|
+
}
|
|
321
|
+
if (kind === "press") {
|
|
322
|
+
let target2 = valid(void 0);
|
|
323
|
+
if (object.value.target !== void 0) target2 = parseLocator(object.value.target, `${at}.target`);
|
|
324
|
+
if (!target2.ok) return target2;
|
|
325
|
+
if (!Array.isArray(object.value.keys) || object.value.keys.length === 0) {
|
|
326
|
+
return invalid(`${at}.keys must be a non-empty array of strings.`);
|
|
327
|
+
}
|
|
328
|
+
for (let index = 0; index < object.value.keys.length; index += 1) {
|
|
329
|
+
if (typeof object.value.keys[index] !== "string" || object.value.keys[index].length === 0) {
|
|
330
|
+
return invalid(`${at}.keys[${index}] must be a non-empty string.`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return valid({
|
|
334
|
+
kind,
|
|
335
|
+
...target2.value ? { target: target2.value } : {},
|
|
336
|
+
keys: object.value.keys
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
const target = parseLocator(object.value.target, `${at}.target`);
|
|
340
|
+
if (!target.ok) return target;
|
|
341
|
+
if (kind === "click") {
|
|
342
|
+
const button = object.value.button;
|
|
343
|
+
if (button !== void 0 && button !== "left" && button !== "middle") {
|
|
344
|
+
return invalid(`${at}.button must be left or middle when provided.`);
|
|
345
|
+
}
|
|
346
|
+
return valid({ kind, target: target.value, ...button ? { button } : {} });
|
|
347
|
+
}
|
|
348
|
+
if (kind === "double_click") return valid({ kind, target: target.value });
|
|
349
|
+
if (kind === "fill" || kind === "type") {
|
|
350
|
+
if (typeof object.value.value !== "string") return invalid(`${at}.value must be a string.`);
|
|
351
|
+
if (kind === "type") {
|
|
352
|
+
const delayMs = optionalNumber(object.value.delayMs, `${at}.delayMs`);
|
|
353
|
+
if (!delayMs.ok) return delayMs;
|
|
354
|
+
return valid({
|
|
355
|
+
kind,
|
|
356
|
+
target: target.value,
|
|
357
|
+
value: object.value.value,
|
|
358
|
+
...delayMs.value !== void 0 ? { delayMs: delayMs.value } : {}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return valid({ kind, target: target.value, value: object.value.value });
|
|
362
|
+
}
|
|
363
|
+
if (kind === "select") {
|
|
364
|
+
if (typeof object.value.option !== "string") return invalid(`${at}.option must be a string.`);
|
|
365
|
+
return valid({ kind, target: target.value, option: object.value.option });
|
|
366
|
+
}
|
|
367
|
+
if (typeof object.value.checked !== "boolean") return invalid(`${at}.checked must be a boolean.`);
|
|
368
|
+
return valid({ kind: "toggle", target: target.value, checked: object.value.checked });
|
|
369
|
+
}
|
|
370
|
+
function parseScenario(json) {
|
|
371
|
+
const root = requireRecord(json, "$");
|
|
372
|
+
if (!root.ok) return (0, import_errors.automationError)("ACTION_FAILED", root.message);
|
|
373
|
+
if (root.value.version !== 1) {
|
|
374
|
+
return (0, import_errors.automationError)("UNSUPPORTED", "$.version must equal 1.");
|
|
375
|
+
}
|
|
376
|
+
if (!Array.isArray(root.value.steps)) {
|
|
377
|
+
return (0, import_errors.automationError)("ACTION_FAILED", "$.steps must be an array.");
|
|
378
|
+
}
|
|
379
|
+
if (root.value.steps.length === 0) {
|
|
380
|
+
return (0, import_errors.automationError)("ACTION_FAILED", "$.steps must contain at least one step.");
|
|
381
|
+
}
|
|
382
|
+
let target;
|
|
383
|
+
if (root.value.target !== void 0) {
|
|
384
|
+
const object = requireRecord(root.value.target, "$.target");
|
|
385
|
+
if (!object.ok) return (0, import_errors.automationError)("ACTION_FAILED", object.message);
|
|
386
|
+
if (object.value.windowId !== void 0 && (typeof object.value.windowId !== "string" || object.value.windowId.length === 0)) {
|
|
387
|
+
return (0, import_errors.automationError)("ACTION_FAILED", "$.target.windowId must be a non-empty string when provided.");
|
|
388
|
+
}
|
|
389
|
+
target = object.value.windowId === void 0 ? {} : { windowId: object.value.windowId };
|
|
390
|
+
}
|
|
391
|
+
let evidence;
|
|
392
|
+
if (root.value.evidence !== void 0) {
|
|
393
|
+
const object = requireRecord(root.value.evidence, "$.evidence");
|
|
394
|
+
if (!object.ok) return (0, import_errors.automationError)("ACTION_FAILED", object.message);
|
|
395
|
+
const screenshot = object.value.screenshot;
|
|
396
|
+
if (screenshot !== void 0 && !["on-failure", "always", "off"].includes(String(screenshot))) {
|
|
397
|
+
return (0, import_errors.automationError)("ACTION_FAILED", "$.evidence.screenshot must be on-failure, always, or off.");
|
|
398
|
+
}
|
|
399
|
+
const consoleCapture = optionalBoolean(object.value.console, "$.evidence.console");
|
|
400
|
+
if (!consoleCapture.ok) return (0, import_errors.automationError)("ACTION_FAILED", consoleCapture.message);
|
|
401
|
+
evidence = {
|
|
402
|
+
...screenshot !== void 0 ? { screenshot } : {},
|
|
403
|
+
...consoleCapture.value !== void 0 ? { console: consoleCapture.value } : {}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const steps = [];
|
|
407
|
+
for (let index = 0; index < root.value.steps.length; index += 1) {
|
|
408
|
+
const at = `$.steps[${index}]`;
|
|
409
|
+
const step = requireRecord(root.value.steps[index], at);
|
|
410
|
+
if (!step.ok) return (0, import_errors.automationError)("ACTION_FAILED", step.message);
|
|
411
|
+
if (step.value.name !== void 0 && (typeof step.value.name !== "string" || step.value.name.length === 0)) {
|
|
412
|
+
return (0, import_errors.automationError)("ACTION_FAILED", `${at}.name must be a non-empty string when provided.`);
|
|
413
|
+
}
|
|
414
|
+
const action = parseAction(step.value.action, `${at}.action`);
|
|
415
|
+
if (!action.ok) return (0, import_errors.automationError)("ACTION_FAILED", action.message);
|
|
416
|
+
const wait = step.value.wait === void 0 ? valid(void 0) : parseWait(step.value.wait, `${at}.wait`);
|
|
417
|
+
if (!wait.ok) return (0, import_errors.automationError)("ACTION_FAILED", wait.message);
|
|
418
|
+
if (step.value.expect !== void 0 && !Array.isArray(step.value.expect)) {
|
|
419
|
+
return (0, import_errors.automationError)("ACTION_FAILED", `${at}.expect must be an array when provided.`);
|
|
420
|
+
}
|
|
421
|
+
const expect = [];
|
|
422
|
+
for (let expectIndex = 0; expectIndex < step.value.expect?.length; expectIndex += 1) {
|
|
423
|
+
const condition = parseWait(
|
|
424
|
+
step.value.expect[expectIndex],
|
|
425
|
+
`${at}.expect[${expectIndex}]`
|
|
426
|
+
);
|
|
427
|
+
if (!condition.ok) return (0, import_errors.automationError)("ACTION_FAILED", condition.message);
|
|
428
|
+
expect.push(condition.value);
|
|
429
|
+
}
|
|
430
|
+
steps.push({
|
|
431
|
+
...step.value.name !== void 0 ? { name: step.value.name } : {},
|
|
432
|
+
action: action.value,
|
|
433
|
+
...wait.value ? { wait: wait.value } : {},
|
|
434
|
+
...step.value.expect !== void 0 ? { expect } : {}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return (0, import_errors.automationOk)({
|
|
438
|
+
version: 1,
|
|
439
|
+
...target ? { target } : {},
|
|
440
|
+
...evidence ? { evidence } : {},
|
|
441
|
+
steps
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function safeEvidenceName(stepIndex, stepName, suffix) {
|
|
445
|
+
const name = (stepName ?? "step").replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "step";
|
|
446
|
+
return `${String(stepIndex + 1).padStart(3, "0")}-${name}-${suffix}.png`;
|
|
447
|
+
}
|
|
448
|
+
async function captureScreenshot(page, hooks, stepIndex, stepName, suffix) {
|
|
449
|
+
if (!hooks.evidenceDir) return void 0;
|
|
450
|
+
await (0, import_promises.mkdir)(hooks.evidenceDir, { recursive: true });
|
|
451
|
+
const outputPath = import_node_path.default.join(hooks.evidenceDir, safeEvidenceName(stepIndex, stepName, suffix));
|
|
452
|
+
await page.screenshot({ path: outputPath });
|
|
453
|
+
return outputPath;
|
|
454
|
+
}
|
|
455
|
+
function attachConsoleCapture(page, entries) {
|
|
456
|
+
const add = (type, message) => {
|
|
457
|
+
entries.push({ type, message, occurredAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
458
|
+
};
|
|
459
|
+
const onConsole = (message) => {
|
|
460
|
+
if (message.type() === "error") add("console_error", message.text());
|
|
461
|
+
};
|
|
462
|
+
const onPageError = (error) => add("page_error", error.message);
|
|
463
|
+
const onRequestFailed = (request) => {
|
|
464
|
+
add("request_failed", `${request.method()} ${request.url()}: ${request.failure()?.errorText ?? "request failed"}`);
|
|
465
|
+
};
|
|
466
|
+
page.on("console", onConsole);
|
|
467
|
+
page.on("pageerror", onPageError);
|
|
468
|
+
page.on("requestfailed", onRequestFailed);
|
|
469
|
+
return () => {
|
|
470
|
+
page.off("console", onConsole);
|
|
471
|
+
page.off("pageerror", onPageError);
|
|
472
|
+
page.off("requestfailed", onRequestFailed);
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function emit(hooks, event) {
|
|
476
|
+
hooks.onEvent(event);
|
|
477
|
+
}
|
|
478
|
+
function classifyThrown(error, message) {
|
|
479
|
+
return {
|
|
480
|
+
code: "ACTION_FAILED",
|
|
481
|
+
message,
|
|
482
|
+
details: { cause: error instanceof Error ? error.message : String(error) }
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
async function runScenarioOnPage(page, scenario, hooks) {
|
|
486
|
+
const started = /* @__PURE__ */ new Date();
|
|
487
|
+
const receipts = [];
|
|
488
|
+
const steps = [];
|
|
489
|
+
const evidence = [];
|
|
490
|
+
const consoleEntries = [];
|
|
491
|
+
const detach = scenario.evidence?.console === false ? () => void 0 : attachConsoleCapture(page, consoleEntries);
|
|
492
|
+
emit(hooks, { kind: "scenario_started", startedAt: started.toISOString(), stepCount: scenario.steps.length });
|
|
493
|
+
try {
|
|
494
|
+
for (let index = 0; index < scenario.steps.length; index += 1) {
|
|
495
|
+
const step = scenario.steps[index];
|
|
496
|
+
const stepStarted = /* @__PURE__ */ new Date();
|
|
497
|
+
const stepEvidence = [];
|
|
498
|
+
let receipt;
|
|
499
|
+
emit(hooks, {
|
|
500
|
+
kind: "step_started",
|
|
501
|
+
stepIndex: index,
|
|
502
|
+
...step.name ? { stepName: step.name } : {},
|
|
503
|
+
startedAt: stepStarted.toISOString()
|
|
504
|
+
});
|
|
505
|
+
let failure;
|
|
506
|
+
const action = await (0, import_actions_executor.executeAction)(page, step.action, { wait: step.wait });
|
|
507
|
+
if (!action.ok) {
|
|
508
|
+
failure = action.error;
|
|
509
|
+
} else {
|
|
510
|
+
receipt = action.value;
|
|
511
|
+
for (let expectIndex = 0; expectIndex < (step.expect?.length ?? 0); expectIndex += 1) {
|
|
512
|
+
const condition = step.expect[expectIndex];
|
|
513
|
+
const result = await (0, import_waits_executor.awaitCondition)(page, condition, { timeoutMs: EXPECT_TIMEOUT_MS });
|
|
514
|
+
if (!result.ok) {
|
|
515
|
+
failure = {
|
|
516
|
+
...result.error,
|
|
517
|
+
message: `Step expectation ${expectIndex + 1} (${condition.kind}) failed: ${result.error.message}`,
|
|
518
|
+
details: {
|
|
519
|
+
...result.error.details,
|
|
520
|
+
condition,
|
|
521
|
+
expectIndex
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (failure) {
|
|
529
|
+
if (scenario.evidence?.screenshot !== "off") {
|
|
530
|
+
try {
|
|
531
|
+
const screenshot = await captureScreenshot(page, hooks, index, step.name, "failure");
|
|
532
|
+
if (screenshot) {
|
|
533
|
+
stepEvidence.push(screenshot);
|
|
534
|
+
evidence.push(screenshot);
|
|
535
|
+
}
|
|
536
|
+
} catch (error) {
|
|
537
|
+
failure = {
|
|
538
|
+
...failure,
|
|
539
|
+
details: {
|
|
540
|
+
...failure.details,
|
|
541
|
+
screenshotError: error instanceof Error ? error.message : String(error)
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const durationMs2 = Date.now() - stepStarted.getTime();
|
|
547
|
+
emit(hooks, {
|
|
548
|
+
kind: "step_failed",
|
|
549
|
+
stepIndex: index,
|
|
550
|
+
...step.name ? { stepName: step.name } : {},
|
|
551
|
+
error: failure,
|
|
552
|
+
durationMs: durationMs2,
|
|
553
|
+
evidence: stepEvidence
|
|
554
|
+
});
|
|
555
|
+
const completed2 = /* @__PURE__ */ new Date();
|
|
556
|
+
emit(hooks, {
|
|
557
|
+
kind: "scenario_completed",
|
|
558
|
+
stepIndex: index,
|
|
559
|
+
...step.name ? { stepName: step.name } : {},
|
|
560
|
+
...receipt ? { receipt } : {},
|
|
561
|
+
error: failure,
|
|
562
|
+
startedAt: started.toISOString(),
|
|
563
|
+
completedAt: completed2.toISOString(),
|
|
564
|
+
durationMs: completed2.getTime() - started.getTime(),
|
|
565
|
+
success: false
|
|
566
|
+
});
|
|
567
|
+
return (0, import_errors.automationError)(failure.code, failure.message, {
|
|
568
|
+
requiredAction: failure.requiredAction,
|
|
569
|
+
details: {
|
|
570
|
+
...failure.details,
|
|
571
|
+
stepIndex: index,
|
|
572
|
+
...step.name ? { stepName: step.name } : {},
|
|
573
|
+
receipts,
|
|
574
|
+
steps,
|
|
575
|
+
evidence,
|
|
576
|
+
console: consoleEntries
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (scenario.evidence?.screenshot === "always") {
|
|
581
|
+
try {
|
|
582
|
+
const screenshot = await captureScreenshot(page, hooks, index, step.name, "completed");
|
|
583
|
+
if (screenshot) {
|
|
584
|
+
stepEvidence.push(screenshot);
|
|
585
|
+
evidence.push(screenshot);
|
|
586
|
+
}
|
|
587
|
+
} catch (error) {
|
|
588
|
+
const screenshotFailure = classifyThrown(error, "Scenario evidence screenshot failed.");
|
|
589
|
+
emit(hooks, {
|
|
590
|
+
kind: "step_failed",
|
|
591
|
+
stepIndex: index,
|
|
592
|
+
...step.name ? { stepName: step.name } : {},
|
|
593
|
+
error: screenshotFailure,
|
|
594
|
+
durationMs: Date.now() - stepStarted.getTime(),
|
|
595
|
+
evidence: stepEvidence
|
|
596
|
+
});
|
|
597
|
+
const completed2 = /* @__PURE__ */ new Date();
|
|
598
|
+
emit(hooks, {
|
|
599
|
+
kind: "scenario_completed",
|
|
600
|
+
stepIndex: index,
|
|
601
|
+
...step.name ? { stepName: step.name } : {},
|
|
602
|
+
receipt,
|
|
603
|
+
error: screenshotFailure,
|
|
604
|
+
startedAt: started.toISOString(),
|
|
605
|
+
completedAt: completed2.toISOString(),
|
|
606
|
+
durationMs: completed2.getTime() - started.getTime(),
|
|
607
|
+
success: false
|
|
608
|
+
});
|
|
609
|
+
return (0, import_errors.automationError)(screenshotFailure.code, screenshotFailure.message, {
|
|
610
|
+
details: {
|
|
611
|
+
...screenshotFailure.details,
|
|
612
|
+
stepIndex: index,
|
|
613
|
+
receipts,
|
|
614
|
+
steps,
|
|
615
|
+
evidence,
|
|
616
|
+
console: consoleEntries
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const durationMs = Date.now() - stepStarted.getTime();
|
|
622
|
+
receipts.push(receipt);
|
|
623
|
+
const stepReport = {
|
|
624
|
+
index,
|
|
625
|
+
...step.name ? { name: step.name } : {},
|
|
626
|
+
startedAt: stepStarted.toISOString(),
|
|
627
|
+
durationMs,
|
|
628
|
+
receipt,
|
|
629
|
+
evidence: stepEvidence
|
|
630
|
+
};
|
|
631
|
+
steps.push(stepReport);
|
|
632
|
+
emit(hooks, {
|
|
633
|
+
kind: "step_completed",
|
|
634
|
+
stepIndex: index,
|
|
635
|
+
...step.name ? { stepName: step.name } : {},
|
|
636
|
+
receipt,
|
|
637
|
+
durationMs,
|
|
638
|
+
evidence: stepEvidence
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
const completed = /* @__PURE__ */ new Date();
|
|
642
|
+
const report = {
|
|
643
|
+
startedAt: started.toISOString(),
|
|
644
|
+
completedAt: completed.toISOString(),
|
|
645
|
+
durationMs: completed.getTime() - started.getTime(),
|
|
646
|
+
success: true,
|
|
647
|
+
receipts,
|
|
648
|
+
steps,
|
|
649
|
+
evidence,
|
|
650
|
+
console: consoleEntries
|
|
651
|
+
};
|
|
652
|
+
const lastStep = scenario.steps.at(-1);
|
|
653
|
+
emit(hooks, {
|
|
654
|
+
kind: "scenario_completed",
|
|
655
|
+
...lastStep ? {
|
|
656
|
+
stepIndex: scenario.steps.length - 1,
|
|
657
|
+
...lastStep.name ? { stepName: lastStep.name } : {},
|
|
658
|
+
receipt: receipts.at(-1)
|
|
659
|
+
} : {},
|
|
660
|
+
startedAt: report.startedAt,
|
|
661
|
+
completedAt: report.completedAt,
|
|
662
|
+
durationMs: report.durationMs,
|
|
663
|
+
success: true
|
|
664
|
+
});
|
|
665
|
+
return (0, import_errors.automationOk)(report);
|
|
666
|
+
} catch (error) {
|
|
667
|
+
const failure = classifyThrown(error, "Scenario runner failed unexpectedly.");
|
|
668
|
+
const completed = /* @__PURE__ */ new Date();
|
|
669
|
+
emit(hooks, {
|
|
670
|
+
kind: "scenario_completed",
|
|
671
|
+
error: failure,
|
|
672
|
+
startedAt: started.toISOString(),
|
|
673
|
+
completedAt: completed.toISOString(),
|
|
674
|
+
durationMs: completed.getTime() - started.getTime(),
|
|
675
|
+
success: false
|
|
676
|
+
});
|
|
677
|
+
return (0, import_errors.automationError)(failure.code, failure.message, {
|
|
678
|
+
details: { ...failure.details, receipts, steps, evidence, console: consoleEntries }
|
|
679
|
+
});
|
|
680
|
+
} finally {
|
|
681
|
+
detach();
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
async function runScenario(connection, scenario, hooks) {
|
|
685
|
+
const entries = await (0, import_target_registry.discoverTargets)(connection.browser);
|
|
686
|
+
const selected = (0, import_target_registry.selectTarget)(entries, scenario.target?.windowId);
|
|
687
|
+
if (!selected.ok) return selected;
|
|
688
|
+
return runScenarioOnPage(selected.value.page, scenario, hooks);
|
|
689
|
+
}
|
|
690
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
691
|
+
0 && (module.exports = {
|
|
692
|
+
parseScenario,
|
|
693
|
+
runScenario,
|
|
694
|
+
runScenarioOnPage
|
|
695
|
+
});
|
|
696
|
+
//# sourceMappingURL=scenario.cjs.map
|