@riddledc/riddle-proof 0.7.57 → 0.7.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/{chunk-H5LDZKGN.js → chunk-Q5VW6MAP.js} +171 -33
- package/dist/cli.cjs +171 -33
- package/dist/cli.js +1 -1
- package/dist/index.cjs +171 -33
- package/dist/index.js +1 -1
- package/dist/profile.cjs +171 -33
- package/dist/profile.d.cts +3 -1
- package/dist/profile.d.ts +3 -1
- package/dist/profile.js +1 -1
- package/dist/proof-run-engine.d.cts +3 -3
- package/dist/proof-run-engine.d.ts +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,7 +240,7 @@ where the second request must carry newer state.
|
|
|
240
240
|
`target.setup_actions` is optional. Use it when the meaningful proof surface
|
|
241
241
|
appears only after a picker, tab, login stub, storage seed, form fill,
|
|
242
242
|
transport control, or other bounded interaction. Supported setup actions are
|
|
243
|
-
`click`, `fill`, `set_input_value`, `assert_text_visible`,
|
|
243
|
+
`click`, `press`, `fill`, `set_input_value`, `assert_text_visible`,
|
|
244
244
|
`assert_text_absent`, `assert_selector_count`, `assert_window_value`,
|
|
245
245
|
`assert_window_number`, `local_storage`, `session_storage`, `clear_storage`, `wait`,
|
|
246
246
|
`wait_for_selector`, `wait_for_text`, and `window_call`; a failed setup action
|
|
@@ -249,8 +249,11 @@ pass without reaching the intended state. Text-matched `click` actions prefer
|
|
|
249
249
|
visible matching elements, which keeps responsive layouts from selecting hidden
|
|
250
250
|
desktop or mobile-only links. Add `force: true` to a click action only when the
|
|
251
251
|
matched visible element is intentionally animated or otherwise never becomes
|
|
252
|
-
stable enough for Playwright's default click actionability checks. Use
|
|
253
|
-
|
|
252
|
+
stable enough for Playwright's default click actionability checks. Use `press`
|
|
253
|
+
with a Playwright key name, such as `Enter`, `Space`, or `ArrowLeft`,
|
|
254
|
+
when a route's intended browser control is keyboard-driven; omit `selector` for
|
|
255
|
+
a page-level key press, or provide `selector` to press against a focused element.
|
|
256
|
+
Use setup assertions when the pre-click or pre-navigation state is part of the contract,
|
|
254
257
|
for example a fresh row must be present, stale copy must be absent, exactly one
|
|
255
258
|
source link must exist before clicking into the final route, or a canvas app's
|
|
256
259
|
proof state must expose a terminal flag. `assert_selector_count` accepts
|
|
@@ -267,6 +270,13 @@ can include `repeat` / `repeat_count` / `times` from 1 to 100; each repetition
|
|
|
267
270
|
is recorded with `repeat_index` and `repeat_count`, and `after_ms` runs after
|
|
268
271
|
each repetition. Use it for bounded game proof helpers, retry controls, or other
|
|
269
272
|
workflows where one declarative action needs to advance the app several times.
|
|
273
|
+
Add `frame_selector` / `frameSelector` to a setup action when the interaction
|
|
274
|
+
target lives inside an embedded iframe, such as a community game player or
|
|
275
|
+
hosted preview surface. Selector-based actions, storage actions, window calls,
|
|
276
|
+
and setup assertions then execute in that frame context and record
|
|
277
|
+
`frame_selector`, `frame_index`, and `frame_count` in setup-action evidence.
|
|
278
|
+
Use `frame_index` / `frameIndex` when more than one matching iframe is present;
|
|
279
|
+
it defaults to the first frame.
|
|
270
280
|
|
|
271
281
|
`target.timeout_sec` is optional. Use it for known-heavy profile targets so the
|
|
272
282
|
profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
|
|
@@ -32,6 +32,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
32
32
|
];
|
|
33
33
|
var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
|
|
34
34
|
"click",
|
|
35
|
+
"press",
|
|
35
36
|
"fill",
|
|
36
37
|
"set_input_value",
|
|
37
38
|
"assert_text_visible",
|
|
@@ -201,7 +202,7 @@ function isSupportedCheckType(value) {
|
|
|
201
202
|
}
|
|
202
203
|
function normalizeSetupActionType(value, index) {
|
|
203
204
|
const normalizedInput = String(value || "").trim().replace(/-/g, "_");
|
|
204
|
-
const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput;
|
|
205
|
+
const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput;
|
|
205
206
|
if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
|
|
206
207
|
return normalized;
|
|
207
208
|
}
|
|
@@ -235,6 +236,11 @@ function normalizeSetupAction(input, index) {
|
|
|
235
236
|
if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
|
|
236
237
|
const type = normalizeSetupActionType(stringValue(input.type), index);
|
|
237
238
|
const selector = stringValue(input.selector);
|
|
239
|
+
const frameSelector = stringFromOwn(input, "frame_selector", "frameSelector", "iframe_selector", "iframeSelector");
|
|
240
|
+
const frameIndex = numberValue(valueFromOwn(input, "frame_index", "frameIndex", "iframe_index", "iframeIndex"));
|
|
241
|
+
if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
|
|
242
|
+
throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
|
|
243
|
+
}
|
|
238
244
|
if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
|
|
239
245
|
throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
|
|
240
246
|
}
|
|
@@ -254,6 +260,9 @@ function normalizeSetupAction(input, index) {
|
|
|
254
260
|
throw new Error(`target.setup_actions[${index}] ${type} requires value.`);
|
|
255
261
|
}
|
|
256
262
|
const key = stringValue(input.key);
|
|
263
|
+
if (type === "press" && !key) {
|
|
264
|
+
throw new Error(`target.setup_actions[${index}] ${type} requires key.`);
|
|
265
|
+
}
|
|
257
266
|
if ((type === "local_storage" || type === "session_storage") && !key) {
|
|
258
267
|
throw new Error(`target.setup_actions[${index}] ${type} requires key.`);
|
|
259
268
|
}
|
|
@@ -284,6 +293,8 @@ function normalizeSetupAction(input, index) {
|
|
|
284
293
|
return {
|
|
285
294
|
type,
|
|
286
295
|
selector,
|
|
296
|
+
frame_selector: frameSelector,
|
|
297
|
+
frame_index: frameIndex,
|
|
287
298
|
force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
|
|
288
299
|
key,
|
|
289
300
|
value,
|
|
@@ -1170,6 +1181,8 @@ function assessSetupActionsFromEvidence(profile, evidence) {
|
|
|
1170
1181
|
viewport: viewport.name,
|
|
1171
1182
|
action: result.action ?? result.type ?? null,
|
|
1172
1183
|
selector: result.selector ?? null,
|
|
1184
|
+
frame_selector: result.frame_selector ?? null,
|
|
1185
|
+
frame_index: result.frame_index ?? null,
|
|
1173
1186
|
reason: result.reason ?? result.error ?? null
|
|
1174
1187
|
});
|
|
1175
1188
|
}
|
|
@@ -1682,10 +1695,12 @@ function assessProfile(profile, evidence) {
|
|
|
1682
1695
|
if (result && result.ok === false) {
|
|
1683
1696
|
failed.push({
|
|
1684
1697
|
viewport: viewport.name,
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1698
|
+
action: result.action || result.type || null,
|
|
1699
|
+
selector: result.selector || null,
|
|
1700
|
+
frame_selector: result.frame_selector || null,
|
|
1701
|
+
frame_index: result.frame_index ?? null,
|
|
1702
|
+
reason: result.reason || result.error || null,
|
|
1703
|
+
});
|
|
1689
1704
|
}
|
|
1690
1705
|
}
|
|
1691
1706
|
if (results.length < actionCount && results.every((result) => !result || result.ok !== false)) {
|
|
@@ -2148,8 +2163,8 @@ function setupJsonValue(value) {
|
|
|
2148
2163
|
function setupValuesEqual(left, right) {
|
|
2149
2164
|
return JSON.stringify(setupJsonValue(left)) === JSON.stringify(setupJsonValue(right));
|
|
2150
2165
|
}
|
|
2151
|
-
async function setupReadWindowValue(path) {
|
|
2152
|
-
return await
|
|
2166
|
+
async function setupReadWindowValue(context, path) {
|
|
2167
|
+
return await context.evaluate(({ path }) => {
|
|
2153
2168
|
const toJsonValue = (value) => {
|
|
2154
2169
|
if (value === null || value === undefined) return null;
|
|
2155
2170
|
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
@@ -2171,6 +2186,80 @@ async function setupReadWindowValue(path) {
|
|
|
2171
2186
|
return { ok: true, value: toJsonValue(current) };
|
|
2172
2187
|
}, { path });
|
|
2173
2188
|
}
|
|
2189
|
+
function setupFrameSelector(action) {
|
|
2190
|
+
return String(action?.frame_selector || action?.frameSelector || action?.iframe_selector || action?.iframeSelector || "").trim();
|
|
2191
|
+
}
|
|
2192
|
+
function setupFrameIndex(action) {
|
|
2193
|
+
const raw = action?.frame_index ?? action?.frameIndex ?? action?.iframe_index ?? action?.iframeIndex ?? 0;
|
|
2194
|
+
const number = Number(raw);
|
|
2195
|
+
return Number.isInteger(number) && number >= 0 ? number : 0;
|
|
2196
|
+
}
|
|
2197
|
+
function setupScopeEvidence(scope) {
|
|
2198
|
+
if (!scope || !scope.frame_selector) return {};
|
|
2199
|
+
return {
|
|
2200
|
+
frame_selector: scope.frame_selector,
|
|
2201
|
+
frame_index: scope.frame_index,
|
|
2202
|
+
frame_count: scope.frame_count,
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
function setupScopeFailure(base, scope) {
|
|
2206
|
+
return {
|
|
2207
|
+
...base,
|
|
2208
|
+
...setupScopeEvidence(scope),
|
|
2209
|
+
reason: scope?.reason || "frame_scope_unavailable",
|
|
2210
|
+
error: scope?.error || undefined,
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
async function setupActionScope(action, timeout) {
|
|
2214
|
+
const frameSelector = setupFrameSelector(action);
|
|
2215
|
+
if (!frameSelector) return { ok: true, context: page };
|
|
2216
|
+
const frameIndex = setupFrameIndex(action);
|
|
2217
|
+
let frameCount = 0;
|
|
2218
|
+
let locator = null;
|
|
2219
|
+
try {
|
|
2220
|
+
await page.waitForSelector(frameSelector, { state: "attached", timeout });
|
|
2221
|
+
locator = page.locator(frameSelector);
|
|
2222
|
+
frameCount = await locator.count();
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
return {
|
|
2225
|
+
ok: false,
|
|
2226
|
+
reason: "frame_selector_not_found",
|
|
2227
|
+
frame_selector: frameSelector,
|
|
2228
|
+
frame_index: frameIndex,
|
|
2229
|
+
frame_count: frameCount,
|
|
2230
|
+
error: String(error && error.message ? error.message : error).slice(0, 1000),
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
if (!frameCount) {
|
|
2234
|
+
return { ok: false, reason: "frame_selector_not_found", frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
|
|
2235
|
+
}
|
|
2236
|
+
if (frameIndex >= frameCount) {
|
|
2237
|
+
return { ok: false, reason: "frame_index_out_of_range", frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
|
|
2238
|
+
}
|
|
2239
|
+
const handle = await locator.nth(frameIndex).elementHandle({ timeout }).catch((error) => ({ __riddle_error: error }));
|
|
2240
|
+
if (!handle || handle.__riddle_error) {
|
|
2241
|
+
return {
|
|
2242
|
+
ok: false,
|
|
2243
|
+
reason: "frame_element_unavailable",
|
|
2244
|
+
frame_selector: frameSelector,
|
|
2245
|
+
frame_index: frameIndex,
|
|
2246
|
+
frame_count: frameCount,
|
|
2247
|
+
error: handle?.__riddle_error ? String(handle.__riddle_error && handle.__riddle_error.message ? handle.__riddle_error.message : handle.__riddle_error).slice(0, 1000) : undefined,
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame().catch((error) => ({ __riddle_error: error })) : null;
|
|
2251
|
+
if (!frame || frame.__riddle_error) {
|
|
2252
|
+
return {
|
|
2253
|
+
ok: false,
|
|
2254
|
+
reason: "content_frame_unavailable",
|
|
2255
|
+
frame_selector: frameSelector,
|
|
2256
|
+
frame_index: frameIndex,
|
|
2257
|
+
frame_count: frameCount,
|
|
2258
|
+
error: frame?.__riddle_error ? String(frame.__riddle_error && frame.__riddle_error.message ? frame.__riddle_error.message : frame.__riddle_error).slice(0, 1000) : undefined,
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
return { ok: true, context: frame, frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
|
|
2262
|
+
}
|
|
2174
2263
|
async function setupLocatorText(locator, index) {
|
|
2175
2264
|
return await locator.nth(index).textContent({ timeout: 1000 }).catch(() => "");
|
|
2176
2265
|
}
|
|
@@ -2332,7 +2421,8 @@ async function registerNetworkMocks(mocks) {
|
|
|
2332
2421
|
}
|
|
2333
2422
|
async function executeSetupAction(action, ordinal) {
|
|
2334
2423
|
const type = setupActionType(action);
|
|
2335
|
-
const
|
|
2424
|
+
const frameSelector = setupFrameSelector(action);
|
|
2425
|
+
const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null, frame_selector: frameSelector || null };
|
|
2336
2426
|
const timeout = setupNumber(action.timeout_ms, 5000);
|
|
2337
2427
|
try {
|
|
2338
2428
|
if (type === "wait") {
|
|
@@ -2341,36 +2431,65 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2341
2431
|
return { ...base, ok: true, ms };
|
|
2342
2432
|
}
|
|
2343
2433
|
if (type === "wait_for_selector") {
|
|
2344
|
-
await
|
|
2345
|
-
return
|
|
2434
|
+
const scope = await setupActionScope(action, timeout);
|
|
2435
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2436
|
+
await scope.context.waitForSelector(action.selector, { state: "visible", timeout });
|
|
2437
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, timeout_ms: timeout };
|
|
2438
|
+
}
|
|
2439
|
+
if (type === "press") {
|
|
2440
|
+
const key = String(action.key || "").trim();
|
|
2441
|
+
if (!key) return { ...base, reason: "missing_key" };
|
|
2442
|
+
const scope = await setupActionScope(action, timeout);
|
|
2443
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2444
|
+
if (!action.selector) {
|
|
2445
|
+
if (scope.frame_selector) {
|
|
2446
|
+
await scope.context.locator("body").press(key, { timeout });
|
|
2447
|
+
} else {
|
|
2448
|
+
await page.keyboard.press(key);
|
|
2449
|
+
}
|
|
2450
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, key };
|
|
2451
|
+
}
|
|
2452
|
+
const locator = scope.context.locator(action.selector);
|
|
2453
|
+
const count = await locator.count();
|
|
2454
|
+
if (!count) return { ...base, reason: "selector_not_found", count, key };
|
|
2455
|
+
const targetIndex = Number.isInteger(action.index) ? action.index : 0;
|
|
2456
|
+
if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex, key };
|
|
2457
|
+
await locator.nth(targetIndex).press(key, { timeout });
|
|
2458
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, key };
|
|
2346
2459
|
}
|
|
2347
2460
|
if (type === "local_storage" || type === "session_storage") {
|
|
2348
2461
|
const value = setupActionValue(action);
|
|
2349
|
-
await
|
|
2462
|
+
const scope = await setupActionScope(action, timeout);
|
|
2463
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2464
|
+
await scope.context.evaluate(({ type, key, value }) => {
|
|
2350
2465
|
const storage = type === "session_storage" ? window.sessionStorage : window.localStorage;
|
|
2351
2466
|
storage.setItem(key, value);
|
|
2352
2467
|
}, { type, key: action.key, value });
|
|
2353
2468
|
if (action.reload === true) {
|
|
2354
2469
|
await page.reload({ waitUntil: "domcontentloaded", timeout: 45000 });
|
|
2355
2470
|
}
|
|
2356
|
-
return { ...base, ok: true, key: action.key, value_length: value.length, reload: action.reload === true };
|
|
2471
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, key: action.key, value_length: value.length, reload: action.reload === true };
|
|
2357
2472
|
}
|
|
2358
2473
|
if (type === "clear_storage") {
|
|
2359
2474
|
const storage = action.storage || "both";
|
|
2360
|
-
await
|
|
2475
|
+
const scope = await setupActionScope(action, timeout);
|
|
2476
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2477
|
+
await scope.context.evaluate(({ storage }) => {
|
|
2361
2478
|
if (storage === "local" || storage === "both") window.localStorage.clear();
|
|
2362
2479
|
if (storage === "session" || storage === "both") window.sessionStorage.clear();
|
|
2363
2480
|
}, { storage });
|
|
2364
2481
|
if (action.reload === true) {
|
|
2365
2482
|
await page.reload({ waitUntil: "domcontentloaded", timeout: 45000 });
|
|
2366
2483
|
}
|
|
2367
|
-
return { ...base, ok: true, storage, reload: action.reload === true };
|
|
2484
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, storage, reload: action.reload === true };
|
|
2368
2485
|
}
|
|
2369
2486
|
if (type === "window_call") {
|
|
2370
2487
|
const path = String(action.path || action.function_path || action.functionPath || "");
|
|
2371
2488
|
const args = Array.isArray(action.args) ? action.args : [];
|
|
2372
2489
|
if (!path) return { ...base, path, reason: "missing_path" };
|
|
2373
|
-
const
|
|
2490
|
+
const scope = await setupActionScope(action, timeout);
|
|
2491
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2492
|
+
const result = await scope.context.evaluate(async ({ path, args }) => {
|
|
2374
2493
|
const toJsonValue = (value) => {
|
|
2375
2494
|
if (value === null || value === undefined) return null;
|
|
2376
2495
|
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
@@ -2410,6 +2529,7 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2410
2529
|
const expectationMet = !hasExpectation || setupValuesEqual(result.returned, expected);
|
|
2411
2530
|
return {
|
|
2412
2531
|
...base,
|
|
2532
|
+
...setupScopeEvidence(scope),
|
|
2413
2533
|
ok: Boolean(result.ok && expectationMet),
|
|
2414
2534
|
path,
|
|
2415
2535
|
arg_count: args.length,
|
|
@@ -2440,13 +2560,16 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2440
2560
|
: action.expect;
|
|
2441
2561
|
if (!path) return { ...base, path, reason: "missing_path" };
|
|
2442
2562
|
if (!hasExpected) return { ...base, path, reason: "missing_expected_value" };
|
|
2563
|
+
const scope = await setupActionScope(action, timeout);
|
|
2564
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2443
2565
|
const startedAt = Date.now();
|
|
2444
2566
|
let result = null;
|
|
2445
2567
|
while (Date.now() - startedAt <= timeout) {
|
|
2446
|
-
result = await setupReadWindowValue(path);
|
|
2568
|
+
result = await setupReadWindowValue(scope.context, path);
|
|
2447
2569
|
if (result.ok && setupValuesEqual(result.value, expected)) {
|
|
2448
2570
|
return {
|
|
2449
2571
|
...base,
|
|
2572
|
+
...setupScopeEvidence(scope),
|
|
2450
2573
|
ok: true,
|
|
2451
2574
|
path,
|
|
2452
2575
|
value: setupJsonValue(result.value),
|
|
@@ -2458,6 +2581,7 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2458
2581
|
}
|
|
2459
2582
|
return {
|
|
2460
2583
|
...base,
|
|
2584
|
+
...setupScopeEvidence(scope),
|
|
2461
2585
|
path,
|
|
2462
2586
|
reason: result?.ok ? "unexpected_value" : result?.reason || "path_not_found",
|
|
2463
2587
|
missing_part: result?.missing_part || undefined,
|
|
@@ -2474,11 +2598,13 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2474
2598
|
const hasExpected = expected !== undefined;
|
|
2475
2599
|
if (!path) return { ...base, path, reason: "missing_path" };
|
|
2476
2600
|
if (!hasExpected && minValue === undefined && maxValue === undefined) return { ...base, path, reason: "missing_number_expectation" };
|
|
2601
|
+
const scope = await setupActionScope(action, timeout);
|
|
2602
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2477
2603
|
const startedAt = Date.now();
|
|
2478
2604
|
let result = null;
|
|
2479
2605
|
let lastReason = "path_not_found";
|
|
2480
2606
|
while (Date.now() - startedAt <= timeout) {
|
|
2481
|
-
result = await setupReadWindowValue(path);
|
|
2607
|
+
result = await setupReadWindowValue(scope.context, path);
|
|
2482
2608
|
if (result.ok) {
|
|
2483
2609
|
const actual = setupFiniteNumber(result.value);
|
|
2484
2610
|
if (actual === undefined) {
|
|
@@ -2492,6 +2618,7 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2492
2618
|
} else {
|
|
2493
2619
|
return {
|
|
2494
2620
|
...base,
|
|
2621
|
+
...setupScopeEvidence(scope),
|
|
2495
2622
|
ok: true,
|
|
2496
2623
|
path,
|
|
2497
2624
|
value: actual,
|
|
@@ -2508,6 +2635,7 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2508
2635
|
}
|
|
2509
2636
|
return {
|
|
2510
2637
|
...base,
|
|
2638
|
+
...setupScopeEvidence(scope),
|
|
2511
2639
|
path,
|
|
2512
2640
|
reason: lastReason,
|
|
2513
2641
|
missing_part: result?.missing_part || undefined,
|
|
@@ -2519,7 +2647,9 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2519
2647
|
};
|
|
2520
2648
|
}
|
|
2521
2649
|
if (type === "click") {
|
|
2522
|
-
const
|
|
2650
|
+
const scope = await setupActionScope(action, timeout);
|
|
2651
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2652
|
+
const locator = scope.context.locator(action.selector);
|
|
2523
2653
|
const count = await locator.count();
|
|
2524
2654
|
if (!count) return { ...base, reason: "selector_not_found", count };
|
|
2525
2655
|
let targetIndex = Number.isInteger(action.index) ? action.index : 0;
|
|
@@ -2551,33 +2681,39 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2551
2681
|
? { timeout, noWaitAfter: true, force: true }
|
|
2552
2682
|
: { timeout, noWaitAfter: true };
|
|
2553
2683
|
await locator.nth(targetIndex).click(clickOptions);
|
|
2554
|
-
return { ...base, ok: true, count, target_index: targetIndex, text: matchedText, force: action.force === true || undefined };
|
|
2684
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, text: matchedText, force: action.force === true || undefined };
|
|
2555
2685
|
}
|
|
2556
2686
|
if (type === "fill" || type === "set_input_value") {
|
|
2557
|
-
const
|
|
2687
|
+
const scope = await setupActionScope(action, timeout);
|
|
2688
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2689
|
+
const locator = scope.context.locator(action.selector);
|
|
2558
2690
|
const count = await locator.count();
|
|
2559
2691
|
if (!count) return { ...base, reason: "selector_not_found", count };
|
|
2560
2692
|
const targetIndex = Number.isInteger(action.index) ? action.index : 0;
|
|
2561
2693
|
if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex };
|
|
2562
2694
|
const value = setupActionValue(action);
|
|
2563
2695
|
await locator.nth(targetIndex).fill(value, { timeout });
|
|
2564
|
-
return { ...base, ok: true, count, target_index: targetIndex, value_length: value.length };
|
|
2696
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, value_length: value.length };
|
|
2565
2697
|
}
|
|
2566
2698
|
if (type === "assert_selector_count") {
|
|
2567
|
-
const
|
|
2699
|
+
const scope = await setupActionScope(action, timeout);
|
|
2700
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2701
|
+
const locator = scope.context.locator(action.selector);
|
|
2568
2702
|
const expectedCount = setupNumber(action.expected_count, -1);
|
|
2569
2703
|
if (!Number.isInteger(expectedCount) || expectedCount < 0) return { ...base, reason: "invalid_expected_count", expected_count: action.expected_count };
|
|
2570
2704
|
const startedAt = Date.now();
|
|
2571
2705
|
let count = 0;
|
|
2572
2706
|
while (Date.now() - startedAt <= timeout) {
|
|
2573
2707
|
count = await locator.count().catch(() => 0);
|
|
2574
|
-
if (count === expectedCount) return { ...base, ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
|
|
2708
|
+
if (count === expectedCount) return { ...base, ...setupScopeEvidence(scope), ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
|
|
2575
2709
|
await page.waitForTimeout(100);
|
|
2576
2710
|
}
|
|
2577
|
-
return { ...base, reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
|
|
2711
|
+
return { ...base, ...setupScopeEvidence(scope), reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
|
|
2578
2712
|
}
|
|
2579
2713
|
if (type === "wait_for_text") {
|
|
2580
|
-
const
|
|
2714
|
+
const scope = await setupActionScope(action, timeout);
|
|
2715
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2716
|
+
const locator = scope.context.locator(action.selector);
|
|
2581
2717
|
const startedAt = Date.now();
|
|
2582
2718
|
let lastText = "";
|
|
2583
2719
|
while (Date.now() - startedAt <= timeout) {
|
|
@@ -2586,15 +2722,17 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2586
2722
|
const text = await setupLocatorText(locator, index);
|
|
2587
2723
|
lastText = text || lastText;
|
|
2588
2724
|
if (setupTextMatches(text, action)) {
|
|
2589
|
-
return { ...base, ok: true, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
|
|
2725
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
|
|
2590
2726
|
}
|
|
2591
2727
|
}
|
|
2592
2728
|
await page.waitForTimeout(100);
|
|
2593
2729
|
}
|
|
2594
|
-
return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
|
|
2730
|
+
return { ...base, ...setupScopeEvidence(scope), reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
|
|
2595
2731
|
}
|
|
2596
2732
|
if (type === "assert_text_visible" || type === "assert_text_absent") {
|
|
2597
|
-
const
|
|
2733
|
+
const scope = await setupActionScope(action, timeout);
|
|
2734
|
+
if (!scope.ok) return setupScopeFailure(base, scope);
|
|
2735
|
+
const locator = scope.context.locator(action.selector);
|
|
2598
2736
|
const startedAt = Date.now();
|
|
2599
2737
|
let lastText = "";
|
|
2600
2738
|
let matchedText = "";
|
|
@@ -2613,7 +2751,7 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2613
2751
|
if (type === "assert_text_visible") {
|
|
2614
2752
|
const visible = await setupLocatorVisible(locator, index);
|
|
2615
2753
|
if (visible) {
|
|
2616
|
-
return { ...base, ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
|
|
2754
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
|
|
2617
2755
|
}
|
|
2618
2756
|
hiddenMatch = true;
|
|
2619
2757
|
break;
|
|
@@ -2622,17 +2760,17 @@ async function executeSetupAction(action, ordinal) {
|
|
|
2622
2760
|
}
|
|
2623
2761
|
}
|
|
2624
2762
|
if (type === "assert_text_absent" && !matched) {
|
|
2625
|
-
return { ...base, ok: true, count, timeout_ms: timeout };
|
|
2763
|
+
return { ...base, ...setupScopeEvidence(scope), ok: true, count, timeout_ms: timeout };
|
|
2626
2764
|
}
|
|
2627
2765
|
await page.waitForTimeout(100);
|
|
2628
2766
|
}
|
|
2629
2767
|
if (type === "assert_text_visible") {
|
|
2630
2768
|
if (hiddenMatch) {
|
|
2631
|
-
return { ...base, reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
|
|
2769
|
+
return { ...base, ...setupScopeEvidence(scope), reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
|
|
2632
2770
|
}
|
|
2633
|
-
return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
|
|
2771
|
+
return { ...base, ...setupScopeEvidence(scope), reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
|
|
2634
2772
|
}
|
|
2635
|
-
return { ...base, reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
|
|
2773
|
+
return { ...base, ...setupScopeEvidence(scope), reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
|
|
2636
2774
|
}
|
|
2637
2775
|
return { ...base, reason: "unsupported_action" };
|
|
2638
2776
|
} catch (error) {
|