@riddledc/riddle-proof 0.7.43 → 0.7.45
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 -0
- package/dist/{chunk-ZB5DSQF2.js → chunk-IJGMTTVP.js} +152 -25
- package/dist/cli.cjs +152 -25
- package/dist/cli.js +1 -1
- package/dist/index.cjs +152 -25
- package/dist/index.js +1 -1
- package/dist/profile.cjs +152 -25
- package/dist/profile.d.cts +4 -0
- package/dist/profile.d.ts +4 -0
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,6 +148,7 @@ or as a stronger proof base before a change loop.
|
|
|
148
148
|
{ "type": "selector_absent", "selector": "[data-testid='loading-spinner']" },
|
|
149
149
|
{ "type": "selector_count_equals", "selector": "[data-testid='pricing-card']", "expected_count": 3 },
|
|
150
150
|
{ "type": "text_visible", "text": "Start building" },
|
|
151
|
+
{ "type": "text_visible", "text": "Compare plans", "viewports": ["desktop"] },
|
|
151
152
|
{ "type": "no_mobile_horizontal_overflow" },
|
|
152
153
|
{ "type": "no_fatal_console_errors" }
|
|
153
154
|
],
|
|
@@ -179,6 +180,11 @@ The package includes a generic starter profile at
|
|
|
179
180
|
profile directory and replace the selector/text checks with app-specific
|
|
180
181
|
invariants.
|
|
181
182
|
|
|
183
|
+
Checks normally apply to every captured viewport. Add `viewports` (or
|
|
184
|
+
`viewport_names`) to a check when responsive UI intentionally exposes an
|
|
185
|
+
invariant only on named viewports, such as desktop-only helper copy while phone
|
|
186
|
+
layouts keep the same route, link, and overflow contracts.
|
|
187
|
+
|
|
182
188
|
`target.network_mocks` is optional. The Riddle runner registers these mocks
|
|
183
189
|
before navigation, records each hit, and adds an implicit
|
|
184
190
|
`network_mocks_succeeded` check when mocks are present. A mock supports
|
|
@@ -217,6 +223,13 @@ cycle instead of reusing the final response, for example to repeat a fail-then-
|
|
|
217
223
|
success pair across multiple viewports. Repeated sequences also record
|
|
218
224
|
`sequence_cycle: true` after the first cycle.
|
|
219
225
|
|
|
226
|
+
Set `capture_request_body: true` to include compact request-body evidence on
|
|
227
|
+
mock hits. Add `request_body_contains` or `request_body_patterns` when the
|
|
228
|
+
request body is part of the contract, such as proving that a save request
|
|
229
|
+
references the build ID returned by a prior mocked build response. Body
|
|
230
|
+
assertions use the full request body for matching and store only length plus a
|
|
231
|
+
compact sample in the proof evidence.
|
|
232
|
+
|
|
220
233
|
`target.setup_actions` is optional. Use it when the meaningful proof surface
|
|
221
234
|
appears only after a picker, tab, login stub, storage seed, form fill,
|
|
222
235
|
transport control, or other bounded interaction. Supported setup actions are
|
|
@@ -253,6 +253,15 @@ function normalizeNetworkMock(input, index) {
|
|
|
253
253
|
const payload = normalizeNetworkMockResponsePayload(input, `target.network_mocks[${index}]`);
|
|
254
254
|
const responsesInput = input.responses ?? input.sequence;
|
|
255
255
|
const responses = normalizeNetworkMockResponses(responsesInput, index, payload);
|
|
256
|
+
const requestBodyContains = normalizeStringList(
|
|
257
|
+
input.request_body_contains ?? input.requestBodyContains ?? input.body_contains ?? input.bodyContains,
|
|
258
|
+
`target.network_mocks[${index}].request_body_contains`
|
|
259
|
+
);
|
|
260
|
+
const requestBodyPatterns = normalizeStringList(
|
|
261
|
+
input.request_body_patterns ?? input.requestBodyPatterns ?? input.body_patterns ?? input.bodyPatterns,
|
|
262
|
+
`target.network_mocks[${index}].request_body_patterns`
|
|
263
|
+
);
|
|
264
|
+
validateRegexPatterns(requestBodyPatterns, `target.network_mocks[${index}].request_body_patterns`);
|
|
256
265
|
const requiredHitCount = numberValue(
|
|
257
266
|
input.required_hit_count ?? input.requiredHitCount ?? input.required_hits ?? input.requiredHits ?? input.min_hits ?? input.minHits
|
|
258
267
|
);
|
|
@@ -267,7 +276,10 @@ function normalizeNetworkMock(input, index) {
|
|
|
267
276
|
responses,
|
|
268
277
|
repeat_responses: input.repeat_responses === true || input.repeatResponses === true || input.cycle_responses === true || input.cycleResponses === true,
|
|
269
278
|
required_hit_count: requiredHitCount,
|
|
270
|
-
required: input.required === false ? false : true
|
|
279
|
+
required: input.required === false ? false : true,
|
|
280
|
+
capture_request_body: input.capture_request_body === true || input.captureRequestBody === true || Boolean(requestBodyContains?.length) || Boolean(requestBodyPatterns?.length),
|
|
281
|
+
request_body_contains: requestBodyContains,
|
|
282
|
+
request_body_patterns: requestBodyPatterns
|
|
271
283
|
};
|
|
272
284
|
}
|
|
273
285
|
function normalizeNetworkMockResponsePayload(input, label, defaults = {}) {
|
|
@@ -350,6 +362,16 @@ function normalizeStringList(value, label) {
|
|
|
350
362
|
if (!values.length) throw new Error(`${label} must contain non-empty strings.`);
|
|
351
363
|
return values;
|
|
352
364
|
}
|
|
365
|
+
function validateRegexPatterns(patterns, label) {
|
|
366
|
+
for (const pattern of patterns || []) {
|
|
367
|
+
try {
|
|
368
|
+
new RegExp(pattern);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
371
|
+
throw new Error(`${label} contains invalid regex ${JSON.stringify(pattern)}: ${message}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
353
375
|
function normalizeCheck(input, index) {
|
|
354
376
|
if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
|
|
355
377
|
const type = stringValue(input.type);
|
|
@@ -402,6 +424,7 @@ function normalizeCheck(input, index) {
|
|
|
402
424
|
text: stringValue(input.text),
|
|
403
425
|
pattern: stringValue(input.pattern),
|
|
404
426
|
flags: stringValue(input.flags),
|
|
427
|
+
viewports: normalizeStringList(input.viewports ?? input.viewport_names ?? input.viewportNames, `checks[${index}] viewports`),
|
|
405
428
|
allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
|
|
406
429
|
allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
|
|
407
430
|
allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
|
|
@@ -640,15 +663,22 @@ function successfulRoute(route, targetUrl) {
|
|
|
640
663
|
const matched = route.matched || routePathMatches(route.observed, route.expected_path, targetUrl);
|
|
641
664
|
return matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
|
|
642
665
|
}
|
|
666
|
+
function viewportsForCheck(check, viewports) {
|
|
667
|
+
if (!check.viewports?.length) return viewports;
|
|
668
|
+
const names = new Set(check.viewports);
|
|
669
|
+
return viewports.filter((viewport) => names.has(viewport.name));
|
|
670
|
+
}
|
|
643
671
|
function assessCheckFromEvidence(check, evidence) {
|
|
644
|
-
const viewports = evidence.viewports || [];
|
|
672
|
+
const viewports = viewportsForCheck(check, evidence.viewports || []);
|
|
645
673
|
if (!viewports.length) {
|
|
646
674
|
return {
|
|
647
675
|
type: check.type,
|
|
648
676
|
label: checkLabel(check),
|
|
649
677
|
status: "failed",
|
|
650
|
-
evidence: {
|
|
651
|
-
|
|
678
|
+
evidence: {
|
|
679
|
+
expected_viewports: check.viewports || []
|
|
680
|
+
},
|
|
681
|
+
message: check.viewports?.length ? `No matching viewport evidence was captured for ${check.viewports.join(", ")}.` : "No viewport evidence was captured."
|
|
652
682
|
};
|
|
653
683
|
}
|
|
654
684
|
if (check.type === "route_loaded") {
|
|
@@ -1012,6 +1042,17 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
|
|
|
1012
1042
|
reason: event.reason ?? event.error ?? "mock_failed"
|
|
1013
1043
|
});
|
|
1014
1044
|
}
|
|
1045
|
+
if (event.request_body_matches === false) {
|
|
1046
|
+
failed.push({
|
|
1047
|
+
label: event.label ?? null,
|
|
1048
|
+
url: event.url ?? null,
|
|
1049
|
+
method: event.method ?? null,
|
|
1050
|
+
reason: "request_body_mismatch",
|
|
1051
|
+
request_body_failures: event.request_body_failures ?? [],
|
|
1052
|
+
request_body_length: event.request_body_length ?? null,
|
|
1053
|
+
request_body_sample: event.request_body_sample ?? null
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1015
1056
|
}
|
|
1016
1057
|
return {
|
|
1017
1058
|
type: "network_mocks_succeeded",
|
|
@@ -1253,6 +1294,11 @@ function frameTextSample(frame) {
|
|
|
1253
1294
|
frame && frame.text,
|
|
1254
1295
|
].map((part) => String(part || "")).filter(Boolean).join(" ");
|
|
1255
1296
|
}
|
|
1297
|
+
function viewportsForCheck(check, viewports) {
|
|
1298
|
+
if (!Array.isArray(check.viewports) || !check.viewports.length) return viewports;
|
|
1299
|
+
const names = new Set(check.viewports);
|
|
1300
|
+
return viewports.filter((viewport) => names.has(viewport.name));
|
|
1301
|
+
}
|
|
1256
1302
|
function summarizeRouteInventory(viewport, inventory) {
|
|
1257
1303
|
const directRoutes = Array.isArray(inventory.direct_routes) ? inventory.direct_routes : [];
|
|
1258
1304
|
const clickthroughs = Array.isArray(inventory.clickthroughs) ? inventory.clickthroughs : [];
|
|
@@ -1379,6 +1425,17 @@ function assessProfile(profile, evidence) {
|
|
|
1379
1425
|
reason: event.reason || event.error || "mock_failed",
|
|
1380
1426
|
});
|
|
1381
1427
|
}
|
|
1428
|
+
if (event && event.request_body_matches === false) {
|
|
1429
|
+
failed.push({
|
|
1430
|
+
label: event.label || null,
|
|
1431
|
+
url: event.url || null,
|
|
1432
|
+
method: event.method || null,
|
|
1433
|
+
reason: "request_body_mismatch",
|
|
1434
|
+
request_body_failures: event.request_body_failures || [],
|
|
1435
|
+
request_body_length: event.request_body_length || null,
|
|
1436
|
+
request_body_sample: event.request_body_sample || null,
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1382
1439
|
}
|
|
1383
1440
|
const hitsByLabel = {};
|
|
1384
1441
|
const requiredHitsByLabel = {};
|
|
@@ -1443,9 +1500,22 @@ function assessProfile(profile, evidence) {
|
|
|
1443
1500
|
});
|
|
1444
1501
|
}
|
|
1445
1502
|
for (const check of profile.checks || []) {
|
|
1503
|
+
const checkViewports = viewportsForCheck(check, viewports);
|
|
1504
|
+
if (!checkViewports.length) {
|
|
1505
|
+
checks.push({
|
|
1506
|
+
type: check.type,
|
|
1507
|
+
label: check.label || check.type,
|
|
1508
|
+
status: "failed",
|
|
1509
|
+
evidence: { expected_viewports: check.viewports || [] },
|
|
1510
|
+
message: Array.isArray(check.viewports) && check.viewports.length
|
|
1511
|
+
? "No matching viewport evidence was captured for " + check.viewports.join(", ") + "."
|
|
1512
|
+
: "No viewport evidence was captured.",
|
|
1513
|
+
});
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1446
1516
|
if (check.type === "route_loaded") {
|
|
1447
1517
|
const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
|
|
1448
|
-
const failed =
|
|
1518
|
+
const failed = checkViewports.filter((viewport) => {
|
|
1449
1519
|
const route = { ...(viewport.route || {}), expected_path: expectedPath };
|
|
1450
1520
|
route.matched = routePathMatches(route.observed, expectedPath, evidence.target_url) || route.matched;
|
|
1451
1521
|
return !routeOk(route, evidence.target_url);
|
|
@@ -1456,8 +1526,8 @@ function assessProfile(profile, evidence) {
|
|
|
1456
1526
|
status: failed.length ? "failed" : "passed",
|
|
1457
1527
|
evidence: {
|
|
1458
1528
|
expected_path: expectedPath,
|
|
1459
|
-
observed_paths:
|
|
1460
|
-
http_statuses:
|
|
1529
|
+
observed_paths: checkViewports.map((viewport) => viewport.route && viewport.route.observed),
|
|
1530
|
+
http_statuses: checkViewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
|
|
1461
1531
|
},
|
|
1462
1532
|
message: failed.length ? "Route did not load as " + expectedPath + " in " + failed.length + " viewport(s)." : undefined,
|
|
1463
1533
|
});
|
|
@@ -1465,24 +1535,24 @@ function assessProfile(profile, evidence) {
|
|
|
1465
1535
|
}
|
|
1466
1536
|
if (check.type === "selector_visible") {
|
|
1467
1537
|
const selector = check.selector || "";
|
|
1468
|
-
const failed =
|
|
1538
|
+
const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
|
|
1469
1539
|
checks.push({
|
|
1470
1540
|
type: check.type,
|
|
1471
1541
|
label: check.label || check.type,
|
|
1472
1542
|
status: failed.length ? "failed" : "passed",
|
|
1473
|
-
evidence: { selector, visible_counts:
|
|
1543
|
+
evidence: { selector, visible_counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
|
|
1474
1544
|
message: failed.length ? "Selector " + selector + " was not visible in " + failed.length + " viewport(s)." : undefined,
|
|
1475
1545
|
});
|
|
1476
1546
|
continue;
|
|
1477
1547
|
}
|
|
1478
1548
|
if (check.type === "selector_absent") {
|
|
1479
1549
|
const selector = check.selector || "";
|
|
1480
|
-
const failed =
|
|
1550
|
+
const failed = checkViewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
|
|
1481
1551
|
checks.push({
|
|
1482
1552
|
type: check.type,
|
|
1483
1553
|
label: check.label || check.type,
|
|
1484
1554
|
status: failed.length ? "failed" : "passed",
|
|
1485
|
-
evidence: { selector, counts:
|
|
1555
|
+
evidence: { selector, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
|
|
1486
1556
|
message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
|
|
1487
1557
|
});
|
|
1488
1558
|
continue;
|
|
@@ -1490,12 +1560,12 @@ function assessProfile(profile, evidence) {
|
|
|
1490
1560
|
if (check.type === "selector_count_at_least") {
|
|
1491
1561
|
const selector = check.selector || "";
|
|
1492
1562
|
const minCount = check.min_count == null ? 1 : check.min_count;
|
|
1493
|
-
const failed =
|
|
1563
|
+
const failed = checkViewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
|
|
1494
1564
|
checks.push({
|
|
1495
1565
|
type: check.type,
|
|
1496
1566
|
label: check.label || check.type,
|
|
1497
1567
|
status: failed.length ? "failed" : "passed",
|
|
1498
|
-
evidence: { selector, min_count: minCount, counts:
|
|
1568
|
+
evidence: { selector, min_count: minCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
|
|
1499
1569
|
message: failed.length ? "Selector " + selector + " count was below " + minCount + " in " + failed.length + " viewport(s)." : undefined,
|
|
1500
1570
|
});
|
|
1501
1571
|
continue;
|
|
@@ -1503,12 +1573,12 @@ function assessProfile(profile, evidence) {
|
|
|
1503
1573
|
if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
|
|
1504
1574
|
const selector = check.selector || "";
|
|
1505
1575
|
const expectedCount = check.expected_count == null ? 0 : check.expected_count;
|
|
1506
|
-
const failed =
|
|
1576
|
+
const failed = checkViewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
|
|
1507
1577
|
checks.push({
|
|
1508
1578
|
type: check.type,
|
|
1509
1579
|
label: check.label || check.type,
|
|
1510
1580
|
status: failed.length ? "failed" : "passed",
|
|
1511
|
-
evidence: { selector, expected_count: expectedCount, counts:
|
|
1581
|
+
evidence: { selector, expected_count: expectedCount, counts: checkViewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
|
|
1512
1582
|
message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
|
|
1513
1583
|
});
|
|
1514
1584
|
continue;
|
|
@@ -1516,7 +1586,7 @@ function assessProfile(profile, evidence) {
|
|
|
1516
1586
|
if (check.type === "selector_text_order") {
|
|
1517
1587
|
const selector = check.selector || "";
|
|
1518
1588
|
const expectedTexts = check.expected_texts || [];
|
|
1519
|
-
const results =
|
|
1589
|
+
const results = checkViewports.map((viewport) => {
|
|
1520
1590
|
const texts = textSequenceForCheck(viewport, check);
|
|
1521
1591
|
const match = textOrderMatch(texts, expectedTexts);
|
|
1522
1592
|
return {
|
|
@@ -1538,7 +1608,7 @@ function assessProfile(profile, evidence) {
|
|
|
1538
1608
|
}
|
|
1539
1609
|
if (check.type === "frame_text_visible") {
|
|
1540
1610
|
const selector = check.selector || "";
|
|
1541
|
-
const results =
|
|
1611
|
+
const results = checkViewports.map((viewport) => {
|
|
1542
1612
|
const frames = frameEvidenceForSelector(viewport, selector);
|
|
1543
1613
|
const matches = frames
|
|
1544
1614
|
.map((frame, index) => ({ index, frame, matched: textMatches(frameTextSample(frame), check) }))
|
|
@@ -1565,7 +1635,7 @@ function assessProfile(profile, evidence) {
|
|
|
1565
1635
|
if (check.type === "frame_no_horizontal_overflow") {
|
|
1566
1636
|
const selector = check.selector || "";
|
|
1567
1637
|
const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
|
|
1568
|
-
const results =
|
|
1638
|
+
const results = checkViewports.map((viewport) => {
|
|
1569
1639
|
const frames = frameEvidenceForSelector(viewport, selector);
|
|
1570
1640
|
const frameOverflows = frames.map((frame, index) => ({
|
|
1571
1641
|
index,
|
|
@@ -1595,7 +1665,7 @@ function assessProfile(profile, evidence) {
|
|
|
1595
1665
|
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
1596
1666
|
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
1597
1667
|
const expectedVisible = check.type === "text_visible";
|
|
1598
|
-
const matches =
|
|
1668
|
+
const matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
|
|
1599
1669
|
const failed = matches.filter((matched) => matched !== expectedVisible).length;
|
|
1600
1670
|
checks.push({
|
|
1601
1671
|
type: check.type,
|
|
@@ -1607,7 +1677,7 @@ function assessProfile(profile, evidence) {
|
|
|
1607
1677
|
continue;
|
|
1608
1678
|
}
|
|
1609
1679
|
if (check.type === "route_inventory") {
|
|
1610
|
-
const inventories =
|
|
1680
|
+
const inventories = checkViewports
|
|
1611
1681
|
.map((viewport) => ({ viewport: viewport.name, inventory: viewport.route_inventory }))
|
|
1612
1682
|
.filter((item) => item.inventory && typeof item.inventory === "object");
|
|
1613
1683
|
if (!inventories.length) {
|
|
@@ -1659,7 +1729,7 @@ function assessProfile(profile, evidence) {
|
|
|
1659
1729
|
}
|
|
1660
1730
|
if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
|
|
1661
1731
|
const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
|
|
1662
|
-
const applicable = check.type === "no_mobile_horizontal_overflow" ?
|
|
1732
|
+
const applicable = check.type === "no_mobile_horizontal_overflow" ? checkViewports.filter((viewport) => viewport.width <= 820) : checkViewports;
|
|
1663
1733
|
if (!applicable.length) {
|
|
1664
1734
|
checks.push({
|
|
1665
1735
|
type: check.type,
|
|
@@ -1814,6 +1884,52 @@ function compactSetupResultText(value) {
|
|
|
1814
1884
|
if (text.length <= 500) return text;
|
|
1815
1885
|
return text.slice(0, 500) + "... (" + text.length + " chars)";
|
|
1816
1886
|
}
|
|
1887
|
+
function compactNetworkMockRequestBody(value) {
|
|
1888
|
+
const text = String(value || "").replace(/\s+/g, " ").trim();
|
|
1889
|
+
if (text.length <= 1000) return text;
|
|
1890
|
+
return text.slice(0, 1000) + "... (" + text.length + " chars)";
|
|
1891
|
+
}
|
|
1892
|
+
function networkMockStringList(value) {
|
|
1893
|
+
return Array.isArray(value) ? value.map((item) => String(item)).filter(Boolean) : [];
|
|
1894
|
+
}
|
|
1895
|
+
function networkMockShouldCaptureRequestBody(mock) {
|
|
1896
|
+
return mock && (
|
|
1897
|
+
mock.capture_request_body === true
|
|
1898
|
+
|| networkMockStringList(mock.request_body_contains).length > 0
|
|
1899
|
+
|| networkMockStringList(mock.request_body_patterns).length > 0
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
function networkMockRequestBodyFailures(body, mock) {
|
|
1903
|
+
const failures = [];
|
|
1904
|
+
const rawBody = String(body || "");
|
|
1905
|
+
const compactBody = compactNetworkMockRequestBody(rawBody);
|
|
1906
|
+
for (const expected of networkMockStringList(mock.request_body_contains)) {
|
|
1907
|
+
if (!rawBody.includes(expected) && !compactBody.includes(expected)) {
|
|
1908
|
+
failures.push({
|
|
1909
|
+
type: "request_body_missing_text",
|
|
1910
|
+
text: String(expected).slice(0, 200),
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
for (const pattern of networkMockStringList(mock.request_body_patterns)) {
|
|
1915
|
+
try {
|
|
1916
|
+
const regex = new RegExp(pattern);
|
|
1917
|
+
if (!regex.test(rawBody) && !regex.test(compactBody)) {
|
|
1918
|
+
failures.push({
|
|
1919
|
+
type: "request_body_pattern_not_matched",
|
|
1920
|
+
pattern: String(pattern).slice(0, 200),
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
failures.push({
|
|
1925
|
+
type: "request_body_invalid_pattern",
|
|
1926
|
+
pattern: String(pattern).slice(0, 200),
|
|
1927
|
+
error: String(error && error.message ? error.message : error).slice(0, 500),
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
return failures;
|
|
1932
|
+
}
|
|
1817
1933
|
async function setupLocatorVisible(locator, index) {
|
|
1818
1934
|
return await locator.nth(index).isVisible({ timeout: 1000 }).catch(() => false);
|
|
1819
1935
|
}
|
|
@@ -1842,7 +1958,11 @@ async function registerNetworkMocks(mocks) {
|
|
|
1842
1958
|
body = JSON.stringify(response.body_json);
|
|
1843
1959
|
contentType = response.content_type || headers["content-type"] || headers["Content-Type"] || "application/json";
|
|
1844
1960
|
}
|
|
1845
|
-
|
|
1961
|
+
const shouldCaptureRequestBody = networkMockShouldCaptureRequestBody(mock);
|
|
1962
|
+
const requestBody = shouldCaptureRequestBody && request.postData ? request.postData() || "" : "";
|
|
1963
|
+
const requestBodyFailures = shouldCaptureRequestBody ? networkMockRequestBodyFailures(requestBody, mock) : [];
|
|
1964
|
+
const status = response.status || mock.status || 200;
|
|
1965
|
+
const event = {
|
|
1846
1966
|
ok: true,
|
|
1847
1967
|
label: mock.label,
|
|
1848
1968
|
response_label: response.label || null,
|
|
@@ -1852,10 +1972,17 @@ async function registerNetworkMocks(mocks) {
|
|
|
1852
1972
|
sequence_cycle: responseIndex !== null && mock.repeat_responses === true && hitIndex >= responses.length,
|
|
1853
1973
|
url: request.url(),
|
|
1854
1974
|
method,
|
|
1855
|
-
status
|
|
1856
|
-
}
|
|
1975
|
+
status,
|
|
1976
|
+
};
|
|
1977
|
+
if (shouldCaptureRequestBody) {
|
|
1978
|
+
event.request_body_matches = requestBodyFailures.length === 0;
|
|
1979
|
+
event.request_body_failures = requestBodyFailures;
|
|
1980
|
+
event.request_body_length = requestBody.length;
|
|
1981
|
+
event.request_body_sample = compactNetworkMockRequestBody(requestBody);
|
|
1982
|
+
}
|
|
1983
|
+
networkMockEvents.push(event);
|
|
1857
1984
|
await route.fulfill({
|
|
1858
|
-
status
|
|
1985
|
+
status,
|
|
1859
1986
|
headers,
|
|
1860
1987
|
contentType,
|
|
1861
1988
|
body,
|