@riddledc/riddle-proof 0.7.38 → 0.7.40
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 +48 -1
- package/dist/{chunk-ZB7AFRN3.js → chunk-RAVOICNN.js} +158 -26
- package/dist/cli.cjs +158 -26
- package/dist/cli.js +1 -1
- package/dist/index.cjs +158 -26
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/profile.cjs +158 -26
- package/dist/profile.d.cts +14 -5
- package/dist/profile.d.ts +14 -5
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -182,7 +182,35 @@ before navigation, records each hit, and adds an implicit
|
|
|
182
182
|
`network_mocks_succeeded` check when mocks are present. A mock supports
|
|
183
183
|
`url`/`glob`/`pattern`, optional `method`, `status`, `content_type`, `headers`,
|
|
184
184
|
string `body`, JSON `json` / `body_json`, and `required: false` for
|
|
185
|
-
best-effort mocks.
|
|
185
|
+
best-effort mocks. Use `responses` for retry or recovery profiles where the
|
|
186
|
+
same endpoint should return a sequence, such as first `503` and then `200`.
|
|
187
|
+
Each response accepts the same payload fields plus an optional label:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"label": "builder-build",
|
|
192
|
+
"url": "**/api/build",
|
|
193
|
+
"method": "POST",
|
|
194
|
+
"responses": [
|
|
195
|
+
{
|
|
196
|
+
"label": "first-build-fails",
|
|
197
|
+
"status": 503,
|
|
198
|
+
"json": { "error": "Synthetic build outage" }
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"label": "second-build-succeeds",
|
|
202
|
+
"status": 200,
|
|
203
|
+
"json": { "previewUrl": "https://cdn.example/game/index.html" }
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
When `responses` is present, `network_mocks_succeeded` requires each configured
|
|
210
|
+
response to be hit at least once by default and records `hit_index`,
|
|
211
|
+
`response_index`, and `response_label` for each request. Set
|
|
212
|
+
`required_hit_count` / `min_hits` or `required: false` when a different
|
|
213
|
+
contract is intentional.
|
|
186
214
|
|
|
187
215
|
`target.setup_actions` is optional. Use it when the meaningful proof surface
|
|
188
216
|
appears only after a picker, tab, login stub, storage seed, form fill,
|
|
@@ -202,6 +230,25 @@ clears `local`, `session`, or `both` browser storage scopes, defaults to
|
|
|
202
230
|
profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
|
|
203
231
|
still overrides the profile value for one-off runs.
|
|
204
232
|
|
|
233
|
+
Use `allowed_console_patterns` / `allowed_console_texts` on
|
|
234
|
+
`no_fatal_console_errors` when a negative-path profile intentionally triggers a
|
|
235
|
+
known browser console error, such as a mocked `503` that the app recovers from:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"type": "no_fatal_console_errors",
|
|
240
|
+
"allowed_console_patterns": [
|
|
241
|
+
"Failed to load resource: the server responded with a status of 503",
|
|
242
|
+
"Build failed: Error: Synthetic build outage"
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Allowed console events and page errors are still counted in check evidence, but
|
|
248
|
+
only unallowed `error` / `assert` console events and page errors fail the check.
|
|
249
|
+
Use `allowed_page_error_patterns`, `allowed_console_texts`, or
|
|
250
|
+
`allowed_page_error_texts` for narrower matching when needed.
|
|
251
|
+
|
|
205
252
|
Use `selector_text_order` when a table, list, or card group must show visible
|
|
206
253
|
items in a specific order after setup actions such as sorting or filtering:
|
|
207
254
|
|
|
@@ -246,24 +246,57 @@ function normalizeNetworkMock(input, index) {
|
|
|
246
246
|
if (!isRecord(input)) throw new Error(`target.network_mocks[${index}] must be an object.`);
|
|
247
247
|
const url = stringValue(input.url) || stringValue(input.glob) || stringValue(input.pattern);
|
|
248
248
|
if (!url) throw new Error(`target.network_mocks[${index}] requires url.`);
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
const payload = normalizeNetworkMockResponsePayload(input, `target.network_mocks[${index}]`);
|
|
250
|
+
const responsesInput = input.responses ?? input.sequence;
|
|
251
|
+
const responses = normalizeNetworkMockResponses(responsesInput, index, payload);
|
|
252
|
+
const requiredHitCount = numberValue(
|
|
253
|
+
input.required_hit_count ?? input.requiredHitCount ?? input.required_hits ?? input.requiredHits ?? input.min_hits ?? input.minHits
|
|
254
|
+
);
|
|
255
|
+
if (requiredHitCount !== void 0 && (!Number.isInteger(requiredHitCount) || requiredHitCount < 1)) {
|
|
256
|
+
throw new Error(`target.network_mocks[${index}].required_hit_count must be a positive integer.`);
|
|
252
257
|
}
|
|
253
|
-
const body = stringValue(input.body) ?? stringValue(input.body_text) ?? stringValue(input.bodyText);
|
|
254
|
-
const hasJsonBody = Object.prototype.hasOwnProperty.call(input, "body_json") || Object.prototype.hasOwnProperty.call(input, "bodyJson") || Object.prototype.hasOwnProperty.call(input, "json");
|
|
255
258
|
return {
|
|
259
|
+
...payload,
|
|
256
260
|
label: normalizeName(input.label || input.name, `network-mock-${index + 1}`),
|
|
257
261
|
url,
|
|
258
262
|
method: stringValue(input.method)?.toUpperCase(),
|
|
263
|
+
responses,
|
|
264
|
+
required_hit_count: requiredHitCount,
|
|
265
|
+
required: input.required === false ? false : true
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function normalizeNetworkMockResponsePayload(input, label, defaults = {}) {
|
|
269
|
+
const status = numberValue(input.status) ?? defaults.status ?? 200;
|
|
270
|
+
if (!Number.isInteger(status) || status < 100 || status > 599) {
|
|
271
|
+
throw new Error(`${label}.status must be an HTTP status code.`);
|
|
272
|
+
}
|
|
273
|
+
const body = stringValue(input.body) ?? stringValue(input.body_text) ?? stringValue(input.bodyText) ?? defaults.body;
|
|
274
|
+
const hasJsonBody = Object.prototype.hasOwnProperty.call(input, "body_json") || Object.prototype.hasOwnProperty.call(input, "bodyJson") || Object.prototype.hasOwnProperty.call(input, "json");
|
|
275
|
+
return {
|
|
276
|
+
label: stringValue(input.label) || stringValue(input.name) || defaults.label,
|
|
259
277
|
status,
|
|
260
|
-
content_type: stringValue(input.content_type) || stringValue(input.contentType),
|
|
261
|
-
headers: stringRecord(input.headers),
|
|
278
|
+
content_type: stringValue(input.content_type) || stringValue(input.contentType) || defaults.content_type,
|
|
279
|
+
headers: stringRecord(input.headers) || defaults.headers,
|
|
262
280
|
body,
|
|
263
|
-
body_json: hasJsonBody ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) :
|
|
264
|
-
required: input.required === false ? false : true
|
|
281
|
+
body_json: hasJsonBody ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : defaults.body_json
|
|
265
282
|
};
|
|
266
283
|
}
|
|
284
|
+
function normalizeNetworkMockResponses(value, mockIndex, defaults) {
|
|
285
|
+
if (value === void 0) return void 0;
|
|
286
|
+
if (!Array.isArray(value)) throw new Error(`target.network_mocks[${mockIndex}].responses must be an array.`);
|
|
287
|
+
if (!value.length) throw new Error(`target.network_mocks[${mockIndex}].responses must not be empty.`);
|
|
288
|
+
const responseDefaults = { ...defaults, label: void 0 };
|
|
289
|
+
return value.map((response, responseIndex) => {
|
|
290
|
+
if (!isRecord(response)) {
|
|
291
|
+
throw new Error(`target.network_mocks[${mockIndex}].responses[${responseIndex}] must be an object.`);
|
|
292
|
+
}
|
|
293
|
+
return normalizeNetworkMockResponsePayload(
|
|
294
|
+
response,
|
|
295
|
+
`target.network_mocks[${mockIndex}].responses[${responseIndex}]`,
|
|
296
|
+
responseDefaults
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
267
300
|
function normalizeNetworkMocks(value) {
|
|
268
301
|
if (value === void 0) return void 0;
|
|
269
302
|
if (!Array.isArray(value)) throw new Error("target.network_mocks must be an array.");
|
|
@@ -305,6 +338,13 @@ function normalizeExpectedTexts(value, index) {
|
|
|
305
338
|
if (!texts.length) throw new Error(`checks[${index}] selector_text_order expected_texts must contain non-empty strings.`);
|
|
306
339
|
return texts;
|
|
307
340
|
}
|
|
341
|
+
function normalizeStringList(value, label) {
|
|
342
|
+
if (value === void 0) return void 0;
|
|
343
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
344
|
+
const values = value.map((item) => String(item).replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
345
|
+
if (!values.length) throw new Error(`${label} must contain non-empty strings.`);
|
|
346
|
+
return values;
|
|
347
|
+
}
|
|
308
348
|
function normalizeCheck(input, index) {
|
|
309
349
|
if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
|
|
310
350
|
const type = stringValue(input.type);
|
|
@@ -353,6 +393,10 @@ function normalizeCheck(input, index) {
|
|
|
353
393
|
text: stringValue(input.text),
|
|
354
394
|
pattern: stringValue(input.pattern),
|
|
355
395
|
flags: stringValue(input.flags),
|
|
396
|
+
allowed_console_texts: normalizeStringList(input.allowed_console_texts ?? input.allowedConsoleTexts ?? input.allow_console_texts ?? input.allowConsoleTexts, `checks[${index}] allowed_console_texts`),
|
|
397
|
+
allowed_console_patterns: normalizeStringList(input.allowed_console_patterns ?? input.allowedConsolePatterns ?? input.allow_console_patterns ?? input.allowConsolePatterns, `checks[${index}] allowed_console_patterns`),
|
|
398
|
+
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`),
|
|
399
|
+
allowed_page_error_patterns: normalizeStringList(input.allowed_page_error_patterns ?? input.allowedPageErrorPatterns ?? input.allow_page_error_patterns ?? input.allowPageErrorPatterns, `checks[${index}] allowed_page_error_patterns`),
|
|
356
400
|
min_count: numberValue(input.min_count),
|
|
357
401
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
358
402
|
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
|
|
@@ -516,6 +560,18 @@ function matchText(sample, check) {
|
|
|
516
560
|
}
|
|
517
561
|
return sample.includes(check.text || "");
|
|
518
562
|
}
|
|
563
|
+
function matchesAllowedMessage(message, texts, patterns) {
|
|
564
|
+
const sample = String(message || "");
|
|
565
|
+
if (texts?.some((text) => sample.includes(text))) return true;
|
|
566
|
+
for (const pattern of patterns || []) {
|
|
567
|
+
try {
|
|
568
|
+
if (new RegExp(pattern).test(sample)) return true;
|
|
569
|
+
} catch {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
519
575
|
function normalizeRoutePath(path) {
|
|
520
576
|
const value = path || "/";
|
|
521
577
|
if (value === "/") return "/";
|
|
@@ -802,14 +858,28 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
802
858
|
};
|
|
803
859
|
}
|
|
804
860
|
if (check.type === "no_fatal_console_errors") {
|
|
805
|
-
const
|
|
861
|
+
const fatalConsoleEvents = (evidence.console?.events || []).filter((event) => event.type === "error" || event.type === "assert");
|
|
862
|
+
const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
|
|
863
|
+
const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
|
|
864
|
+
const pageErrors = evidence.page_errors || [];
|
|
865
|
+
const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
|
|
866
|
+
const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
|
|
867
|
+
const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
|
|
806
868
|
return {
|
|
807
869
|
type: check.type,
|
|
808
870
|
label: checkLabel(check),
|
|
809
871
|
status: fatalCount ? "failed" : "passed",
|
|
810
872
|
evidence: {
|
|
811
|
-
console_fatal_count:
|
|
812
|
-
page_error_count:
|
|
873
|
+
console_fatal_count: unallowedConsoleEvents.length,
|
|
874
|
+
page_error_count: unallowedPageErrors.length,
|
|
875
|
+
total_console_fatal_count: fatalConsoleEvents.length,
|
|
876
|
+
total_page_error_count: pageErrors.length,
|
|
877
|
+
allowed_console_fatal_count: allowedConsoleEvents.length,
|
|
878
|
+
allowed_page_error_count: allowedPageErrors.length,
|
|
879
|
+
allowed_console_texts: check.allowed_console_texts || [],
|
|
880
|
+
allowed_console_patterns: check.allowed_console_patterns || [],
|
|
881
|
+
allowed_page_error_texts: check.allowed_page_error_texts || [],
|
|
882
|
+
allowed_page_error_patterns: check.allowed_page_error_patterns || []
|
|
813
883
|
},
|
|
814
884
|
message: fatalCount ? `${fatalCount} fatal browser error(s) were captured.` : void 0
|
|
815
885
|
};
|
|
@@ -872,12 +942,15 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
|
|
|
872
942
|
const requiredMocks = mocks.filter((mock) => mock.required !== false);
|
|
873
943
|
for (const mock of requiredMocks) {
|
|
874
944
|
const hits = events.filter((event) => event.label === mock.label && event.ok !== false).length;
|
|
875
|
-
|
|
945
|
+
const requiredHitCount = requiredNetworkMockHitCount(mock);
|
|
946
|
+
if (hits < requiredHitCount) {
|
|
876
947
|
failed.push({
|
|
877
948
|
label: mock.label,
|
|
878
949
|
url: mock.url,
|
|
879
950
|
method: mock.method || null,
|
|
880
|
-
reason: "required_mock_not_hit"
|
|
951
|
+
reason: hits < 1 ? "required_mock_not_hit" : "required_mock_hit_count_not_met",
|
|
952
|
+
required_hit_count: requiredHitCount,
|
|
953
|
+
hit_count: hits
|
|
881
954
|
});
|
|
882
955
|
}
|
|
883
956
|
}
|
|
@@ -903,11 +976,21 @@ function assessNetworkMocksFromEvidence(profile, evidence) {
|
|
|
903
976
|
mock.label,
|
|
904
977
|
events.filter((event) => event.label === mock.label && event.ok !== false).length
|
|
905
978
|
])),
|
|
979
|
+
required_hits_by_label: Object.fromEntries(requiredMocks.map((mock) => [
|
|
980
|
+
mock.label,
|
|
981
|
+
requiredNetworkMockHitCount(mock)
|
|
982
|
+
])),
|
|
906
983
|
failed
|
|
907
984
|
},
|
|
908
985
|
message: failed.length ? `Network mocks failed or were not hit for ${failed.length} mock(s).` : void 0
|
|
909
986
|
};
|
|
910
987
|
}
|
|
988
|
+
function requiredNetworkMockHitCount(mock) {
|
|
989
|
+
if (mock.required === false) return 0;
|
|
990
|
+
if (mock.required_hit_count !== void 0) return mock.required_hit_count;
|
|
991
|
+
if (Array.isArray(mock.responses) && mock.responses.length) return mock.responses.length;
|
|
992
|
+
return 1;
|
|
993
|
+
}
|
|
911
994
|
function profileStatusFromEvidence(profile, evidence, checks) {
|
|
912
995
|
if (!evidence) return "proof_insufficient";
|
|
913
996
|
const viewports = evidence.viewports || [];
|
|
@@ -1065,6 +1148,16 @@ function textMatches(sample, check) {
|
|
|
1065
1148
|
}
|
|
1066
1149
|
return String(sample || "").includes(check.text || "");
|
|
1067
1150
|
}
|
|
1151
|
+
function matchesAllowedMessage(message, texts, patterns) {
|
|
1152
|
+
const sample = String(message || "");
|
|
1153
|
+
if ((texts || []).some((text) => sample.includes(text))) return true;
|
|
1154
|
+
for (const pattern of patterns || []) {
|
|
1155
|
+
try {
|
|
1156
|
+
if (new RegExp(pattern).test(sample)) return true;
|
|
1157
|
+
} catch {}
|
|
1158
|
+
}
|
|
1159
|
+
return false;
|
|
1160
|
+
}
|
|
1068
1161
|
function textSequenceForCheck(viewport, check) {
|
|
1069
1162
|
const key = check.selector || "";
|
|
1070
1163
|
const sequence = viewport.text_sequences && viewport.text_sequences[key];
|
|
@@ -1192,6 +1285,12 @@ function horizontalBoundsOverflowPx(value) {
|
|
|
1192
1285
|
}
|
|
1193
1286
|
return roundPixels(max);
|
|
1194
1287
|
}
|
|
1288
|
+
function requiredNetworkMockHitCount(mock) {
|
|
1289
|
+
if (!mock || mock.required === false) return 0;
|
|
1290
|
+
if (Number.isInteger(mock.required_hit_count) && mock.required_hit_count > 0) return mock.required_hit_count;
|
|
1291
|
+
if (Array.isArray(mock.responses) && mock.responses.length) return mock.responses.length;
|
|
1292
|
+
return 1;
|
|
1293
|
+
}
|
|
1195
1294
|
function assessProfile(profile, evidence) {
|
|
1196
1295
|
const checks = [];
|
|
1197
1296
|
const viewports = evidence.viewports || [];
|
|
@@ -1201,12 +1300,15 @@ function assessProfile(profile, evidence) {
|
|
|
1201
1300
|
const failed = [];
|
|
1202
1301
|
for (const mock of requiredMocks) {
|
|
1203
1302
|
const hits = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
|
|
1204
|
-
|
|
1303
|
+
const requiredHitCount = requiredNetworkMockHitCount(mock);
|
|
1304
|
+
if (hits < requiredHitCount) {
|
|
1205
1305
|
failed.push({
|
|
1206
1306
|
label: mock.label,
|
|
1207
1307
|
url: mock.url,
|
|
1208
1308
|
method: mock.method || null,
|
|
1209
|
-
reason: "required_mock_not_hit",
|
|
1309
|
+
reason: hits < 1 ? "required_mock_not_hit" : "required_mock_hit_count_not_met",
|
|
1310
|
+
required_hit_count: requiredHitCount,
|
|
1311
|
+
hit_count: hits,
|
|
1210
1312
|
});
|
|
1211
1313
|
}
|
|
1212
1314
|
}
|
|
@@ -1221,8 +1323,10 @@ function assessProfile(profile, evidence) {
|
|
|
1221
1323
|
}
|
|
1222
1324
|
}
|
|
1223
1325
|
const hitsByLabel = {};
|
|
1326
|
+
const requiredHitsByLabel = {};
|
|
1224
1327
|
for (const mock of profile.target.network_mocks) {
|
|
1225
1328
|
hitsByLabel[mock.label] = events.filter((event) => event && event.label === mock.label && event.ok !== false).length;
|
|
1329
|
+
if (mock && mock.required !== false) requiredHitsByLabel[mock.label] = requiredNetworkMockHitCount(mock);
|
|
1226
1330
|
}
|
|
1227
1331
|
checks.push({
|
|
1228
1332
|
type: "network_mocks_succeeded",
|
|
@@ -1233,6 +1337,7 @@ function assessProfile(profile, evidence) {
|
|
|
1233
1337
|
required_count: requiredMocks.length,
|
|
1234
1338
|
hit_count: events.filter((event) => event && event.ok !== false).length,
|
|
1235
1339
|
hits_by_label: hitsByLabel,
|
|
1340
|
+
required_hits_by_label: requiredHitsByLabel,
|
|
1236
1341
|
failed,
|
|
1237
1342
|
},
|
|
1238
1343
|
message: failed.length ? "Network mocks failed or were not hit for " + failed.length + " mock(s)." : undefined,
|
|
@@ -1499,12 +1604,29 @@ function assessProfile(profile, evidence) {
|
|
|
1499
1604
|
continue;
|
|
1500
1605
|
}
|
|
1501
1606
|
if (check.type === "no_fatal_console_errors") {
|
|
1502
|
-
const
|
|
1607
|
+
const fatalConsoleEvents = ((evidence.console && evidence.console.events) || []).filter((event) => event && (event.type === "error" || event.type === "assert"));
|
|
1608
|
+
const allowedConsoleEvents = fatalConsoleEvents.filter((event) => matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
|
|
1609
|
+
const unallowedConsoleEvents = fatalConsoleEvents.filter((event) => !matchesAllowedMessage(event.text, check.allowed_console_texts, check.allowed_console_patterns));
|
|
1610
|
+
const pageErrors = evidence.page_errors || [];
|
|
1611
|
+
const allowedPageErrors = pageErrors.filter((error) => matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
|
|
1612
|
+
const unallowedPageErrors = pageErrors.filter((error) => !matchesAllowedMessage(error && error.message, check.allowed_page_error_texts, check.allowed_page_error_patterns));
|
|
1613
|
+
const fatalCount = unallowedConsoleEvents.length + unallowedPageErrors.length;
|
|
1503
1614
|
checks.push({
|
|
1504
1615
|
type: check.type,
|
|
1505
1616
|
label: check.label || check.type,
|
|
1506
1617
|
status: fatalCount ? "failed" : "passed",
|
|
1507
|
-
evidence: {
|
|
1618
|
+
evidence: {
|
|
1619
|
+
console_fatal_count: unallowedConsoleEvents.length,
|
|
1620
|
+
page_error_count: unallowedPageErrors.length,
|
|
1621
|
+
total_console_fatal_count: fatalConsoleEvents.length,
|
|
1622
|
+
total_page_error_count: pageErrors.length,
|
|
1623
|
+
allowed_console_fatal_count: allowedConsoleEvents.length,
|
|
1624
|
+
allowed_page_error_count: allowedPageErrors.length,
|
|
1625
|
+
allowed_console_texts: check.allowed_console_texts || [],
|
|
1626
|
+
allowed_console_patterns: check.allowed_console_patterns || [],
|
|
1627
|
+
allowed_page_error_texts: check.allowed_page_error_texts || [],
|
|
1628
|
+
allowed_page_error_patterns: check.allowed_page_error_patterns || [],
|
|
1629
|
+
},
|
|
1508
1630
|
message: fatalCount ? String(fatalCount) + " fatal browser error(s) were captured." : undefined,
|
|
1509
1631
|
});
|
|
1510
1632
|
continue;
|
|
@@ -1614,6 +1736,7 @@ async function setupLocatorVisible(locator, index) {
|
|
|
1614
1736
|
}
|
|
1615
1737
|
async function registerNetworkMocks(mocks) {
|
|
1616
1738
|
for (const mock of mocks || []) {
|
|
1739
|
+
let hitCount = 0;
|
|
1617
1740
|
await page.route(mock.url, async (route) => {
|
|
1618
1741
|
const request = route.request();
|
|
1619
1742
|
const method = request.method ? request.method() : "";
|
|
@@ -1622,22 +1745,31 @@ async function registerNetworkMocks(mocks) {
|
|
|
1622
1745
|
return;
|
|
1623
1746
|
}
|
|
1624
1747
|
try {
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1748
|
+
const responses = Array.isArray(mock.responses) ? mock.responses : [];
|
|
1749
|
+
const hitIndex = hitCount;
|
|
1750
|
+
hitCount += 1;
|
|
1751
|
+
const responseIndex = responses.length ? Math.min(hitIndex, responses.length - 1) : null;
|
|
1752
|
+
const response = responseIndex === null ? mock : responses[responseIndex];
|
|
1753
|
+
const headers = { ...(response.headers || mock.headers || {}) };
|
|
1754
|
+
let body = response.body || "";
|
|
1755
|
+
let contentType = response.content_type || headers["content-type"] || headers["Content-Type"] || "text/plain";
|
|
1756
|
+
if (response.body_json !== undefined) {
|
|
1757
|
+
body = JSON.stringify(response.body_json);
|
|
1758
|
+
contentType = response.content_type || headers["content-type"] || headers["Content-Type"] || "application/json";
|
|
1631
1759
|
}
|
|
1632
1760
|
networkMockEvents.push({
|
|
1633
1761
|
ok: true,
|
|
1634
1762
|
label: mock.label,
|
|
1763
|
+
response_label: response.label || null,
|
|
1764
|
+
hit_index: hitIndex,
|
|
1765
|
+
response_index: responseIndex,
|
|
1766
|
+
sequence_reused: responseIndex !== null && hitIndex >= responses.length,
|
|
1635
1767
|
url: request.url(),
|
|
1636
1768
|
method,
|
|
1637
|
-
status: mock.status || 200,
|
|
1769
|
+
status: response.status || mock.status || 200,
|
|
1638
1770
|
});
|
|
1639
1771
|
await route.fulfill({
|
|
1640
|
-
status: mock.status || 200,
|
|
1772
|
+
status: response.status || mock.status || 200,
|
|
1641
1773
|
headers,
|
|
1642
1774
|
contentType,
|
|
1643
1775
|
body,
|