@piwitests/reporter 0.54.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +3578 -2460
- package/dist/global-setup-module.js +96 -57
- package/dist/index.d.ts +30 -9
- package/dist/index.js +6171 -5344
- package/dist/internal/capture/attachments.d.ts +2 -0
- package/dist/internal/capture/attachments.js +2 -0
- package/dist/internal/capture/capture-fixtures.d.ts +32 -10
- package/dist/internal/capture/capture-fixtures.js +1914 -1391
- package/dist/internal/capture/locator-healing.js +70 -26
- package/dist/internal/capture/pick-on-failure.js +84 -54
- package/package.json +1 -1
|
@@ -58,7 +58,7 @@ __export(capture_fixtures_exports, {
|
|
|
58
58
|
stopResourceLedger: () => stopResourceLedger
|
|
59
59
|
});
|
|
60
60
|
module.exports = __toCommonJS(capture_fixtures_exports);
|
|
61
|
-
var
|
|
61
|
+
var path12 = __toESM(require("path"));
|
|
62
62
|
var import_node_zlib2 = require("zlib");
|
|
63
63
|
|
|
64
64
|
// ../picker-dom/src/probe.ts
|
|
@@ -489,26 +489,26 @@ function installPickerOverlay(arg) {
|
|
|
489
489
|
}
|
|
490
490
|
};
|
|
491
491
|
mount(highlight, label, banner);
|
|
492
|
-
const
|
|
492
|
+
const quoteJs = (s) => `'${JSON.stringify(s).slice(1, -1).replace(/\\"/g, '"').replace(/'/g, "\\'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}'`;
|
|
493
493
|
const describe = (el) => {
|
|
494
494
|
const tag = (el.tagName || "?").toLowerCase();
|
|
495
495
|
const testId = el.getAttribute && el.getAttribute(testIdAttr);
|
|
496
|
-
if (testId) return `getByTestId(
|
|
496
|
+
if (testId) return `getByTestId(${quoteJs(testId)})`;
|
|
497
497
|
if (el.labels && el.labels.length > 0) {
|
|
498
498
|
const labelText = (el.labels[0].textContent || "").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
499
|
-
if (labelText) return `getByLabel(
|
|
499
|
+
if (labelText) return `getByLabel(${quoteJs(labelText)})`;
|
|
500
500
|
}
|
|
501
501
|
const ariaLabel = el.getAttribute && el.getAttribute("aria-label");
|
|
502
|
-
if (ariaLabel) return `getByLabel(
|
|
502
|
+
if (ariaLabel) return `getByLabel(${quoteJs(ariaLabel)})`;
|
|
503
503
|
const placeholder = el.getAttribute && el.getAttribute("placeholder");
|
|
504
|
-
if (placeholder) return `getByPlaceholder(
|
|
504
|
+
if (placeholder) return `getByPlaceholder(${quoteJs(placeholder)})`;
|
|
505
505
|
const alt = el.getAttribute && el.getAttribute("alt");
|
|
506
|
-
if (alt) return `getByAltText(
|
|
506
|
+
if (alt) return `getByAltText(${quoteJs(alt)})`;
|
|
507
507
|
const titleAttr = el.getAttribute && el.getAttribute("title");
|
|
508
|
-
if (titleAttr) return `getByTitle(
|
|
509
|
-
if (el.id) return `locator(
|
|
508
|
+
if (titleAttr) return `getByTitle(${quoteJs(titleAttr)})`;
|
|
509
|
+
if (el.id) return `locator(${quoteJs(`#${el.id}`)})`;
|
|
510
510
|
const cls = (el.getAttribute && el.getAttribute("class") || "").split(/\s+/).find((c) => c.length > 1);
|
|
511
|
-
return cls ? `locator(
|
|
511
|
+
return cls ? `locator(${quoteJs(`.${cls}`)})` : tag;
|
|
512
512
|
};
|
|
513
513
|
const buildChain = (raw) => {
|
|
514
514
|
const chain2 = [];
|
|
@@ -1198,422 +1198,706 @@ function takePauseState() {
|
|
|
1198
1198
|
return typeof state === "string" ? state : null;
|
|
1199
1199
|
}
|
|
1200
1200
|
|
|
1201
|
-
// ../core/src/locator-
|
|
1202
|
-
var
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
h2: "heading",
|
|
1231
|
-
h3: "heading",
|
|
1232
|
-
h4: "heading",
|
|
1233
|
-
h5: "heading",
|
|
1234
|
-
h6: "heading",
|
|
1235
|
-
details: "group",
|
|
1236
|
-
summary: "button",
|
|
1237
|
-
search: "search"
|
|
1201
|
+
// ../core/src/locator-chain.ts
|
|
1202
|
+
var LOCATING_METHODS = /* @__PURE__ */ new Set([
|
|
1203
|
+
"getByRole",
|
|
1204
|
+
"getByTestId",
|
|
1205
|
+
"getByText",
|
|
1206
|
+
"getByLabel",
|
|
1207
|
+
"getByPlaceholder",
|
|
1208
|
+
"getByAltText",
|
|
1209
|
+
"getByTitle",
|
|
1210
|
+
"locator",
|
|
1211
|
+
"frameLocator"
|
|
1212
|
+
]);
|
|
1213
|
+
var NARROWING_METHODS = /* @__PURE__ */ new Set([
|
|
1214
|
+
"filter",
|
|
1215
|
+
"first",
|
|
1216
|
+
"last",
|
|
1217
|
+
"nth",
|
|
1218
|
+
"and",
|
|
1219
|
+
"or",
|
|
1220
|
+
"visible",
|
|
1221
|
+
"contentFrame",
|
|
1222
|
+
"owner"
|
|
1223
|
+
]);
|
|
1224
|
+
var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([...LOCATING_METHODS, ...NARROWING_METHODS]);
|
|
1225
|
+
var LocatorParseError = class extends Error {
|
|
1226
|
+
constructor(message) {
|
|
1227
|
+
super(message);
|
|
1228
|
+
this.name = "LocatorParseError";
|
|
1229
|
+
}
|
|
1238
1230
|
};
|
|
1239
|
-
var
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
search: "searchbox",
|
|
1248
|
-
number: "spinbutton",
|
|
1249
|
-
text: "textbox",
|
|
1250
|
-
email: "textbox",
|
|
1251
|
-
tel: "textbox",
|
|
1252
|
-
url: "textbox",
|
|
1253
|
-
password: "textbox"
|
|
1231
|
+
var SIMPLE_ESCAPES = {
|
|
1232
|
+
n: "\n",
|
|
1233
|
+
t: " ",
|
|
1234
|
+
r: "\r",
|
|
1235
|
+
b: "\b",
|
|
1236
|
+
f: "\f",
|
|
1237
|
+
v: "\v",
|
|
1238
|
+
"0": "\0"
|
|
1254
1239
|
};
|
|
1255
|
-
var
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
"data-testid",
|
|
1260
|
-
"placeholder",
|
|
1261
|
-
"alt",
|
|
1262
|
-
"title",
|
|
1263
|
-
"aria-label",
|
|
1264
|
-
"aria-labelledby",
|
|
1265
|
-
"aria-level",
|
|
1266
|
-
"role",
|
|
1267
|
-
"type",
|
|
1268
|
-
"href",
|
|
1269
|
-
"multiple"
|
|
1270
|
-
];
|
|
1271
|
-
function resolveAriaRole(attrs) {
|
|
1272
|
-
const explicit = attrs.attributes["role"];
|
|
1273
|
-
if (explicit) return explicit;
|
|
1274
|
-
const tag = attrs.tagName;
|
|
1275
|
-
if (!tag) return null;
|
|
1276
|
-
if (tag === "input") {
|
|
1277
|
-
const type = (attrs.attributes["type"] ?? "text").toLowerCase();
|
|
1278
|
-
return INPUT_TYPE_TO_ROLE[type] ?? "textbox";
|
|
1279
|
-
}
|
|
1280
|
-
if (tag === "select") {
|
|
1281
|
-
return attrs.attributes["multiple"] != null ? "listbox" : "combobox";
|
|
1282
|
-
}
|
|
1283
|
-
if (tag === "a") {
|
|
1284
|
-
return attrs.attributes["href"] != null ? "link" : null;
|
|
1285
|
-
}
|
|
1286
|
-
return TAG_TO_ROLE[tag] ?? null;
|
|
1287
|
-
}
|
|
1288
|
-
function headingLevel(attrs, role) {
|
|
1289
|
-
if (role !== "heading") return null;
|
|
1290
|
-
const tagMatch = attrs.tagName.match(/^h([1-6])$/);
|
|
1291
|
-
if (tagMatch) return Number(tagMatch[1]);
|
|
1292
|
-
const ariaLevel = attrs.attributes["aria-level"];
|
|
1293
|
-
if (ariaLevel && /^\d+$/.test(ariaLevel)) return Number(ariaLevel);
|
|
1294
|
-
return null;
|
|
1295
|
-
}
|
|
1296
|
-
var attr = (a, key) => a.attributes[key] || null;
|
|
1297
|
-
var esc = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1298
|
-
var escCssAttrValue = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1299
|
-
var isCssSafeId = (id) => /^[A-Za-z][A-Za-z0-9_-]*$/.test(id);
|
|
1300
|
-
function classifyCssStability(className) {
|
|
1301
|
-
if (/[a-f0-9]{8,}/i.test(className)) return 10;
|
|
1302
|
-
if (/^(?:css|sc|emotion|styled)-/i.test(className)) return 15;
|
|
1303
|
-
if (/^(bg|text|border|shadow|opacity|font|w-|h-|m[tblrxy]?-|p[tblrxy]?-|flex|grid|gap|rounded|absolute|relative|fixed|sticky|block|inline|hidden|overflow|z-|top-|right-|bottom-|left-|inset-|justify-|items-|self-|content-|order-|col-|row-)/.test(
|
|
1304
|
-
className
|
|
1305
|
-
))
|
|
1306
|
-
return 25;
|
|
1307
|
-
if (/__/.test(className) || /--/.test(className)) return 35;
|
|
1308
|
-
if (/^[a-z]+(-[a-z]+)+$/.test(className)) return 40;
|
|
1309
|
-
if (/^[a-z]+[A-Z][a-zA-Z]+$/.test(className)) return 40;
|
|
1310
|
-
if (className.includes("_")) return 15;
|
|
1311
|
-
if (/^[a-z]+-[a-z0-9]{5,}$/.test(className) && /[0-9]/.test(className)) return 15;
|
|
1312
|
-
return 15;
|
|
1313
|
-
}
|
|
1314
|
-
function isAutoGenerated(value) {
|
|
1315
|
-
if (/^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(value)) return true;
|
|
1316
|
-
if (/^[a-f0-9]{8,}$/i.test(value)) return true;
|
|
1317
|
-
if (/^[a-z]+-\d{4,}$/.test(value)) return true;
|
|
1318
|
-
if (/^(tab|panel|input|select|option|dialog|modal|popup|tooltip|menu|listbox|combobox|checkbox|radio|textarea|button|field|label|accordion|collapse|step|slider|toggle|switch|dropdown|overlay|portal|layer)-\d+$/i.test(
|
|
1319
|
-
value
|
|
1320
|
-
)) {
|
|
1321
|
-
return true;
|
|
1240
|
+
var Parser = class {
|
|
1241
|
+
constructor(src) {
|
|
1242
|
+
this.src = src;
|
|
1243
|
+
this.i = 0;
|
|
1322
1244
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
return false;
|
|
1329
|
-
}
|
|
1330
|
-
var TEST_DATA_ATTRS = /* @__PURE__ */ new Set(["data-test", "data-test-id", "data-qa", "data-qa-id", "data-cy", "data-e2e"]);
|
|
1331
|
-
function generateAlternatives(attrs, options = {}) {
|
|
1332
|
-
const alts = [];
|
|
1333
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1334
|
-
const add = (loc) => {
|
|
1335
|
-
if (!seen.has(loc.locator)) {
|
|
1336
|
-
seen.add(loc.locator);
|
|
1337
|
-
alts.push(loc);
|
|
1338
|
-
}
|
|
1339
|
-
};
|
|
1340
|
-
const { accessibleName } = attrs;
|
|
1341
|
-
const tag = attrs.tagName;
|
|
1342
|
-
const role = options.role !== void 0 ? options.role : resolveAriaRole(attrs);
|
|
1343
|
-
const counts = attrs.selectorCounts;
|
|
1344
|
-
const isUnique = (n) => n == null || n <= 1;
|
|
1345
|
-
const ambiguityPenalty = (n) => n != null && n > 1 ? 45 : 0;
|
|
1346
|
-
const text = attrs.textContent ? attrs.textContent.replace(/\s+/g, " ").trim() : null;
|
|
1347
|
-
const testIdAttribute = options.testIdAttribute || "data-testid";
|
|
1348
|
-
const selectable = (name2) => name2.toLowerCase() !== testIdAttribute.toLowerCase();
|
|
1349
|
-
const testId = attr(attrs, testIdAttribute);
|
|
1350
|
-
if (testId && isUnique(counts?.testId)) {
|
|
1351
|
-
add({
|
|
1352
|
-
locator: `getByTestId('${esc(testId)}')`,
|
|
1353
|
-
method: "getByTestId",
|
|
1354
|
-
args: { testId },
|
|
1355
|
-
score: 100
|
|
1356
|
-
});
|
|
1245
|
+
parseTopLevel() {
|
|
1246
|
+
const chain = this.parseChain();
|
|
1247
|
+
this.skipSpace();
|
|
1248
|
+
if (this.i < this.src.length) this.fail("unexpected text");
|
|
1249
|
+
return chain;
|
|
1357
1250
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
const withLevel = (base) => level != null ? { ...base, level } : base;
|
|
1361
|
-
if (role && accessibleName) {
|
|
1362
|
-
add({
|
|
1363
|
-
locator: `getByRole('${role}', { name: '${esc(accessibleName)}'${levelPart} })`,
|
|
1364
|
-
method: "getByRole",
|
|
1365
|
-
args: withLevel({ role, name: accessibleName }),
|
|
1366
|
-
score: 90 - ambiguityPenalty(counts?.roleName)
|
|
1367
|
-
});
|
|
1251
|
+
fail(message) {
|
|
1252
|
+
throw new LocatorParseError(`${message} at "${this.src.slice(this.i, this.i + 20)}"`);
|
|
1368
1253
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
add({
|
|
1372
|
-
locator: `getByRole('${role}', { name: '${esc(ariaLabel)}'${levelPart} })`,
|
|
1373
|
-
method: "getByRole",
|
|
1374
|
-
args: withLevel({ role, name: ariaLabel }),
|
|
1375
|
-
score: 85 - ambiguityPenalty(counts?.roleName)
|
|
1376
|
-
});
|
|
1254
|
+
skipSpace() {
|
|
1255
|
+
while (this.i < this.src.length && /\s/.test(this.src[this.i])) this.i++;
|
|
1377
1256
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
const labelBacked = attrs.hasLabel === void 0 ? true : attrs.hasLabel === true || label === accessibleName;
|
|
1381
|
-
if (labelBacked) {
|
|
1382
|
-
add({
|
|
1383
|
-
locator: `getByLabel('${esc(accessibleName)}')`,
|
|
1384
|
-
method: "getByLabel",
|
|
1385
|
-
args: { label: accessibleName },
|
|
1386
|
-
score: 85
|
|
1387
|
-
});
|
|
1388
|
-
}
|
|
1257
|
+
peek() {
|
|
1258
|
+
return this.src[this.i];
|
|
1389
1259
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
method: "getByPlaceholder",
|
|
1395
|
-
args: { placeholder },
|
|
1396
|
-
score: 80 - ambiguityPenalty(counts?.placeholder)
|
|
1397
|
-
});
|
|
1260
|
+
expect(ch) {
|
|
1261
|
+
this.skipSpace();
|
|
1262
|
+
if (this.src[this.i] !== ch) this.fail(`expected "${ch}"`);
|
|
1263
|
+
this.i++;
|
|
1398
1264
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
score: 75 - ambiguityPenalty(counts?.text)
|
|
1405
|
-
});
|
|
1265
|
+
identifier() {
|
|
1266
|
+
const m = /^[A-Za-z_$][\w$]*/.exec(this.src.slice(this.i));
|
|
1267
|
+
if (!m) this.fail("expected a name");
|
|
1268
|
+
this.i += m[0].length;
|
|
1269
|
+
return m[0];
|
|
1406
1270
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1271
|
+
/**
|
|
1272
|
+
* A chain of calls joined by `.`. With `lenient`, parsing stops before the
|
|
1273
|
+
* first link that doesn't parse instead of failing, so a chain followed by
|
|
1274
|
+
* other text (an error message) is read up to where it ends. `spans` receives
|
|
1275
|
+
* where each top-level call starts and ends.
|
|
1276
|
+
*/
|
|
1277
|
+
parseChain(opts = {}) {
|
|
1278
|
+
this.skipSpace();
|
|
1279
|
+
const calls = [];
|
|
1280
|
+
for (; ; ) {
|
|
1281
|
+
const before = this.i;
|
|
1282
|
+
if (calls.length > 0) this.i++;
|
|
1283
|
+
this.skipSpace();
|
|
1284
|
+
const start = this.i;
|
|
1285
|
+
try {
|
|
1286
|
+
calls.push(this.parseCall(calls.length === 0));
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
if (!opts.lenient || calls.length === 0) throw error;
|
|
1289
|
+
this.i = before;
|
|
1290
|
+
break;
|
|
1291
|
+
}
|
|
1292
|
+
opts.spans?.push({ start, end: this.i });
|
|
1293
|
+
const end = this.i;
|
|
1294
|
+
this.skipSpace();
|
|
1295
|
+
if (this.peek() !== ".") {
|
|
1296
|
+
this.i = end;
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return { calls };
|
|
1416
1301
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
add({
|
|
1421
|
-
locator: `locator('${esc(selector)}')`,
|
|
1422
|
-
method: "locator",
|
|
1423
|
-
args: { selector },
|
|
1424
|
-
score: 60
|
|
1425
|
-
});
|
|
1302
|
+
/** Where the parser stopped. */
|
|
1303
|
+
get position() {
|
|
1304
|
+
return this.i;
|
|
1426
1305
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1306
|
+
parseCall(first) {
|
|
1307
|
+
this.skipSpace();
|
|
1308
|
+
const method = this.identifier();
|
|
1309
|
+
if (!LOCATOR_CHAIN_METHODS.has(method)) this.fail(`unsupported method ${method}()`);
|
|
1310
|
+
if (first && !LOCATING_METHODS.has(method)) this.fail(`${method}() needs a locator before it`);
|
|
1311
|
+
this.expect("(");
|
|
1312
|
+
const args = [];
|
|
1313
|
+
this.skipSpace();
|
|
1314
|
+
if (this.peek() === ")") {
|
|
1315
|
+
this.i++;
|
|
1316
|
+
return { method, args };
|
|
1317
|
+
}
|
|
1318
|
+
for (; ; ) {
|
|
1319
|
+
args.push(this.parseValue());
|
|
1320
|
+
this.skipSpace();
|
|
1321
|
+
if (this.peek() === ",") {
|
|
1322
|
+
this.i++;
|
|
1323
|
+
this.skipSpace();
|
|
1324
|
+
if (this.peek() === ")") {
|
|
1325
|
+
this.i++;
|
|
1326
|
+
break;
|
|
1327
|
+
}
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
this.expect(")");
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1333
|
+
return { method, args };
|
|
1435
1334
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1335
|
+
parseValue() {
|
|
1336
|
+
this.skipSpace();
|
|
1337
|
+
const ch = this.peek();
|
|
1338
|
+
if (ch === "'" || ch === '"' || ch === "`") return { type: "string", value: this.parseString() };
|
|
1339
|
+
if (ch === "/") return this.parseRegex();
|
|
1340
|
+
if (ch === "{") return this.parseObject();
|
|
1341
|
+
if (ch !== void 0 && /[-\d]/.test(ch)) {
|
|
1342
|
+
const m = /^-?\d+(?:\.\d+)?/.exec(this.src.slice(this.i));
|
|
1343
|
+
if (!m) this.fail("expected a number");
|
|
1344
|
+
this.i += m[0].length;
|
|
1345
|
+
return { type: "number", value: Number(m[0]) };
|
|
1346
|
+
}
|
|
1347
|
+
const rest = this.src.slice(this.i);
|
|
1348
|
+
if (/^true\b/.test(rest)) {
|
|
1349
|
+
this.i += 4;
|
|
1350
|
+
return { type: "boolean", value: true };
|
|
1351
|
+
}
|
|
1352
|
+
if (/^false\b/.test(rest)) {
|
|
1353
|
+
this.i += 5;
|
|
1354
|
+
return { type: "boolean", value: false };
|
|
1355
|
+
}
|
|
1356
|
+
if (/^[A-Za-z_$]/.test(rest)) return { type: "chain", chain: this.parseChain() };
|
|
1357
|
+
return this.fail("expected a value");
|
|
1444
1358
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
let
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
testIdAnchorDone = true;
|
|
1456
|
-
add({
|
|
1457
|
-
locator: `getByTestId('${esc(anc.testId)}').${leaf.expr}`,
|
|
1458
|
-
method: leaf.method,
|
|
1459
|
-
args: { ...leaf.args, anchorTestId: anc.testId },
|
|
1460
|
-
score: scores.testId
|
|
1461
|
-
});
|
|
1462
|
-
}
|
|
1463
|
-
if (!idAnchorDone && anc.id && selectable("id") && !isAutoGenerated(anc.id) && anc.idCount === 1) {
|
|
1464
|
-
idAnchorDone = true;
|
|
1465
|
-
const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
|
|
1466
|
-
add({
|
|
1467
|
-
locator: `locator('${esc(anchorSelector)}').${leaf.expr}`,
|
|
1468
|
-
method: leaf.method,
|
|
1469
|
-
args: { ...leaf.args, anchorSelector },
|
|
1470
|
-
score: scores.id
|
|
1471
|
-
});
|
|
1359
|
+
/** A quoted string, decoding the escapes `JSON.stringify` and JavaScript produce. */
|
|
1360
|
+
parseString() {
|
|
1361
|
+
const quote = this.src[this.i];
|
|
1362
|
+
let out = "";
|
|
1363
|
+
this.i++;
|
|
1364
|
+
while (this.i < this.src.length) {
|
|
1365
|
+
const c = this.src[this.i];
|
|
1366
|
+
if (c === quote) {
|
|
1367
|
+
this.i++;
|
|
1368
|
+
return out;
|
|
1472
1369
|
}
|
|
1473
|
-
if (
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
add({
|
|
1478
|
-
locator: `locator('${esc(anchorSelector)}').${leaf.expr}`,
|
|
1479
|
-
method: leaf.method,
|
|
1480
|
-
args: { ...leaf.args, anchorSelector },
|
|
1481
|
-
score: TEST_DATA_ATTRS.has(name2) ? scores.testData : scores.data
|
|
1482
|
-
});
|
|
1370
|
+
if (c !== "\\") {
|
|
1371
|
+
out += c;
|
|
1372
|
+
this.i++;
|
|
1373
|
+
continue;
|
|
1483
1374
|
}
|
|
1484
|
-
const
|
|
1485
|
-
if (
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
score: scores.role
|
|
1492
|
-
});
|
|
1375
|
+
const next = this.src[this.i + 1];
|
|
1376
|
+
if (next === void 0) break;
|
|
1377
|
+
const simple = SIMPLE_ESCAPES[next];
|
|
1378
|
+
if (simple !== void 0) {
|
|
1379
|
+
out += simple;
|
|
1380
|
+
this.i += 2;
|
|
1381
|
+
continue;
|
|
1493
1382
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
args: { ...leaf.args, anchorRole: ancestorRole, anchorHasText: anc.filterText },
|
|
1500
|
-
score: scores.filter
|
|
1501
|
-
});
|
|
1383
|
+
const hex = next === "u" && this.src[this.i + 2] === "{" ? /^u\{([0-9a-fA-F]{1,6})\}/.exec(this.src.slice(this.i + 1)) : next === "u" ? /^u([0-9a-fA-F]{4})/.exec(this.src.slice(this.i + 1)) : next === "x" ? /^x([0-9a-fA-F]{2})/.exec(this.src.slice(this.i + 1)) : null;
|
|
1384
|
+
if (hex) {
|
|
1385
|
+
out += String.fromCodePoint(parseInt(hex[1], 16));
|
|
1386
|
+
this.i += 1 + hex[0].length;
|
|
1387
|
+
continue;
|
|
1502
1388
|
}
|
|
1389
|
+
if (next === "u" || next === "x") this.fail("malformed escape");
|
|
1390
|
+
out += next;
|
|
1391
|
+
this.i += 2;
|
|
1503
1392
|
}
|
|
1504
|
-
|
|
1505
|
-
if (role && !hasOwnTestId) {
|
|
1506
|
-
const rolePart = level != null ? `'${role}', { level: ${level} }` : `'${role}'`;
|
|
1507
|
-
const leafArgs = withLevel({ role });
|
|
1508
|
-
addAnchoredChains(
|
|
1509
|
-
{ expr: `getByRole(${rolePart})`, method: "getByRole", args: leafArgs, role },
|
|
1510
|
-
(anc) => anc.scopedRoleCount,
|
|
1511
|
-
{ testId: 72, testData: 70, id: 64, data: 60, role: 55, filter: 53 }
|
|
1512
|
-
);
|
|
1513
|
-
const pos = attrs.rolePosition;
|
|
1514
|
-
if (pos && pos.role === role && (pos.count === 1 || level != null && pos.levelCount === 1)) {
|
|
1515
|
-
add({
|
|
1516
|
-
locator: `getByRole(${rolePart})`,
|
|
1517
|
-
method: "getByRole",
|
|
1518
|
-
args: leafArgs,
|
|
1519
|
-
score: 58
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
} else if (!role && text && text.length < 80 && !hasOwnTestId) {
|
|
1523
|
-
addAnchoredChains(
|
|
1524
|
-
{ expr: `getByText('${esc(text)}')`, method: "getByText", args: { text }, role: null },
|
|
1525
|
-
(anc) => anc.scopedTextCount,
|
|
1526
|
-
{ testId: 68, testData: 66, id: 60, data: 56, role: 51, filter: 49 }
|
|
1527
|
-
);
|
|
1393
|
+
return this.fail("unterminated string");
|
|
1528
1394
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1395
|
+
parseRegex() {
|
|
1396
|
+
let j = this.i + 1;
|
|
1397
|
+
let inClass = false;
|
|
1398
|
+
while (j < this.src.length) {
|
|
1399
|
+
const c = this.src[j];
|
|
1400
|
+
if (c === "\\") {
|
|
1401
|
+
j += 2;
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
if (c === "[") inClass = true;
|
|
1405
|
+
else if (c === "]") inClass = false;
|
|
1406
|
+
else if (c === "/" && !inClass) break;
|
|
1407
|
+
j++;
|
|
1540
1408
|
}
|
|
1409
|
+
if (j >= this.src.length) this.fail("unterminated regex");
|
|
1410
|
+
const source = this.src.slice(this.i + 1, j);
|
|
1411
|
+
const flags = /^[a-z]*/.exec(this.src.slice(j + 1))[0];
|
|
1412
|
+
this.i = j + 1 + flags.length;
|
|
1413
|
+
return { type: "regex", source, flags };
|
|
1414
|
+
}
|
|
1415
|
+
parseObject() {
|
|
1416
|
+
this.expect("{");
|
|
1417
|
+
const entries = [];
|
|
1418
|
+
for (; ; ) {
|
|
1419
|
+
this.skipSpace();
|
|
1420
|
+
if (this.peek() === "}") {
|
|
1421
|
+
this.i++;
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
const ch = this.peek();
|
|
1425
|
+
const key = ch === "'" || ch === '"' ? this.parseString() : this.identifier();
|
|
1426
|
+
this.expect(":");
|
|
1427
|
+
entries.push([key, this.parseValue()]);
|
|
1428
|
+
this.skipSpace();
|
|
1429
|
+
if (this.peek() === ",") {
|
|
1430
|
+
this.i++;
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
this.expect("}");
|
|
1434
|
+
break;
|
|
1435
|
+
}
|
|
1436
|
+
return { type: "object", entries };
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
function parseLocatorChain(expr) {
|
|
1440
|
+
const trimmed = expr.trim().replace(/^(?:await\s+)?(?:this\.)?page\./, "");
|
|
1441
|
+
if (!trimmed) throw new LocatorParseError("empty expression");
|
|
1442
|
+
return new Parser(trimmed).parseTopLevel();
|
|
1443
|
+
}
|
|
1444
|
+
function tryParseLocatorChain(expr) {
|
|
1445
|
+
try {
|
|
1446
|
+
return parseLocatorChain(expr);
|
|
1447
|
+
} catch {
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
function quoteJsString(value) {
|
|
1452
|
+
const json = JSON.stringify(value).slice(1, -1);
|
|
1453
|
+
return `'${json.replace(/\\"/g, '"').replace(/'/g, "\\'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}'`;
|
|
1454
|
+
}
|
|
1455
|
+
function parseLeafLocatorCall(expr) {
|
|
1456
|
+
const chain = tryParseLocatorChain(expr);
|
|
1457
|
+
if (!chain) return null;
|
|
1458
|
+
for (let i = chain.calls.length - 1; i >= 0; i--) {
|
|
1459
|
+
if (LOCATING_METHODS.has(chain.calls[i].method)) return chain.calls[i];
|
|
1541
1460
|
}
|
|
1542
|
-
return alts.sort((a, b) => b.score - a.score);
|
|
1543
|
-
}
|
|
1544
|
-
function approximateAccessibleName(attrs) {
|
|
1545
|
-
const a = attrs.attributes;
|
|
1546
|
-
const labelText = attrs.labelText || null;
|
|
1547
|
-
if (a["aria-labelledby"] && labelText) return labelText;
|
|
1548
|
-
const ariaLabel = a["aria-label"];
|
|
1549
|
-
if (ariaLabel) return ariaLabel;
|
|
1550
|
-
if (["input", "select", "textarea"].includes(attrs.tagName)) {
|
|
1551
|
-
if (labelText) return labelText;
|
|
1552
|
-
} else if (attrs.textContent) {
|
|
1553
|
-
return attrs.textContent;
|
|
1554
|
-
}
|
|
1555
|
-
const title = a["title"];
|
|
1556
|
-
if (title) return title;
|
|
1557
|
-
const placeholder = a["placeholder"];
|
|
1558
|
-
if (placeholder) return placeholder;
|
|
1559
1461
|
return null;
|
|
1560
1462
|
}
|
|
1463
|
+
function locatorCallValues(call, opts = {}) {
|
|
1464
|
+
const value = (arg) => {
|
|
1465
|
+
switch (arg.type) {
|
|
1466
|
+
case "string":
|
|
1467
|
+
case "number":
|
|
1468
|
+
case "boolean":
|
|
1469
|
+
return arg.value;
|
|
1470
|
+
case "regex":
|
|
1471
|
+
return opts.regexAsText ? `/${arg.source}/${arg.flags}` : void 0;
|
|
1472
|
+
case "object": {
|
|
1473
|
+
const out = {};
|
|
1474
|
+
for (const [key, v] of arg.entries) {
|
|
1475
|
+
const plain = value(v);
|
|
1476
|
+
if (plain !== void 0) out[key] = plain;
|
|
1477
|
+
}
|
|
1478
|
+
return out;
|
|
1479
|
+
}
|
|
1480
|
+
case "chain":
|
|
1481
|
+
return void 0;
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
return call.args.map(value).filter((v) => v !== void 0);
|
|
1485
|
+
}
|
|
1561
1486
|
|
|
1562
|
-
// ../
|
|
1563
|
-
var
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1487
|
+
// ../core/src/locator-generation.ts
|
|
1488
|
+
var TAG_TO_ROLE = {
|
|
1489
|
+
a: "link",
|
|
1490
|
+
button: "button",
|
|
1491
|
+
nav: "navigation",
|
|
1492
|
+
main: "main",
|
|
1493
|
+
article: "article",
|
|
1494
|
+
section: "region",
|
|
1495
|
+
form: "form",
|
|
1496
|
+
img: "img",
|
|
1497
|
+
figure: "figure",
|
|
1498
|
+
figcaption: "caption",
|
|
1499
|
+
blockquote: "blockquote",
|
|
1500
|
+
table: "table",
|
|
1501
|
+
caption: "caption",
|
|
1502
|
+
thead: "rowgroup",
|
|
1503
|
+
tbody: "rowgroup",
|
|
1504
|
+
tfoot: "rowgroup",
|
|
1505
|
+
tr: "row",
|
|
1506
|
+
td: "cell",
|
|
1507
|
+
ul: "list",
|
|
1508
|
+
ol: "list",
|
|
1509
|
+
li: "listitem",
|
|
1510
|
+
dialog: "dialog",
|
|
1511
|
+
output: "status",
|
|
1512
|
+
progress: "progressbar",
|
|
1513
|
+
meter: "meter",
|
|
1514
|
+
textarea: "textbox",
|
|
1515
|
+
h1: "heading",
|
|
1516
|
+
h2: "heading",
|
|
1517
|
+
h3: "heading",
|
|
1518
|
+
h4: "heading",
|
|
1519
|
+
h5: "heading",
|
|
1520
|
+
h6: "heading",
|
|
1521
|
+
details: "group",
|
|
1522
|
+
summary: "button",
|
|
1523
|
+
search: "search"
|
|
1524
|
+
};
|
|
1525
|
+
var INPUT_TYPE_TO_ROLE = {
|
|
1526
|
+
button: "button",
|
|
1527
|
+
submit: "button",
|
|
1528
|
+
reset: "button",
|
|
1529
|
+
image: "button",
|
|
1530
|
+
checkbox: "checkbox",
|
|
1531
|
+
radio: "radio",
|
|
1532
|
+
range: "slider",
|
|
1533
|
+
search: "searchbox",
|
|
1534
|
+
number: "spinbutton",
|
|
1535
|
+
text: "textbox",
|
|
1536
|
+
email: "textbox",
|
|
1537
|
+
tel: "textbox",
|
|
1538
|
+
url: "textbox",
|
|
1539
|
+
password: "textbox"
|
|
1540
|
+
};
|
|
1541
|
+
var CAPTURED_ATTRIBUTES = [
|
|
1542
|
+
"id",
|
|
1543
|
+
"class",
|
|
1544
|
+
"name",
|
|
1545
|
+
"data-testid",
|
|
1546
|
+
"placeholder",
|
|
1547
|
+
"alt",
|
|
1548
|
+
"title",
|
|
1549
|
+
"aria-label",
|
|
1550
|
+
"aria-labelledby",
|
|
1551
|
+
"aria-level",
|
|
1552
|
+
"role",
|
|
1553
|
+
"type",
|
|
1554
|
+
"href",
|
|
1555
|
+
"multiple"
|
|
1556
|
+
];
|
|
1557
|
+
function resolveAriaRole(attrs) {
|
|
1558
|
+
const explicit = attrs.attributes["role"];
|
|
1559
|
+
if (explicit) return explicit;
|
|
1560
|
+
const tag = attrs.tagName;
|
|
1561
|
+
if (!tag) return null;
|
|
1562
|
+
if (tag === "input") {
|
|
1563
|
+
const type = (attrs.attributes["type"] ?? "text").toLowerCase();
|
|
1564
|
+
return INPUT_TYPE_TO_ROLE[type] ?? "textbox";
|
|
1573
1565
|
}
|
|
1574
|
-
if (
|
|
1575
|
-
return
|
|
1576
|
-
code: `getByRole('${escStr(a.role)}', { name: '${escStr(a.ariaLabel)}' })`,
|
|
1577
|
-
kind: "labeledRole",
|
|
1578
|
-
flatArgs: { anchorRole: a.role, anchorName: a.ariaLabel }
|
|
1579
|
-
};
|
|
1566
|
+
if (tag === "select") {
|
|
1567
|
+
return attrs.attributes["multiple"] != null ? "listbox" : "combobox";
|
|
1580
1568
|
}
|
|
1581
|
-
if (
|
|
1582
|
-
return
|
|
1569
|
+
if (tag === "a") {
|
|
1570
|
+
return attrs.attributes["href"] != null ? "link" : null;
|
|
1583
1571
|
}
|
|
1572
|
+
return TAG_TO_ROLE[tag] ?? null;
|
|
1573
|
+
}
|
|
1574
|
+
function headingLevel(attrs, role) {
|
|
1575
|
+
if (role !== "heading") return null;
|
|
1576
|
+
const tagMatch = attrs.tagName.match(/^h([1-6])$/);
|
|
1577
|
+
if (tagMatch) return Number(tagMatch[1]);
|
|
1578
|
+
const ariaLevel = attrs.attributes["aria-level"];
|
|
1579
|
+
if (ariaLevel && /^\d+$/.test(ariaLevel)) return Number(ariaLevel);
|
|
1584
1580
|
return null;
|
|
1585
1581
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1582
|
+
var attr = (a, key) => a.attributes[key] || null;
|
|
1583
|
+
var escCssAttrValue = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1584
|
+
var isCssSafeId = (id) => /^[A-Za-z][A-Za-z0-9_-]*$/.test(id);
|
|
1585
|
+
function classifyCssStability(className) {
|
|
1586
|
+
if (/[a-f0-9]{8,}/i.test(className)) return 10;
|
|
1587
|
+
if (/^(?:css|sc|emotion|styled)-/i.test(className)) return 15;
|
|
1588
|
+
if (/^(bg|text|border|shadow|opacity|font|w-|h-|m[tblrxy]?-|p[tblrxy]?-|flex|grid|gap|rounded|absolute|relative|fixed|sticky|block|inline|hidden|overflow|z-|top-|right-|bottom-|left-|inset-|justify-|items-|self-|content-|order-|col-|row-)/.test(
|
|
1589
|
+
className
|
|
1590
|
+
))
|
|
1591
|
+
return 25;
|
|
1592
|
+
if (/__/.test(className) || /--/.test(className)) return 35;
|
|
1593
|
+
if (/^[a-z]+(-[a-z]+)+$/.test(className)) return 40;
|
|
1594
|
+
if (/^[a-z]+[A-Z][a-zA-Z]+$/.test(className)) return 40;
|
|
1595
|
+
if (className.includes("_")) return 15;
|
|
1596
|
+
if (/^[a-z]+-[a-z0-9]{5,}$/.test(className) && /[0-9]/.test(className)) return 15;
|
|
1597
|
+
return 15;
|
|
1598
|
+
}
|
|
1599
|
+
function isAutoGenerated(value) {
|
|
1600
|
+
if (/^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(value)) return true;
|
|
1601
|
+
if (/^[a-f0-9]{8,}$/i.test(value)) return true;
|
|
1602
|
+
if (/^[a-z]+-\d{4,}$/.test(value)) return true;
|
|
1603
|
+
if (/^(tab|panel|input|select|option|dialog|modal|popup|tooltip|menu|listbox|combobox|checkbox|radio|textarea|button|field|label|accordion|collapse|step|slider|toggle|switch|dropdown|overlay|portal|layer)-\d+$/i.test(
|
|
1604
|
+
value
|
|
1605
|
+
)) {
|
|
1606
|
+
return true;
|
|
1607
|
+
}
|
|
1608
|
+
if (/^(emotion-|styled-|css-|sc-)/.test(value)) return true;
|
|
1609
|
+
if (value.startsWith("ng-")) return true;
|
|
1610
|
+
if (/^(radix-|headlessui-|mui-|mantine-|chakra-|reka-|bits-|downshift-|react-aria|el-id-)/i.test(value)) return true;
|
|
1611
|
+
if (/^:r[0-9a-z]+:$/i.test(value) || /^«r[0-9a-z]+»$/i.test(value)) return true;
|
|
1612
|
+
if (/(^|[-_:])v-\d+(-\d+)+$/.test(value) || /(^|[-_:])v-\d+(-\d+)+[-_:]/.test(value)) return true;
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
var TEST_DATA_ATTRS = /* @__PURE__ */ new Set(["data-test", "data-test-id", "data-qa", "data-qa-id", "data-cy", "data-e2e"]);
|
|
1616
|
+
function generateAlternatives(attrs, options = {}) {
|
|
1617
|
+
const alts = [];
|
|
1589
1618
|
const seen = /* @__PURE__ */ new Set();
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
seen.add(l.locator);
|
|
1595
|
-
out.push(l);
|
|
1619
|
+
const add = (loc) => {
|
|
1620
|
+
if (!seen.has(loc.locator)) {
|
|
1621
|
+
seen.add(loc.locator);
|
|
1622
|
+
alts.push(loc);
|
|
1596
1623
|
}
|
|
1597
1624
|
};
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1625
|
+
const { accessibleName } = attrs;
|
|
1626
|
+
const tag = attrs.tagName;
|
|
1627
|
+
const role = options.role !== void 0 ? options.role : resolveAriaRole(attrs);
|
|
1628
|
+
const counts = attrs.selectorCounts;
|
|
1629
|
+
const isUnique = (n) => n == null || n <= 1;
|
|
1630
|
+
const ambiguityPenalty = (n) => n != null && n > 1 ? 45 : 0;
|
|
1631
|
+
const text = attrs.textContent ? attrs.textContent.replace(/\s+/g, " ").trim() : null;
|
|
1632
|
+
const testIdAttribute = options.testIdAttribute || "data-testid";
|
|
1633
|
+
const selectable = (name2) => name2.toLowerCase() !== testIdAttribute.toLowerCase();
|
|
1634
|
+
const testId = attr(attrs, testIdAttribute);
|
|
1635
|
+
if (testId && isUnique(counts?.testId)) {
|
|
1602
1636
|
add({
|
|
1603
|
-
locator:
|
|
1637
|
+
locator: `getByTestId(${quoteJsString(testId)})`,
|
|
1638
|
+
method: "getByTestId",
|
|
1639
|
+
args: { testId },
|
|
1640
|
+
score: 100
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
const level = headingLevel(attrs, role);
|
|
1644
|
+
const levelPart = level != null ? `, level: ${level}` : "";
|
|
1645
|
+
const withLevel = (base) => level != null ? { ...base, level } : base;
|
|
1646
|
+
if (role && accessibleName) {
|
|
1647
|
+
add({
|
|
1648
|
+
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(accessibleName)}${levelPart} })`,
|
|
1604
1649
|
method: "getByRole",
|
|
1605
|
-
args: {
|
|
1606
|
-
score:
|
|
1650
|
+
args: withLevel({ role, name: accessibleName }),
|
|
1651
|
+
score: 90 - ambiguityPenalty(counts?.roleName)
|
|
1607
1652
|
});
|
|
1608
1653
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1654
|
+
const ariaLabel = attr(attrs, "aria-label");
|
|
1655
|
+
if (role && ariaLabel && ariaLabel !== accessibleName) {
|
|
1656
|
+
add({
|
|
1657
|
+
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(ariaLabel)}${levelPart} })`,
|
|
1658
|
+
method: "getByRole",
|
|
1659
|
+
args: withLevel({ role, name: ariaLabel }),
|
|
1660
|
+
score: 85 - ambiguityPenalty(counts?.roleName)
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
if (accessibleName && ["input", "select", "textarea"].includes(tag)) {
|
|
1664
|
+
const label = attr(attrs, "aria-label");
|
|
1665
|
+
const labelBacked = attrs.hasLabel === void 0 ? true : attrs.hasLabel === true || label === accessibleName;
|
|
1666
|
+
if (labelBacked) {
|
|
1667
|
+
add({
|
|
1668
|
+
locator: `getByLabel(${quoteJsString(accessibleName)})`,
|
|
1669
|
+
method: "getByLabel",
|
|
1670
|
+
args: { label: accessibleName },
|
|
1671
|
+
score: 85
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
const placeholder = attr(attrs, "placeholder");
|
|
1676
|
+
if (placeholder) {
|
|
1677
|
+
add({
|
|
1678
|
+
locator: `getByPlaceholder(${quoteJsString(placeholder)})`,
|
|
1679
|
+
method: "getByPlaceholder",
|
|
1680
|
+
args: { placeholder },
|
|
1681
|
+
score: 80 - ambiguityPenalty(counts?.placeholder)
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
if (text && text.length < 80) {
|
|
1685
|
+
add({
|
|
1686
|
+
locator: `getByText(${quoteJsString(text)})`,
|
|
1687
|
+
method: "getByText",
|
|
1688
|
+
args: { text },
|
|
1689
|
+
score: 75 - ambiguityPenalty(counts?.text)
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
const id = attr(attrs, "id");
|
|
1693
|
+
if (id && selectable("id") && !isAutoGenerated(id) && isUnique(counts?.id)) {
|
|
1694
|
+
const selector = isCssSafeId(id) ? `#${id}` : `[id="${escCssAttrValue(id)}"]`;
|
|
1695
|
+
add({
|
|
1696
|
+
locator: `locator(${quoteJsString(selector)})`,
|
|
1697
|
+
method: "locator",
|
|
1698
|
+
args: { selector },
|
|
1699
|
+
score: 65
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
const name = attr(attrs, "name");
|
|
1703
|
+
if (name && selectable("name") && isUnique(counts?.name)) {
|
|
1704
|
+
const selector = `[name="${escCssAttrValue(name)}"]`;
|
|
1705
|
+
add({
|
|
1706
|
+
locator: `locator(${quoteJsString(selector)})`,
|
|
1707
|
+
method: "locator",
|
|
1708
|
+
args: { selector },
|
|
1709
|
+
score: 60
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
const alt = attr(attrs, "alt");
|
|
1713
|
+
if (alt) {
|
|
1714
|
+
add({
|
|
1715
|
+
locator: `getByAltText(${quoteJsString(alt)})`,
|
|
1716
|
+
method: "getByAltText",
|
|
1717
|
+
args: { text: alt },
|
|
1718
|
+
score: 60 - ambiguityPenalty(counts?.alt)
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
const title = attr(attrs, "title");
|
|
1722
|
+
if (title) {
|
|
1723
|
+
add({
|
|
1724
|
+
locator: `getByTitle(${quoteJsString(title)})`,
|
|
1725
|
+
method: "getByTitle",
|
|
1726
|
+
args: { title },
|
|
1727
|
+
score: 50 - ambiguityPenalty(counts?.title)
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
const hasOwnTestId = !!(testId && isUnique(counts?.testId));
|
|
1731
|
+
const addAnchoredChains = (leaf, scopedCount, scores) => {
|
|
1732
|
+
let testIdAnchorDone = false;
|
|
1733
|
+
let idAnchorDone = false;
|
|
1734
|
+
let roleAnchorDone = false;
|
|
1735
|
+
let dataAnchorDone = false;
|
|
1736
|
+
let filterAnchorDone = false;
|
|
1737
|
+
for (const anc of attrs.ancestors ?? []) {
|
|
1738
|
+
if (scopedCount(anc) !== 1) continue;
|
|
1739
|
+
if (!testIdAnchorDone && anc.testId && anc.testIdCount === 1) {
|
|
1740
|
+
testIdAnchorDone = true;
|
|
1741
|
+
add({
|
|
1742
|
+
locator: `getByTestId(${quoteJsString(anc.testId)}).${leaf.expr}`,
|
|
1743
|
+
method: leaf.method,
|
|
1744
|
+
args: { ...leaf.args, anchorTestId: anc.testId },
|
|
1745
|
+
score: scores.testId
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
if (!idAnchorDone && anc.id && selectable("id") && !isAutoGenerated(anc.id) && anc.idCount === 1) {
|
|
1749
|
+
idAnchorDone = true;
|
|
1750
|
+
const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
|
|
1751
|
+
add({
|
|
1752
|
+
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
1753
|
+
method: leaf.method,
|
|
1754
|
+
args: { ...leaf.args, anchorSelector },
|
|
1755
|
+
score: scores.id
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
if (!dataAnchorDone && anc.dataAttr && selectable(anc.dataAttr.name) && anc.dataAttrCount === 1) {
|
|
1759
|
+
dataAnchorDone = true;
|
|
1760
|
+
const { name: name2, value } = anc.dataAttr;
|
|
1761
|
+
const anchorSelector = `[${name2}="${escCssAttrValue(value)}"]`;
|
|
1762
|
+
add({
|
|
1763
|
+
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
1764
|
+
method: leaf.method,
|
|
1765
|
+
args: { ...leaf.args, anchorSelector },
|
|
1766
|
+
score: TEST_DATA_ATTRS.has(name2) ? scores.testData : scores.data
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
const ancestorRole = anc.role || TAG_TO_ROLE[anc.tag] || null;
|
|
1770
|
+
if (!roleAnchorDone && ancestorRole && ancestorRole !== leaf.role && anc.roleCount === 1) {
|
|
1771
|
+
roleAnchorDone = true;
|
|
1772
|
+
add({
|
|
1773
|
+
locator: `getByRole(${quoteJsString(ancestorRole)}).${leaf.expr}`,
|
|
1774
|
+
method: leaf.method,
|
|
1775
|
+
args: { ...leaf.args, anchorRole: ancestorRole },
|
|
1776
|
+
score: scores.role
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
if (!filterAnchorDone && ancestorRole && anc.filterText && anc.filterRoleCount === 1) {
|
|
1780
|
+
filterAnchorDone = true;
|
|
1781
|
+
add({
|
|
1782
|
+
locator: `getByRole(${quoteJsString(ancestorRole)}).filter({ hasText: ${quoteJsString(anc.filterText)} }).${leaf.expr}`,
|
|
1783
|
+
method: leaf.method,
|
|
1784
|
+
args: { ...leaf.args, anchorRole: ancestorRole, anchorHasText: anc.filterText },
|
|
1785
|
+
score: scores.filter
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
};
|
|
1790
|
+
if (role && !hasOwnTestId) {
|
|
1791
|
+
const rolePart = level != null ? `${quoteJsString(role)}, { level: ${level} }` : quoteJsString(role);
|
|
1792
|
+
const leafArgs = withLevel({ role });
|
|
1793
|
+
addAnchoredChains(
|
|
1794
|
+
{ expr: `getByRole(${rolePart})`, method: "getByRole", args: leafArgs, role },
|
|
1795
|
+
(anc) => anc.scopedRoleCount,
|
|
1796
|
+
{ testId: 72, testData: 70, id: 64, data: 60, role: 55, filter: 53 }
|
|
1797
|
+
);
|
|
1798
|
+
const pos = attrs.rolePosition;
|
|
1799
|
+
if (pos && pos.role === role && (pos.count === 1 || level != null && pos.levelCount === 1)) {
|
|
1800
|
+
add({
|
|
1801
|
+
locator: `getByRole(${rolePart})`,
|
|
1802
|
+
method: "getByRole",
|
|
1803
|
+
args: leafArgs,
|
|
1804
|
+
score: 58
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
} else if (!role && text && text.length < 80 && !hasOwnTestId) {
|
|
1808
|
+
addAnchoredChains(
|
|
1809
|
+
{ expr: `getByText(${quoteJsString(text)})`, method: "getByText", args: { text }, role: null },
|
|
1810
|
+
(anc) => anc.scopedTextCount,
|
|
1811
|
+
{ testId: 68, testData: 66, id: 60, data: 56, role: 51, filter: 49 }
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
const clsStr = attr(attrs, "class");
|
|
1815
|
+
if (clsStr) {
|
|
1816
|
+
const countsClasses = counts?.classes;
|
|
1817
|
+
const classes = clsStr.split(/\s+/).filter((c) => c.length > 1 && /^[A-Za-z_-][A-Za-z0-9_-]*$/.test(c) && isUnique(countsClasses?.[c])).map((cls) => ({ cls, score: classifyCssStability(cls) })).sort((a, b) => b.score - a.score).slice(0, 3);
|
|
1818
|
+
for (const { cls, score } of classes) {
|
|
1819
|
+
add({
|
|
1820
|
+
locator: `locator(${quoteJsString(`.${cls}`)})`,
|
|
1821
|
+
method: "locator",
|
|
1822
|
+
args: { selector: `.${cls}` },
|
|
1823
|
+
score
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
return alts.sort((a, b) => b.score - a.score);
|
|
1828
|
+
}
|
|
1829
|
+
function approximateAccessibleName(attrs) {
|
|
1830
|
+
const a = attrs.attributes;
|
|
1831
|
+
const labelText = attrs.labelText || null;
|
|
1832
|
+
if (a["aria-labelledby"] && labelText) return labelText;
|
|
1833
|
+
const ariaLabel = a["aria-label"];
|
|
1834
|
+
if (ariaLabel) return ariaLabel;
|
|
1835
|
+
if (["input", "select", "textarea"].includes(attrs.tagName)) {
|
|
1836
|
+
if (labelText) return labelText;
|
|
1837
|
+
} else if (attrs.textContent) {
|
|
1838
|
+
return attrs.textContent;
|
|
1839
|
+
}
|
|
1840
|
+
const title = a["title"];
|
|
1841
|
+
if (title) return title;
|
|
1842
|
+
const placeholder = a["placeholder"];
|
|
1843
|
+
if (placeholder) return placeholder;
|
|
1844
|
+
return null;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// ../picker-dom/src/anchor-alternatives.ts
|
|
1848
|
+
var cssIdSelector = (id) => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(id) ? `#${id}` : `[id="${id.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"]`;
|
|
1849
|
+
var ANCHOR_KIND_SCORES = { testid: 80, id: 74, labeledRole: 68, role: 62 };
|
|
1850
|
+
function anchorSegment(a) {
|
|
1851
|
+
if (a.testId && a.testIdCount === 1) {
|
|
1852
|
+
return { code: `getByTestId(${quoteJsString(a.testId)})`, kind: "testid", flatArgs: { anchorTestId: a.testId } };
|
|
1853
|
+
}
|
|
1854
|
+
if (a.id && !isAutoGenerated(a.id) && a.idCount === 1) {
|
|
1855
|
+
const anchorSelector = cssIdSelector(a.id);
|
|
1856
|
+
return { code: `locator(${quoteJsString(anchorSelector)})`, kind: "id", flatArgs: { anchorSelector } };
|
|
1857
|
+
}
|
|
1858
|
+
if (a.role && a.ariaLabel && a.labeledRoleCount === 1) {
|
|
1859
|
+
return {
|
|
1860
|
+
code: `getByRole(${quoteJsString(a.role)}, { name: ${quoteJsString(a.ariaLabel)} })`,
|
|
1861
|
+
kind: "labeledRole",
|
|
1862
|
+
flatArgs: { anchorRole: a.role, anchorName: a.ariaLabel }
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
if (a.role && a.roleCount === 1) {
|
|
1866
|
+
return { code: `getByRole(${quoteJsString(a.role)})`, kind: "role", flatArgs: { anchorRole: a.role } };
|
|
1867
|
+
}
|
|
1868
|
+
return null;
|
|
1869
|
+
}
|
|
1870
|
+
function generateAnchoredAlternatives(leaf, anchors, chainLeafCount) {
|
|
1871
|
+
if (!leaf.role || anchors.length === 0) return [];
|
|
1872
|
+
const out = [];
|
|
1873
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1874
|
+
const rolePart = leaf.level != null ? `${quoteJsString(leaf.role)}, { level: ${leaf.level} }` : quoteJsString(leaf.role);
|
|
1875
|
+
const leafArgs = leaf.level != null ? { role: leaf.role, level: leaf.level } : { role: leaf.role };
|
|
1876
|
+
const add = (l) => {
|
|
1877
|
+
if (!seen.has(l.locator)) {
|
|
1878
|
+
seen.add(l.locator);
|
|
1879
|
+
out.push(l);
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
for (const anchor of anchors) {
|
|
1883
|
+
if (anchor.scopedLeafCount !== 1) continue;
|
|
1884
|
+
const seg = anchorSegment(anchor);
|
|
1885
|
+
if (!seg) continue;
|
|
1886
|
+
add({
|
|
1887
|
+
locator: `${seg.code}.getByRole(${rolePart})`,
|
|
1888
|
+
method: "getByRole",
|
|
1889
|
+
args: { ...leafArgs, ...seg.flatArgs },
|
|
1890
|
+
score: ANCHOR_KIND_SCORES[seg.kind]
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
if (anchors.length >= 2 && chainLeafCount === 1) {
|
|
1894
|
+
const ordered = [...anchors].sort((a, b) => b.depth - a.depth);
|
|
1895
|
+
const segments = ordered.map(anchorSegment);
|
|
1896
|
+
if (segments.every((s) => s !== null)) {
|
|
1897
|
+
const innermost = segments[segments.length - 1];
|
|
1898
|
+
const score = Math.min(...segments.map((s) => ANCHOR_KIND_SCORES[s.kind]));
|
|
1899
|
+
add({
|
|
1900
|
+
locator: `${segments.map((s) => s.code).join(".")}.getByRole(${rolePart})`,
|
|
1617
1901
|
method: "getByRole",
|
|
1618
1902
|
// Flat args describe the innermost anchor (what the healing fingerprint
|
|
1619
1903
|
// logic understands); the full chain rides along for transparency.
|
|
@@ -1642,21 +1926,21 @@ var MAX_INDETERMINATE = 20;
|
|
|
1642
1926
|
var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
|
|
1643
1927
|
var LEVEL = `${STEP}(?:/${STEP})*`;
|
|
1644
1928
|
var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
|
|
1645
|
-
function isElementPath(
|
|
1646
|
-
return typeof
|
|
1929
|
+
function isElementPath(path13) {
|
|
1930
|
+
return typeof path13 === "string" && path13.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path13);
|
|
1647
1931
|
}
|
|
1648
1932
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
1649
1933
|
function elementRef(v) {
|
|
1650
1934
|
if (!v || typeof v !== "object") return null;
|
|
1651
|
-
const { path:
|
|
1652
|
-
if (!isElementPath(
|
|
1653
|
-
return { path:
|
|
1935
|
+
const { path: path13, label } = v;
|
|
1936
|
+
if (!isElementPath(path13)) return null;
|
|
1937
|
+
return { path: path13, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
|
|
1654
1938
|
}
|
|
1655
1939
|
function textPoint(v) {
|
|
1656
1940
|
if (!v || typeof v !== "object") return null;
|
|
1657
|
-
const { path:
|
|
1941
|
+
const { path: path13, offset } = v;
|
|
1658
1942
|
const at = offsetOf(offset);
|
|
1659
|
-
return isElementPath(
|
|
1943
|
+
return isElementPath(path13) && at !== null ? { path: path13, offset: at } : null;
|
|
1660
1944
|
}
|
|
1661
1945
|
function sanitizePageInteraction(raw) {
|
|
1662
1946
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -1719,6 +2003,23 @@ function textSimilarity(a, b) {
|
|
|
1719
2003
|
for (const t of sa) if (sb.has(t)) common++;
|
|
1720
2004
|
return 2 * common / (sa.size + sb.size);
|
|
1721
2005
|
}
|
|
2006
|
+
var FUNCTION_WORDS = new Set(
|
|
2007
|
+
"the and for with from your you our this that les des une pour avec par sur aux dans vos votre nos notre".split(" ")
|
|
2008
|
+
);
|
|
2009
|
+
function significantWords(text) {
|
|
2010
|
+
const all = (text ?? "").replace(new RegExp("(\\p{Ll})(\\p{Lu})", "gu"), "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
2011
|
+
const meaningful = all.filter((w) => w.length >= 3 && !FUNCTION_WORDS.has(w));
|
|
2012
|
+
return new Set(meaningful.length > 0 ? meaningful : all);
|
|
2013
|
+
}
|
|
2014
|
+
function significantWordSimilarity(a, b) {
|
|
2015
|
+
const sa = significantWords(a);
|
|
2016
|
+
const sb = significantWords(b);
|
|
2017
|
+
if (sa.size === 0 && sb.size === 0) return 1;
|
|
2018
|
+
if (sa.size === 0 || sb.size === 0) return 0;
|
|
2019
|
+
let common = 0;
|
|
2020
|
+
for (const w of sa) if (sb.has(w)) common++;
|
|
2021
|
+
return 2 * common / (sa.size + sb.size);
|
|
2022
|
+
}
|
|
1722
2023
|
function fingerprintPresent(fp, candidates) {
|
|
1723
2024
|
if (!fp.name) return false;
|
|
1724
2025
|
return candidates.some(
|
|
@@ -1740,7 +2041,7 @@ function matchRenamedElement(fp, candidates) {
|
|
|
1740
2041
|
let best = null;
|
|
1741
2042
|
let bestScore = -1;
|
|
1742
2043
|
for (const c of pool) {
|
|
1743
|
-
const s =
|
|
2044
|
+
const s = significantWordSimilarity(c.name, fp.name);
|
|
1744
2045
|
if (s > bestScore) {
|
|
1745
2046
|
bestScore = s;
|
|
1746
2047
|
best = c;
|
|
@@ -1903,7 +2204,6 @@ var NAME_BASED_METHODS = /* @__PURE__ */ new Set([
|
|
|
1903
2204
|
"getByTitle",
|
|
1904
2205
|
"getByAltText"
|
|
1905
2206
|
]);
|
|
1906
|
-
var escAttr = (s) => s.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
|
|
1907
2207
|
var SUGG_TEXT_ROLES = /* @__PURE__ */ new Set([
|
|
1908
2208
|
"button",
|
|
1909
2209
|
"link",
|
|
@@ -1936,9 +2236,9 @@ function failedNameAndRole(failed) {
|
|
|
1936
2236
|
function renderFailing(failed) {
|
|
1937
2237
|
const { role, name } = failedNameAndRole(failed);
|
|
1938
2238
|
if (failed.method === "getByRole") {
|
|
1939
|
-
return name ? `getByRole(
|
|
2239
|
+
return name ? `getByRole(${quoteJsString(role ?? "")}, { name: ${quoteJsString(name)} })` : `getByRole(${quoteJsString(role ?? "")})`;
|
|
1940
2240
|
}
|
|
1941
|
-
return `${failed.method}(
|
|
2241
|
+
return `${failed.method}(${quoteJsString(name ?? "")})`;
|
|
1942
2242
|
}
|
|
1943
2243
|
function freshSuggestions(candidate, failedMethod) {
|
|
1944
2244
|
const out = [];
|
|
@@ -1948,9 +2248,9 @@ function freshSuggestions(candidate, failedMethod) {
|
|
|
1948
2248
|
if (!out.includes(s)) out.push(s);
|
|
1949
2249
|
};
|
|
1950
2250
|
const levelPart = candidate.level != null ? `, level: ${candidate.level}` : "";
|
|
1951
|
-
const roleLoc = `getByRole(
|
|
1952
|
-
const textLoc = `getByText(
|
|
1953
|
-
const labelLoc = `getByLabel(
|
|
2251
|
+
const roleLoc = `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(name)}${levelPart} })`;
|
|
2252
|
+
const textLoc = `getByText(${quoteJsString(name)})`;
|
|
2253
|
+
const labelLoc = `getByLabel(${quoteJsString(name)})`;
|
|
1954
2254
|
if (failedMethod === "getByText" && SUGG_TEXT_ROLES.has(role)) push(textLoc);
|
|
1955
2255
|
if (failedMethod === "getByLabel" && SUGG_FIELD_ROLES.has(role)) push(labelLoc);
|
|
1956
2256
|
push(roleLoc);
|
|
@@ -2037,6 +2337,8 @@ var ATTACHMENT_NAMES = {
|
|
|
2037
2337
|
pageInventory: "piwi-page-inventory",
|
|
2038
2338
|
locatorPages: "piwi-locator-pages",
|
|
2039
2339
|
codeReach: "piwi-code-reach",
|
|
2340
|
+
browserCoverage: "piwi-browser-coverage",
|
|
2341
|
+
browserFunctions: "piwi-browser-functions",
|
|
2040
2342
|
serverCoverageRequests: "piwi-server-coverage-requests",
|
|
2041
2343
|
webVitals: "piwi-web-vitals",
|
|
2042
2344
|
locatorSuggestion: "piwi-locator-suggestion",
|
|
@@ -2279,7 +2581,6 @@ var LocatorPageLog = class {
|
|
|
2279
2581
|
|
|
2280
2582
|
// src/internal/probe/mode.ts
|
|
2281
2583
|
var fs2 = __toESM(require("fs"));
|
|
2282
|
-
var path3 = __toESM(require("path"));
|
|
2283
2584
|
|
|
2284
2585
|
// src/internal/config/desktop.ts
|
|
2285
2586
|
var fs = __toESM(require("fs"));
|
|
@@ -2416,6 +2717,17 @@ var ENV_FALLBACK_SPECS = [
|
|
|
2416
2717
|
{ option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
|
|
2417
2718
|
];
|
|
2418
2719
|
|
|
2720
|
+
// src/internal/support/test-paths.ts
|
|
2721
|
+
var path3 = __toESM(require("path"));
|
|
2722
|
+
function configDirFromConfig(config) {
|
|
2723
|
+
if (config?.configFile) return path3.dirname(config.configFile);
|
|
2724
|
+
if (config?.rootDir) return config.rootDir;
|
|
2725
|
+
return process.cwd();
|
|
2726
|
+
}
|
|
2727
|
+
function relativeTestPath(file, base) {
|
|
2728
|
+
return path3.relative(base, file).split(path3.sep).join("/");
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2419
2731
|
// src/internal/probe/plan.ts
|
|
2420
2732
|
function parseRouteKey(routeKey2) {
|
|
2421
2733
|
const space = routeKey2.indexOf(" ");
|
|
@@ -2423,8 +2735,8 @@ function parseRouteKey(routeKey2) {
|
|
|
2423
2735
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
2424
2736
|
}
|
|
2425
2737
|
function patternToRegex(pattern) {
|
|
2426
|
-
const
|
|
2427
|
-
const escaped =
|
|
2738
|
+
const path13 = pattern.split("?")[0] ?? pattern;
|
|
2739
|
+
const escaped = path13.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
2428
2740
|
return new RegExp(`^${escaped}$`);
|
|
2429
2741
|
}
|
|
2430
2742
|
function suitePathEqual(a, b) {
|
|
@@ -2447,13 +2759,13 @@ function matchProbeItem(plan, test) {
|
|
|
2447
2759
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
2448
2760
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
2449
2761
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
2450
|
-
let
|
|
2762
|
+
let path13;
|
|
2451
2763
|
try {
|
|
2452
|
-
|
|
2764
|
+
path13 = new URL(url).pathname;
|
|
2453
2765
|
} catch {
|
|
2454
|
-
|
|
2766
|
+
path13 = url.split("?")[0] ?? url;
|
|
2455
2767
|
}
|
|
2456
|
-
return patternToRegex(pattern).test(
|
|
2768
|
+
return patternToRegex(pattern).test(path13);
|
|
2457
2769
|
}
|
|
2458
2770
|
function createProbeState() {
|
|
2459
2771
|
return { navigated: false, matches: 0 };
|
|
@@ -2475,10 +2787,10 @@ function isProbeMode() {
|
|
|
2475
2787
|
function loadPlan() {
|
|
2476
2788
|
if (planCache !== void 0) return planCache;
|
|
2477
2789
|
planCache = null;
|
|
2478
|
-
const
|
|
2479
|
-
if (
|
|
2790
|
+
const path13 = process.env[PIWI_PROBE_ENV.plan];
|
|
2791
|
+
if (path13) {
|
|
2480
2792
|
try {
|
|
2481
|
-
planCache = JSON.parse(fs2.readFileSync(
|
|
2793
|
+
planCache = JSON.parse(fs2.readFileSync(path13, "utf8"));
|
|
2482
2794
|
} catch {
|
|
2483
2795
|
planCache = null;
|
|
2484
2796
|
}
|
|
@@ -2488,7 +2800,7 @@ function loadPlan() {
|
|
|
2488
2800
|
function probeItemForTest(test) {
|
|
2489
2801
|
const plan = loadPlan();
|
|
2490
2802
|
if (!plan) return null;
|
|
2491
|
-
const filePath = test.file ?
|
|
2803
|
+
const filePath = test.file ? relativeTestPath(test.file, test.base ?? process.cwd()) : null;
|
|
2492
2804
|
const suitePath = test.titlePath ? test.titlePath.slice(1, -1) : void 0;
|
|
2493
2805
|
return matchProbeItem(plan, { title: test.title, filePath, suitePath });
|
|
2494
2806
|
}
|
|
@@ -2502,10 +2814,10 @@ function outcomeFromStatus(status, applied) {
|
|
|
2502
2814
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
2503
2815
|
}
|
|
2504
2816
|
function recordProbeOutcome(line) {
|
|
2505
|
-
const
|
|
2506
|
-
if (!
|
|
2817
|
+
const path13 = process.env[PIWI_PROBE_ENV.results];
|
|
2818
|
+
if (!path13) return;
|
|
2507
2819
|
try {
|
|
2508
|
-
fs2.appendFileSync(
|
|
2820
|
+
fs2.appendFileSync(path13, `${JSON.stringify(line)}
|
|
2509
2821
|
`);
|
|
2510
2822
|
} catch {
|
|
2511
2823
|
}
|
|
@@ -2518,519 +2830,236 @@ var import_node_zlib = require("zlib");
|
|
|
2518
2830
|
var PROBE_FAULTS = ["status-500", "empty-body", "drop-field", "stale-value", "slow"];
|
|
2519
2831
|
var SERVER_PROBE_FAULTS = [
|
|
2520
2832
|
"throw",
|
|
2521
|
-
"status",
|
|
2522
|
-
"delay",
|
|
2523
|
-
"dependency",
|
|
2524
|
-
"data",
|
|
2525
|
-
"extreme",
|
|
2526
|
-
"replay",
|
|
2527
|
-
"auth",
|
|
2528
|
-
"slow-first"
|
|
2529
|
-
];
|
|
2530
|
-
function isFaultAllowed(level, fault) {
|
|
2531
|
-
const list = level === "server" ? SERVER_PROBE_FAULTS : PROBE_FAULTS;
|
|
2532
|
-
return list.includes(fault);
|
|
2533
|
-
}
|
|
2534
|
-
var SLOW_FAULT_DELAY_MS = 5e3;
|
|
2535
|
-
function faultRouteAction(fault) {
|
|
2536
|
-
return fault === "slow" ? { kind: "delay", ms: SLOW_FAULT_DELAY_MS } : null;
|
|
2537
|
-
}
|
|
2538
|
-
function dropOneField(body) {
|
|
2539
|
-
let parsed2;
|
|
2540
|
-
try {
|
|
2541
|
-
parsed2 = JSON.parse(body);
|
|
2542
|
-
} catch {
|
|
2543
|
-
return body;
|
|
2544
|
-
}
|
|
2545
|
-
if (Array.isArray(parsed2)) {
|
|
2546
|
-
let key;
|
|
2547
|
-
for (const el of parsed2) {
|
|
2548
|
-
if (el && typeof el === "object" && !Array.isArray(el)) {
|
|
2549
|
-
key ??= Object.keys(el)[0];
|
|
2550
|
-
if (key) delete el[key];
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
return JSON.stringify(parsed2);
|
|
2554
|
-
}
|
|
2555
|
-
if (parsed2 && typeof parsed2 === "object") {
|
|
2556
|
-
const obj = parsed2;
|
|
2557
|
-
const key = Object.keys(obj)[0];
|
|
2558
|
-
if (key) delete obj[key];
|
|
2559
|
-
return JSON.stringify(obj);
|
|
2560
|
-
}
|
|
2561
|
-
return body;
|
|
2562
|
-
}
|
|
2563
|
-
function computeFault(fault, input) {
|
|
2564
|
-
const base = {
|
|
2565
|
-
status: input.status,
|
|
2566
|
-
body: input.body,
|
|
2567
|
-
contentType: input.contentType ?? null,
|
|
2568
|
-
delayMs: 0,
|
|
2569
|
-
changed: false
|
|
2570
|
-
};
|
|
2571
|
-
switch (fault) {
|
|
2572
|
-
case "status-500":
|
|
2573
|
-
return { ...base, status: 500, changed: input.status !== 500 };
|
|
2574
|
-
case "empty-body":
|
|
2575
|
-
return { ...base, body: "", changed: input.body.length > 0 };
|
|
2576
|
-
case "drop-field": {
|
|
2577
|
-
const body = dropOneField(input.body);
|
|
2578
|
-
return { ...base, body, changed: body !== input.body };
|
|
2579
|
-
}
|
|
2580
|
-
case "stale-value": {
|
|
2581
|
-
const body = input.priorBody ?? input.body;
|
|
2582
|
-
return { ...base, body, changed: body !== input.body };
|
|
2583
|
-
}
|
|
2584
|
-
case "slow":
|
|
2585
|
-
return { ...base, delayMs: SLOW_FAULT_DELAY_MS, changed: true };
|
|
2586
|
-
default:
|
|
2587
|
-
return base;
|
|
2588
|
-
}
|
|
2589
|
-
}
|
|
2590
|
-
|
|
2591
|
-
// src/internal/probe/sign.ts
|
|
2592
|
-
var import_node_crypto = require("crypto");
|
|
2593
|
-
function signProbeMessage(secret, nonce, ts, specJson) {
|
|
2594
|
-
return (0, import_node_crypto.createHmac)("sha256", secret).update(`${nonce}.${ts}.${specJson}`).digest("hex");
|
|
2595
|
-
}
|
|
2596
|
-
function buildProbeHeader(secret, spec, now = Date.now()) {
|
|
2597
|
-
const nonce = (0, import_node_crypto.randomBytes)(8).toString("hex");
|
|
2598
|
-
const specJson = JSON.stringify(spec);
|
|
2599
|
-
const sig = signProbeMessage(secret, nonce, now, specJson);
|
|
2600
|
-
return Buffer.from(JSON.stringify({ nonce, ts: now, specJson, sig }), "utf8").toString("base64");
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
// src/internal/probe/interception.ts
|
|
2604
|
-
function parseProbeTrace(traceHeader) {
|
|
2605
|
-
const none = { appliedFault: null, serverError: false };
|
|
2606
|
-
if (!traceHeader) return none;
|
|
2607
|
-
try {
|
|
2608
|
-
const spans = JSON.parse((0, import_node_zlib.gunzipSync)(Buffer.from(traceHeader, "base64")).toString("utf8"));
|
|
2609
|
-
if (!Array.isArray(spans)) return none;
|
|
2610
|
-
const root = spans.find((s) => s && typeof s === "object" && !s.parentId);
|
|
2611
|
-
if (!root) return none;
|
|
2612
|
-
const attrs = root.attrs ?? {};
|
|
2613
|
-
const appliedRaw = attrs["piwi.probe.applied"];
|
|
2614
|
-
const status = Number(attrs["http.status_code"]) || 0;
|
|
2615
|
-
return {
|
|
2616
|
-
appliedFault: appliedRaw != null ? String(appliedRaw) : null,
|
|
2617
|
-
serverError: root.status === "error" || status >= 500
|
|
2618
|
-
};
|
|
2619
|
-
} catch {
|
|
2620
|
-
return none;
|
|
2621
|
-
}
|
|
2622
|
-
}
|
|
2623
|
-
function fulfillHeaders(responseHeaders, contentType) {
|
|
2624
|
-
const headers = { ...responseHeaders };
|
|
2625
|
-
delete headers["content-length"];
|
|
2626
|
-
delete headers["content-encoding"];
|
|
2627
|
-
if (contentType) headers["content-type"] = contentType;
|
|
2628
|
-
return headers;
|
|
2629
|
-
}
|
|
2630
|
-
function originOf(url) {
|
|
2631
|
-
try {
|
|
2632
|
-
const origin = new URL(url).origin;
|
|
2633
|
-
return origin && origin !== "null" ? origin : null;
|
|
2634
|
-
} catch {
|
|
2635
|
-
return null;
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, Math.max(0, ms)));
|
|
2639
|
-
async function performRouteAction(route, action, startedAt) {
|
|
2640
|
-
switch (action.kind) {
|
|
2641
|
-
case "abort":
|
|
2642
|
-
await route.abort("connectionreset");
|
|
2643
|
-
return;
|
|
2644
|
-
case "status":
|
|
2645
|
-
await route.fulfill({ status: action.status, body: "" });
|
|
2646
|
-
return;
|
|
2647
|
-
case "delay": {
|
|
2648
|
-
const response = await route.fetch();
|
|
2649
|
-
await sleep(startedAt + action.ms - Date.now());
|
|
2650
|
-
await route.fulfill({ response });
|
|
2651
|
-
return;
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
async function installRouteRules(page, rules) {
|
|
2656
|
-
const state = rules.map(() => createProbeState());
|
|
2657
|
-
page.on("framenavigated", (frame) => {
|
|
2658
|
-
if (frame === page.mainFrame()) for (const s of state) markNavigated(s);
|
|
2659
|
-
});
|
|
2660
|
-
await page.route("**/*", async (route) => {
|
|
2661
|
-
const startedAt = Date.now();
|
|
2662
|
-
const request = route.request();
|
|
2663
|
-
for (let i = 0; i < rules.length; i++) {
|
|
2664
|
-
const rule = rules[i];
|
|
2665
|
-
if (rule.done?.() || !rule.matches(request.method(), request.url())) continue;
|
|
2666
|
-
if (!shouldAct(state[i], rule.match)) continue;
|
|
2667
|
-
await rule.handle(route, startedAt);
|
|
2668
|
-
return;
|
|
2669
|
-
}
|
|
2670
|
-
await route.fallback();
|
|
2671
|
-
});
|
|
2672
|
-
return { state };
|
|
2673
|
-
}
|
|
2674
|
-
async function installProbeInterception(page, item) {
|
|
2675
|
-
if (!isFaultAllowed(item.level ?? "client", item.fault)) {
|
|
2676
|
-
return { applied: () => false, appliedFault: () => null, serverError: () => false };
|
|
2677
|
-
}
|
|
2678
|
-
let applied = false;
|
|
2679
|
-
let appliedFault = null;
|
|
2680
|
-
let serverError = false;
|
|
2681
|
-
let appOrigin = null;
|
|
2682
|
-
const probeSecret = process.env[PIWI_PROBE_ENV.secret];
|
|
2683
|
-
page.on("framenavigated", (frame) => {
|
|
2684
|
-
if (frame === page.mainFrame()) {
|
|
2685
|
-
const origin = originOf(page.url());
|
|
2686
|
-
if (origin) appOrigin = origin;
|
|
2687
|
-
}
|
|
2688
|
-
});
|
|
2689
|
-
const handle = async (route, startedAt) => {
|
|
2690
|
-
const request = route.request();
|
|
2691
|
-
if (item.level === "server") {
|
|
2692
|
-
if (!probeSecret || appOrigin !== null && originOf(request.url()) !== appOrigin) {
|
|
2693
|
-
await route.fallback();
|
|
2694
|
-
return;
|
|
2695
|
-
}
|
|
2696
|
-
const spec = { route: item.routeKey, fault: item.fault, nth: item.nth };
|
|
2697
|
-
if (item.dependency) spec.dependency = item.dependency;
|
|
2698
|
-
try {
|
|
2699
|
-
const response = await route.fetch({
|
|
2700
|
-
headers: { ...request.headers(), "x-piwi-probe": buildProbeHeader(probeSecret, spec) }
|
|
2701
|
-
});
|
|
2702
|
-
const trace = parseProbeTrace(response.headers()["x-piwi-trace"]);
|
|
2703
|
-
appliedFault = trace.appliedFault;
|
|
2704
|
-
serverError = trace.serverError;
|
|
2705
|
-
applied = trace.appliedFault != null && trace.appliedFault.split(":", 1)[0] === item.fault;
|
|
2706
|
-
await route.fulfill({ response });
|
|
2707
|
-
} catch {
|
|
2708
|
-
applied = false;
|
|
2709
|
-
await route.fallback();
|
|
2710
|
-
}
|
|
2711
|
-
return;
|
|
2712
|
-
}
|
|
2713
|
-
try {
|
|
2714
|
-
const action = faultRouteAction(item.fault);
|
|
2715
|
-
if (action) {
|
|
2716
|
-
applied = true;
|
|
2717
|
-
appliedFault = item.fault;
|
|
2718
|
-
await performRouteAction(route, action, startedAt);
|
|
2719
|
-
return;
|
|
2720
|
-
}
|
|
2721
|
-
const response = await route.fetch();
|
|
2722
|
-
const body = await response.text();
|
|
2723
|
-
const out = computeFault(item.fault, {
|
|
2724
|
-
status: response.status(),
|
|
2725
|
-
body,
|
|
2726
|
-
contentType: response.headers()["content-type"] ?? null
|
|
2727
|
-
});
|
|
2728
|
-
applied = out.changed;
|
|
2729
|
-
if (applied) appliedFault = item.fault;
|
|
2730
|
-
const headers = fulfillHeaders(response.headers(), out.contentType);
|
|
2731
|
-
await route.fulfill({ status: out.status, body: out.body, headers });
|
|
2732
|
-
} catch {
|
|
2733
|
-
applied = false;
|
|
2734
|
-
await route.fallback();
|
|
2735
|
-
}
|
|
2736
|
-
};
|
|
2737
|
-
await installRouteRules(page, [
|
|
2738
|
-
{
|
|
2739
|
-
matches: (method, url) => requestMatchesRoute(method, url, item.routeKey),
|
|
2740
|
-
match: Math.max(1, item.nth ?? 1),
|
|
2741
|
-
handle,
|
|
2742
|
-
done: () => applied
|
|
2743
|
-
}
|
|
2744
|
-
]);
|
|
2745
|
-
return { applied: () => applied, appliedFault: () => appliedFault, serverError: () => serverError };
|
|
2746
|
-
}
|
|
2747
|
-
|
|
2748
|
-
// src/internal/flake/mode.ts
|
|
2749
|
-
var fs3 = __toESM(require("fs"));
|
|
2750
|
-
var path4 = __toESM(require("path"));
|
|
2751
|
-
|
|
2752
|
-
// ../core/src/locator-chain.ts
|
|
2753
|
-
var LOCATING_METHODS = /* @__PURE__ */ new Set([
|
|
2754
|
-
"getByRole",
|
|
2755
|
-
"getByTestId",
|
|
2756
|
-
"getByText",
|
|
2757
|
-
"getByLabel",
|
|
2758
|
-
"getByPlaceholder",
|
|
2759
|
-
"getByAltText",
|
|
2760
|
-
"getByTitle",
|
|
2761
|
-
"locator",
|
|
2762
|
-
"frameLocator"
|
|
2763
|
-
]);
|
|
2764
|
-
var NARROWING_METHODS = /* @__PURE__ */ new Set([
|
|
2765
|
-
"filter",
|
|
2766
|
-
"first",
|
|
2767
|
-
"last",
|
|
2768
|
-
"nth",
|
|
2769
|
-
"and",
|
|
2770
|
-
"or",
|
|
2771
|
-
"visible",
|
|
2772
|
-
"contentFrame",
|
|
2773
|
-
"owner"
|
|
2774
|
-
]);
|
|
2775
|
-
var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([...LOCATING_METHODS, ...NARROWING_METHODS]);
|
|
2776
|
-
var LocatorParseError = class extends Error {
|
|
2777
|
-
constructor(message) {
|
|
2778
|
-
super(message);
|
|
2779
|
-
this.name = "LocatorParseError";
|
|
2780
|
-
}
|
|
2781
|
-
};
|
|
2782
|
-
var SIMPLE_ESCAPES = {
|
|
2783
|
-
n: "\n",
|
|
2784
|
-
t: " ",
|
|
2785
|
-
r: "\r",
|
|
2786
|
-
b: "\b",
|
|
2787
|
-
f: "\f",
|
|
2788
|
-
v: "\v",
|
|
2789
|
-
"0": "\0"
|
|
2790
|
-
};
|
|
2791
|
-
var Parser = class {
|
|
2792
|
-
constructor(src) {
|
|
2793
|
-
this.src = src;
|
|
2794
|
-
this.i = 0;
|
|
2795
|
-
}
|
|
2796
|
-
parseTopLevel() {
|
|
2797
|
-
const chain = this.parseChain();
|
|
2798
|
-
this.skipSpace();
|
|
2799
|
-
if (this.i < this.src.length) this.fail("unexpected text");
|
|
2800
|
-
return chain;
|
|
2801
|
-
}
|
|
2802
|
-
fail(message) {
|
|
2803
|
-
throw new LocatorParseError(`${message} at "${this.src.slice(this.i, this.i + 20)}"`);
|
|
2804
|
-
}
|
|
2805
|
-
skipSpace() {
|
|
2806
|
-
while (this.i < this.src.length && /\s/.test(this.src[this.i])) this.i++;
|
|
2807
|
-
}
|
|
2808
|
-
peek() {
|
|
2809
|
-
return this.src[this.i];
|
|
2810
|
-
}
|
|
2811
|
-
expect(ch) {
|
|
2812
|
-
this.skipSpace();
|
|
2813
|
-
if (this.src[this.i] !== ch) this.fail(`expected "${ch}"`);
|
|
2814
|
-
this.i++;
|
|
2815
|
-
}
|
|
2816
|
-
identifier() {
|
|
2817
|
-
const m = /^[A-Za-z_$][\w$]*/.exec(this.src.slice(this.i));
|
|
2818
|
-
if (!m) this.fail("expected a name");
|
|
2819
|
-
this.i += m[0].length;
|
|
2820
|
-
return m[0];
|
|
2821
|
-
}
|
|
2822
|
-
/**
|
|
2823
|
-
* A chain of calls joined by `.`. With `lenient`, parsing stops before the
|
|
2824
|
-
* first link that doesn't parse instead of failing, so a chain followed by
|
|
2825
|
-
* other text (an error message) is read up to where it ends. `spans` receives
|
|
2826
|
-
* where each top-level call starts and ends.
|
|
2827
|
-
*/
|
|
2828
|
-
parseChain(opts = {}) {
|
|
2829
|
-
this.skipSpace();
|
|
2830
|
-
const calls = [];
|
|
2831
|
-
for (; ; ) {
|
|
2832
|
-
const before = this.i;
|
|
2833
|
-
if (calls.length > 0) this.i++;
|
|
2834
|
-
this.skipSpace();
|
|
2835
|
-
const start = this.i;
|
|
2836
|
-
try {
|
|
2837
|
-
calls.push(this.parseCall(calls.length === 0));
|
|
2838
|
-
} catch (error) {
|
|
2839
|
-
if (!opts.lenient || calls.length === 0) throw error;
|
|
2840
|
-
this.i = before;
|
|
2841
|
-
break;
|
|
2842
|
-
}
|
|
2843
|
-
opts.spans?.push({ start, end: this.i });
|
|
2844
|
-
const end = this.i;
|
|
2845
|
-
this.skipSpace();
|
|
2846
|
-
if (this.peek() !== ".") {
|
|
2847
|
-
this.i = end;
|
|
2848
|
-
break;
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
return { calls };
|
|
2852
|
-
}
|
|
2853
|
-
/** Where the parser stopped. */
|
|
2854
|
-
get position() {
|
|
2855
|
-
return this.i;
|
|
2856
|
-
}
|
|
2857
|
-
parseCall(first) {
|
|
2858
|
-
this.skipSpace();
|
|
2859
|
-
const method = this.identifier();
|
|
2860
|
-
if (!LOCATOR_CHAIN_METHODS.has(method)) this.fail(`unsupported method ${method}()`);
|
|
2861
|
-
if (first && !LOCATING_METHODS.has(method)) this.fail(`${method}() needs a locator before it`);
|
|
2862
|
-
this.expect("(");
|
|
2863
|
-
const args = [];
|
|
2864
|
-
this.skipSpace();
|
|
2865
|
-
if (this.peek() === ")") {
|
|
2866
|
-
this.i++;
|
|
2867
|
-
return { method, args };
|
|
2868
|
-
}
|
|
2869
|
-
for (; ; ) {
|
|
2870
|
-
args.push(this.parseValue());
|
|
2871
|
-
this.skipSpace();
|
|
2872
|
-
if (this.peek() === ",") {
|
|
2873
|
-
this.i++;
|
|
2874
|
-
this.skipSpace();
|
|
2875
|
-
if (this.peek() === ")") {
|
|
2876
|
-
this.i++;
|
|
2877
|
-
break;
|
|
2878
|
-
}
|
|
2879
|
-
continue;
|
|
2880
|
-
}
|
|
2881
|
-
this.expect(")");
|
|
2882
|
-
break;
|
|
2883
|
-
}
|
|
2884
|
-
return { method, args };
|
|
2885
|
-
}
|
|
2886
|
-
parseValue() {
|
|
2887
|
-
this.skipSpace();
|
|
2888
|
-
const ch = this.peek();
|
|
2889
|
-
if (ch === "'" || ch === '"' || ch === "`") return { type: "string", value: this.parseString() };
|
|
2890
|
-
if (ch === "/") return this.parseRegex();
|
|
2891
|
-
if (ch === "{") return this.parseObject();
|
|
2892
|
-
if (ch !== void 0 && /[-\d]/.test(ch)) {
|
|
2893
|
-
const m = /^-?\d+(?:\.\d+)?/.exec(this.src.slice(this.i));
|
|
2894
|
-
if (!m) this.fail("expected a number");
|
|
2895
|
-
this.i += m[0].length;
|
|
2896
|
-
return { type: "number", value: Number(m[0]) };
|
|
2897
|
-
}
|
|
2898
|
-
const rest = this.src.slice(this.i);
|
|
2899
|
-
if (/^true\b/.test(rest)) {
|
|
2900
|
-
this.i += 4;
|
|
2901
|
-
return { type: "boolean", value: true };
|
|
2902
|
-
}
|
|
2903
|
-
if (/^false\b/.test(rest)) {
|
|
2904
|
-
this.i += 5;
|
|
2905
|
-
return { type: "boolean", value: false };
|
|
2906
|
-
}
|
|
2907
|
-
if (/^[A-Za-z_$]/.test(rest)) return { type: "chain", chain: this.parseChain() };
|
|
2908
|
-
return this.fail("expected a value");
|
|
2909
|
-
}
|
|
2910
|
-
/** A quoted string, decoding the escapes `JSON.stringify` and JavaScript produce. */
|
|
2911
|
-
parseString() {
|
|
2912
|
-
const quote = this.src[this.i];
|
|
2913
|
-
let out = "";
|
|
2914
|
-
this.i++;
|
|
2915
|
-
while (this.i < this.src.length) {
|
|
2916
|
-
const c = this.src[this.i];
|
|
2917
|
-
if (c === quote) {
|
|
2918
|
-
this.i++;
|
|
2919
|
-
return out;
|
|
2920
|
-
}
|
|
2921
|
-
if (c !== "\\") {
|
|
2922
|
-
out += c;
|
|
2923
|
-
this.i++;
|
|
2924
|
-
continue;
|
|
2925
|
-
}
|
|
2926
|
-
const next = this.src[this.i + 1];
|
|
2927
|
-
if (next === void 0) break;
|
|
2928
|
-
const simple = SIMPLE_ESCAPES[next];
|
|
2929
|
-
if (simple !== void 0) {
|
|
2930
|
-
out += simple;
|
|
2931
|
-
this.i += 2;
|
|
2932
|
-
continue;
|
|
2933
|
-
}
|
|
2934
|
-
const hex = next === "u" && this.src[this.i + 2] === "{" ? /^u\{([0-9a-fA-F]{1,6})\}/.exec(this.src.slice(this.i + 1)) : next === "u" ? /^u([0-9a-fA-F]{4})/.exec(this.src.slice(this.i + 1)) : next === "x" ? /^x([0-9a-fA-F]{2})/.exec(this.src.slice(this.i + 1)) : null;
|
|
2935
|
-
if (hex) {
|
|
2936
|
-
out += String.fromCodePoint(parseInt(hex[1], 16));
|
|
2937
|
-
this.i += 1 + hex[0].length;
|
|
2938
|
-
continue;
|
|
2939
|
-
}
|
|
2940
|
-
if (next === "u" || next === "x") this.fail("malformed escape");
|
|
2941
|
-
out += next;
|
|
2942
|
-
this.i += 2;
|
|
2943
|
-
}
|
|
2944
|
-
return this.fail("unterminated string");
|
|
2833
|
+
"status",
|
|
2834
|
+
"delay",
|
|
2835
|
+
"dependency",
|
|
2836
|
+
"data",
|
|
2837
|
+
"extreme",
|
|
2838
|
+
"replay",
|
|
2839
|
+
"auth",
|
|
2840
|
+
"slow-first"
|
|
2841
|
+
];
|
|
2842
|
+
function isFaultAllowed(level, fault) {
|
|
2843
|
+
const list = level === "server" ? SERVER_PROBE_FAULTS : PROBE_FAULTS;
|
|
2844
|
+
return list.includes(fault);
|
|
2845
|
+
}
|
|
2846
|
+
var SLOW_FAULT_DELAY_MS = 5e3;
|
|
2847
|
+
function faultRouteAction(fault) {
|
|
2848
|
+
return fault === "slow" ? { kind: "delay", ms: SLOW_FAULT_DELAY_MS } : null;
|
|
2849
|
+
}
|
|
2850
|
+
function dropOneField(body) {
|
|
2851
|
+
let parsed2;
|
|
2852
|
+
try {
|
|
2853
|
+
parsed2 = JSON.parse(body);
|
|
2854
|
+
} catch {
|
|
2855
|
+
return body;
|
|
2945
2856
|
}
|
|
2946
|
-
|
|
2947
|
-
let
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
j += 2;
|
|
2953
|
-
continue;
|
|
2857
|
+
if (Array.isArray(parsed2)) {
|
|
2858
|
+
let key;
|
|
2859
|
+
for (const el of parsed2) {
|
|
2860
|
+
if (el && typeof el === "object" && !Array.isArray(el)) {
|
|
2861
|
+
key ??= Object.keys(el)[0];
|
|
2862
|
+
if (key) delete el[key];
|
|
2954
2863
|
}
|
|
2955
|
-
if (c === "[") inClass = true;
|
|
2956
|
-
else if (c === "]") inClass = false;
|
|
2957
|
-
else if (c === "/" && !inClass) break;
|
|
2958
|
-
j++;
|
|
2959
2864
|
}
|
|
2960
|
-
|
|
2961
|
-
const source = this.src.slice(this.i + 1, j);
|
|
2962
|
-
const flags = /^[a-z]*/.exec(this.src.slice(j + 1))[0];
|
|
2963
|
-
this.i = j + 1 + flags.length;
|
|
2964
|
-
return { type: "regex", source, flags };
|
|
2865
|
+
return JSON.stringify(parsed2);
|
|
2965
2866
|
}
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
const
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2867
|
+
if (parsed2 && typeof parsed2 === "object") {
|
|
2868
|
+
const obj = parsed2;
|
|
2869
|
+
const key = Object.keys(obj)[0];
|
|
2870
|
+
if (key) delete obj[key];
|
|
2871
|
+
return JSON.stringify(obj);
|
|
2872
|
+
}
|
|
2873
|
+
return body;
|
|
2874
|
+
}
|
|
2875
|
+
function computeFault(fault, input) {
|
|
2876
|
+
const base = {
|
|
2877
|
+
status: input.status,
|
|
2878
|
+
body: input.body,
|
|
2879
|
+
contentType: input.contentType ?? null,
|
|
2880
|
+
delayMs: 0,
|
|
2881
|
+
changed: false
|
|
2882
|
+
};
|
|
2883
|
+
switch (fault) {
|
|
2884
|
+
case "status-500":
|
|
2885
|
+
return { ...base, status: 500, changed: input.status !== 500 };
|
|
2886
|
+
case "empty-body":
|
|
2887
|
+
return { ...base, body: "", changed: input.body.length > 0 };
|
|
2888
|
+
case "drop-field": {
|
|
2889
|
+
const body = dropOneField(input.body);
|
|
2890
|
+
return { ...base, body, changed: body !== input.body };
|
|
2986
2891
|
}
|
|
2987
|
-
|
|
2892
|
+
case "stale-value": {
|
|
2893
|
+
const body = input.priorBody ?? input.body;
|
|
2894
|
+
return { ...base, body, changed: body !== input.body };
|
|
2895
|
+
}
|
|
2896
|
+
case "slow":
|
|
2897
|
+
return { ...base, delayMs: SLOW_FAULT_DELAY_MS, changed: true };
|
|
2898
|
+
default:
|
|
2899
|
+
return base;
|
|
2988
2900
|
}
|
|
2989
|
-
};
|
|
2990
|
-
function parseLocatorChain(expr) {
|
|
2991
|
-
const trimmed = expr.trim().replace(/^(?:await\s+)?(?:this\.)?page\./, "");
|
|
2992
|
-
if (!trimmed) throw new LocatorParseError("empty expression");
|
|
2993
|
-
return new Parser(trimmed).parseTopLevel();
|
|
2994
2901
|
}
|
|
2995
|
-
|
|
2902
|
+
|
|
2903
|
+
// src/internal/probe/sign.ts
|
|
2904
|
+
var import_node_crypto = require("crypto");
|
|
2905
|
+
function signProbeMessage(secret, nonce, ts, specJson) {
|
|
2906
|
+
return (0, import_node_crypto.createHmac)("sha256", secret).update(`${nonce}.${ts}.${specJson}`).digest("hex");
|
|
2907
|
+
}
|
|
2908
|
+
function buildProbeHeader(secret, spec, now = Date.now()) {
|
|
2909
|
+
const nonce = (0, import_node_crypto.randomBytes)(8).toString("hex");
|
|
2910
|
+
const specJson = JSON.stringify(spec);
|
|
2911
|
+
const sig = signProbeMessage(secret, nonce, now, specJson);
|
|
2912
|
+
return Buffer.from(JSON.stringify({ nonce, ts: now, specJson, sig }), "utf8").toString("base64");
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/internal/probe/interception.ts
|
|
2916
|
+
function parseProbeTrace(traceHeader) {
|
|
2917
|
+
const none = { appliedFault: null, serverError: false };
|
|
2918
|
+
if (!traceHeader) return none;
|
|
2996
2919
|
try {
|
|
2997
|
-
|
|
2920
|
+
const spans = JSON.parse((0, import_node_zlib.gunzipSync)(Buffer.from(traceHeader, "base64")).toString("utf8"));
|
|
2921
|
+
if (!Array.isArray(spans)) return none;
|
|
2922
|
+
const root = spans.find((s) => s && typeof s === "object" && !s.parentId);
|
|
2923
|
+
if (!root) return none;
|
|
2924
|
+
const attrs = root.attrs ?? {};
|
|
2925
|
+
const appliedRaw = attrs["piwi.probe.applied"];
|
|
2926
|
+
const status = Number(attrs["http.status_code"]) || 0;
|
|
2927
|
+
return {
|
|
2928
|
+
appliedFault: appliedRaw != null ? String(appliedRaw) : null,
|
|
2929
|
+
serverError: root.status === "error" || status >= 500
|
|
2930
|
+
};
|
|
2931
|
+
} catch {
|
|
2932
|
+
return none;
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
function fulfillHeaders(responseHeaders, contentType) {
|
|
2936
|
+
const headers = { ...responseHeaders };
|
|
2937
|
+
delete headers["content-length"];
|
|
2938
|
+
delete headers["content-encoding"];
|
|
2939
|
+
if (contentType) headers["content-type"] = contentType;
|
|
2940
|
+
return headers;
|
|
2941
|
+
}
|
|
2942
|
+
function originOf(url) {
|
|
2943
|
+
try {
|
|
2944
|
+
const origin = new URL(url).origin;
|
|
2945
|
+
return origin && origin !== "null" ? origin : null;
|
|
2998
2946
|
} catch {
|
|
2999
2947
|
return null;
|
|
3000
2948
|
}
|
|
3001
2949
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
2950
|
+
var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, Math.max(0, ms)));
|
|
2951
|
+
async function performRouteAction(route, action, startedAt) {
|
|
2952
|
+
switch (action.kind) {
|
|
2953
|
+
case "abort":
|
|
2954
|
+
await route.abort("connectionreset");
|
|
2955
|
+
return;
|
|
2956
|
+
case "status":
|
|
2957
|
+
await route.fulfill({ status: action.status, body: "" });
|
|
2958
|
+
return;
|
|
2959
|
+
case "delay": {
|
|
2960
|
+
const response = await route.fetch();
|
|
2961
|
+
await sleep(startedAt + action.ms - Date.now());
|
|
2962
|
+
await route.fulfill({ response });
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
3007
2965
|
}
|
|
3008
|
-
return null;
|
|
3009
2966
|
}
|
|
3010
|
-
function
|
|
3011
|
-
const
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
2967
|
+
async function installRouteRules(page, rules) {
|
|
2968
|
+
const state = rules.map(() => createProbeState());
|
|
2969
|
+
page.on("framenavigated", (frame) => {
|
|
2970
|
+
if (frame === page.mainFrame()) for (const s of state) markNavigated(s);
|
|
2971
|
+
});
|
|
2972
|
+
await page.route("**/*", async (route) => {
|
|
2973
|
+
const startedAt = Date.now();
|
|
2974
|
+
const request = route.request();
|
|
2975
|
+
for (let i = 0; i < rules.length; i++) {
|
|
2976
|
+
const rule = rules[i];
|
|
2977
|
+
if (rule.done?.() || !rule.matches(request.method(), request.url())) continue;
|
|
2978
|
+
if (!shouldAct(state[i], rule.match)) continue;
|
|
2979
|
+
await rule.handle(route, startedAt);
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
await route.fallback();
|
|
2983
|
+
});
|
|
2984
|
+
return { state };
|
|
2985
|
+
}
|
|
2986
|
+
async function installProbeInterception(page, item) {
|
|
2987
|
+
if (!isFaultAllowed(item.level ?? "client", item.fault)) {
|
|
2988
|
+
return { applied: () => false, appliedFault: () => null, serverError: () => false };
|
|
2989
|
+
}
|
|
2990
|
+
let applied = false;
|
|
2991
|
+
let appliedFault = null;
|
|
2992
|
+
let serverError = false;
|
|
2993
|
+
let appOrigin = null;
|
|
2994
|
+
const probeSecret = process.env[PIWI_PROBE_ENV.secret];
|
|
2995
|
+
page.on("framenavigated", (frame) => {
|
|
2996
|
+
if (frame === page.mainFrame()) {
|
|
2997
|
+
const origin = originOf(page.url());
|
|
2998
|
+
if (origin) appOrigin = origin;
|
|
2999
|
+
}
|
|
3000
|
+
});
|
|
3001
|
+
const handle = async (route, startedAt) => {
|
|
3002
|
+
const request = route.request();
|
|
3003
|
+
if (item.level === "server") {
|
|
3004
|
+
if (!probeSecret || appOrigin !== null && originOf(request.url()) !== appOrigin) {
|
|
3005
|
+
await route.fallback();
|
|
3006
|
+
return;
|
|
3026
3007
|
}
|
|
3027
|
-
|
|
3028
|
-
|
|
3008
|
+
const spec = { route: item.routeKey, fault: item.fault, nth: item.nth };
|
|
3009
|
+
if (item.dependency) spec.dependency = item.dependency;
|
|
3010
|
+
try {
|
|
3011
|
+
const response = await route.fetch({
|
|
3012
|
+
headers: { ...request.headers(), "x-piwi-probe": buildProbeHeader(probeSecret, spec) }
|
|
3013
|
+
});
|
|
3014
|
+
const trace = parseProbeTrace(response.headers()["x-piwi-trace"]);
|
|
3015
|
+
appliedFault = trace.appliedFault;
|
|
3016
|
+
serverError = trace.serverError;
|
|
3017
|
+
applied = trace.appliedFault != null && trace.appliedFault.split(":", 1)[0] === item.fault;
|
|
3018
|
+
await route.fulfill({ response });
|
|
3019
|
+
} catch {
|
|
3020
|
+
applied = false;
|
|
3021
|
+
await route.fallback();
|
|
3022
|
+
}
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
try {
|
|
3026
|
+
const action = faultRouteAction(item.fault);
|
|
3027
|
+
if (action) {
|
|
3028
|
+
applied = true;
|
|
3029
|
+
appliedFault = item.fault;
|
|
3030
|
+
await performRouteAction(route, action, startedAt);
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
const response = await route.fetch();
|
|
3034
|
+
const body = await response.text();
|
|
3035
|
+
const out = computeFault(item.fault, {
|
|
3036
|
+
status: response.status(),
|
|
3037
|
+
body,
|
|
3038
|
+
contentType: response.headers()["content-type"] ?? null
|
|
3039
|
+
});
|
|
3040
|
+
applied = out.changed;
|
|
3041
|
+
if (applied) appliedFault = item.fault;
|
|
3042
|
+
const headers = fulfillHeaders(response.headers(), out.contentType);
|
|
3043
|
+
await route.fulfill({ status: out.status, body: out.body, headers });
|
|
3044
|
+
} catch {
|
|
3045
|
+
applied = false;
|
|
3046
|
+
await route.fallback();
|
|
3029
3047
|
}
|
|
3030
3048
|
};
|
|
3031
|
-
|
|
3049
|
+
await installRouteRules(page, [
|
|
3050
|
+
{
|
|
3051
|
+
matches: (method, url) => requestMatchesRoute(method, url, item.routeKey),
|
|
3052
|
+
match: Math.max(1, item.nth ?? 1),
|
|
3053
|
+
handle,
|
|
3054
|
+
done: () => applied
|
|
3055
|
+
}
|
|
3056
|
+
]);
|
|
3057
|
+
return { applied: () => applied, appliedFault: () => appliedFault, serverError: () => serverError };
|
|
3032
3058
|
}
|
|
3033
3059
|
|
|
3060
|
+
// src/internal/flake/mode.ts
|
|
3061
|
+
var fs3 = __toESM(require("fs"));
|
|
3062
|
+
|
|
3034
3063
|
// ../core/src/error-parse.ts
|
|
3035
3064
|
var ANSI_RE = new RegExp("\\u001B\\[[0-9;]*m", "g");
|
|
3036
3065
|
function stripAnsi(text) {
|
|
@@ -3053,11 +3082,20 @@ function extractMessageHead(text) {
|
|
|
3053
3082
|
let head = text;
|
|
3054
3083
|
const callLogIdx = head.indexOf("\nCall log:");
|
|
3055
3084
|
if (callLogIdx !== -1) head = head.slice(0, callLogIdx);
|
|
3056
|
-
const stackIdx = head
|
|
3085
|
+
const stackIdx = stackStart(head);
|
|
3057
3086
|
if (stackIdx !== -1) head = head.slice(0, stackIdx);
|
|
3058
3087
|
const lines = head.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
3059
3088
|
return lines.slice(0, 5).join("\n");
|
|
3060
3089
|
}
|
|
3090
|
+
function stackStart(text) {
|
|
3091
|
+
const runs = /\s+/g;
|
|
3092
|
+
for (let run = runs.exec(text); run; run = runs.exec(text)) {
|
|
3093
|
+
if (!text.startsWith("at ", runs.lastIndex)) continue;
|
|
3094
|
+
const lineBreak = run[0].indexOf("\n");
|
|
3095
|
+
if (lineBreak !== -1 && lineBreak < run[0].length - 1) return run.index + lineBreak;
|
|
3096
|
+
}
|
|
3097
|
+
return -1;
|
|
3098
|
+
}
|
|
3061
3099
|
function extractSelector(text) {
|
|
3062
3100
|
const match = SELECTOR_FN_RE.exec(text);
|
|
3063
3101
|
if (!match) return null;
|
|
@@ -3078,7 +3116,7 @@ function extractSelector(text) {
|
|
|
3078
3116
|
}
|
|
3079
3117
|
var LEAF_METHODS = new Set(LOCATOR_BUILDER_METHODS);
|
|
3080
3118
|
function extractStackFrames(text) {
|
|
3081
|
-
const frameRe =
|
|
3119
|
+
const frameRe = /^[ \t]+at (?:.*? \()?([^()\s][^()\n]*?):(\d+):(\d+)\)?[ \t]*$/gm;
|
|
3082
3120
|
const frames = [];
|
|
3083
3121
|
let m;
|
|
3084
3122
|
while ((m = frameRe.exec(text)) !== null) {
|
|
@@ -3096,6 +3134,11 @@ function extractTopFrameFile(text) {
|
|
|
3096
3134
|
}
|
|
3097
3135
|
var SUBJECTS = "locator|page|frame|frameLocator|elementHandle|mouse|keyboard|touchscreen|browserContext|browser|apiRequestContext|request|response|route|download|dialog|fileChooser|expect|worker|jsHandle|clock|tracing|video";
|
|
3098
3136
|
var CALL_RE = new RegExp(`\\b(${SUBJECTS})\\.(\\w+): `);
|
|
3137
|
+
function readLocatorHeader(text) {
|
|
3138
|
+
const header = /^[ \t]*Locator:[ \t]*(.+)$/m.exec(text);
|
|
3139
|
+
return header ? header[1].trim() : null;
|
|
3140
|
+
}
|
|
3141
|
+
var MAX_PARSED_ERROR_CHARS = 64 * 1024;
|
|
3099
3142
|
|
|
3100
3143
|
// ../core/src/error-signature.ts
|
|
3101
3144
|
var UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
|
|
@@ -3338,12 +3381,12 @@ function loadFlakePlan() {
|
|
|
3338
3381
|
throw new Error(`[Piwi Dashboard] ${errorMessage(error)} (${file})`);
|
|
3339
3382
|
}
|
|
3340
3383
|
}
|
|
3341
|
-
function relativeTestFile(file) {
|
|
3342
|
-
return file ?
|
|
3384
|
+
function relativeTestFile(file, base = process.cwd()) {
|
|
3385
|
+
return file ? relativeTestPath(file, base) : null;
|
|
3343
3386
|
}
|
|
3344
3387
|
function flakeRoleForTest(plan, test) {
|
|
3345
3388
|
const running = {
|
|
3346
|
-
file: relativeTestFile(test.file),
|
|
3389
|
+
file: relativeTestFile(test.file, test.base),
|
|
3347
3390
|
title: test.title,
|
|
3348
3391
|
suite: test.titlePath ? test.titlePath.slice(1, -1) : void 0
|
|
3349
3392
|
};
|
|
@@ -3459,7 +3502,7 @@ function flakeResultLine(plan, attempt) {
|
|
|
3459
3502
|
experimentId: plan.experimentId,
|
|
3460
3503
|
armId: plan.arm.id,
|
|
3461
3504
|
role: attempt.role,
|
|
3462
|
-
file: relativeTestFile(attempt.file),
|
|
3505
|
+
file: relativeTestFile(attempt.file, attempt.base),
|
|
3463
3506
|
title: attempt.title,
|
|
3464
3507
|
project: attempt.project,
|
|
3465
3508
|
browserName: attempt.browserName,
|
|
@@ -3485,20 +3528,468 @@ function recordFlakeResult(line) {
|
|
|
3485
3528
|
}
|
|
3486
3529
|
}
|
|
3487
3530
|
|
|
3488
|
-
// ../core/src/error-text.ts
|
|
3489
|
-
function joinErrorMessages(errors) {
|
|
3490
|
-
if (!errors || errors.length === 0) return null;
|
|
3491
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3492
|
-
const parts = [];
|
|
3493
|
-
for (const err of errors) {
|
|
3494
|
-
const msg = err.message ?? "";
|
|
3495
|
-
if (!msg) continue;
|
|
3496
|
-
const dedupeKey = msg.trim().slice(0, 200);
|
|
3497
|
-
if (seen.has(dedupeKey)) continue;
|
|
3498
|
-
seen.add(dedupeKey);
|
|
3499
|
-
parts.push(msg);
|
|
3500
|
-
}
|
|
3501
|
-
return parts.join("\n---\n") || null;
|
|
3531
|
+
// ../core/src/error-text.ts
|
|
3532
|
+
function joinErrorMessages(errors) {
|
|
3533
|
+
if (!errors || errors.length === 0) return null;
|
|
3534
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3535
|
+
const parts = [];
|
|
3536
|
+
for (const err of errors) {
|
|
3537
|
+
const msg = err.message ?? "";
|
|
3538
|
+
if (!msg) continue;
|
|
3539
|
+
const dedupeKey = msg.trim().slice(0, 200);
|
|
3540
|
+
if (seen.has(dedupeKey)) continue;
|
|
3541
|
+
seen.add(dedupeKey);
|
|
3542
|
+
parts.push(msg);
|
|
3543
|
+
}
|
|
3544
|
+
return parts.join("\n---\n") || null;
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
// ../core/src/coverage.ts
|
|
3548
|
+
var MAX_COVERAGE_FILES = 2e3;
|
|
3549
|
+
var MAX_COVERAGE_RANGES = 1e4;
|
|
3550
|
+
var MAX_LINE = 1e6;
|
|
3551
|
+
var MAX_PATH_LENGTH = 500;
|
|
3552
|
+
var BLOB_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
3553
|
+
function isRecord(value) {
|
|
3554
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3555
|
+
}
|
|
3556
|
+
function isRuntime(value) {
|
|
3557
|
+
return value === "browser" || value === "server";
|
|
3558
|
+
}
|
|
3559
|
+
function isLine(value) {
|
|
3560
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_LINE;
|
|
3561
|
+
}
|
|
3562
|
+
function validRanges(raw) {
|
|
3563
|
+
if (!Array.isArray(raw)) return [];
|
|
3564
|
+
const out = [];
|
|
3565
|
+
for (const item of raw) {
|
|
3566
|
+
if (!Array.isArray(item) || item.length !== 2) continue;
|
|
3567
|
+
const [start, end] = item;
|
|
3568
|
+
if (isLine(start) && isLine(end) && start <= end) out.push([start, end]);
|
|
3569
|
+
}
|
|
3570
|
+
return out.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
3571
|
+
}
|
|
3572
|
+
function normalizeLineRanges(ranges) {
|
|
3573
|
+
const out = [];
|
|
3574
|
+
for (const range of validRanges(ranges)) {
|
|
3575
|
+
const last = out[out.length - 1];
|
|
3576
|
+
if (last && range[0] <= last[1] + 1) last[1] = Math.max(last[1], range[1]);
|
|
3577
|
+
else out.push(range);
|
|
3578
|
+
}
|
|
3579
|
+
return out;
|
|
3580
|
+
}
|
|
3581
|
+
function normalizeFunctionRanges(ranges) {
|
|
3582
|
+
const out = [];
|
|
3583
|
+
for (const range of validRanges(ranges)) {
|
|
3584
|
+
const last = out[out.length - 1];
|
|
3585
|
+
if (!last || last[0] !== range[0] || last[1] !== range[1]) out.push(range);
|
|
3586
|
+
}
|
|
3587
|
+
return out;
|
|
3588
|
+
}
|
|
3589
|
+
function normalizeCoveragePath(raw) {
|
|
3590
|
+
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
3591
|
+
const path13 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3592
|
+
if (!path13 || path13.startsWith("../") || path13.startsWith("/") || /^[A-Za-z]:\//.test(path13)) return null;
|
|
3593
|
+
if (/(^|\/)node_modules\//.test(path13)) return null;
|
|
3594
|
+
return path13;
|
|
3595
|
+
}
|
|
3596
|
+
function fileEntry(path13, runtime, blob, functions = [], lines = []) {
|
|
3597
|
+
const file = { path: path13, runtime };
|
|
3598
|
+
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
3599
|
+
if (functions.length > 0) file.functions = functions;
|
|
3600
|
+
if (lines.length > 0) file.lines = lines;
|
|
3601
|
+
return file;
|
|
3602
|
+
}
|
|
3603
|
+
function captureOf(files, incomplete) {
|
|
3604
|
+
return incomplete ? { v: 1, files, incomplete: true } : { v: 1, files };
|
|
3605
|
+
}
|
|
3606
|
+
function compareFiles(a, b) {
|
|
3607
|
+
if (a.path !== b.path) return a.path < b.path ? -1 : 1;
|
|
3608
|
+
if (a.runtime !== b.runtime) return a.runtime < b.runtime ? -1 : 1;
|
|
3609
|
+
return 0;
|
|
3610
|
+
}
|
|
3611
|
+
function sanitizeCoverage(raw) {
|
|
3612
|
+
if (!isRecord(raw) || raw.v !== 1 || !Array.isArray(raw.files)) return null;
|
|
3613
|
+
const files = [];
|
|
3614
|
+
for (const item of raw.files) {
|
|
3615
|
+
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
3616
|
+
const path13 = normalizeCoveragePath(item.path);
|
|
3617
|
+
if (path13 === null) continue;
|
|
3618
|
+
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
3619
|
+
files.push(
|
|
3620
|
+
fileEntry(path13, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
3621
|
+
);
|
|
3622
|
+
}
|
|
3623
|
+
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
3624
|
+
if (!merged) return null;
|
|
3625
|
+
let incomplete = merged.incomplete === true || merged.files.length > MAX_COVERAGE_FILES;
|
|
3626
|
+
let budget = MAX_COVERAGE_RANGES;
|
|
3627
|
+
const capped = merged.files.slice(0, MAX_COVERAGE_FILES).map((file) => {
|
|
3628
|
+
const count = (file.functions?.length ?? 0) + (file.lines?.length ?? 0);
|
|
3629
|
+
if (count <= budget) {
|
|
3630
|
+
budget -= count;
|
|
3631
|
+
return file;
|
|
3632
|
+
}
|
|
3633
|
+
incomplete = true;
|
|
3634
|
+
return fileEntry(file.path, file.runtime);
|
|
3635
|
+
});
|
|
3636
|
+
return captureOf(capped, incomplete);
|
|
3637
|
+
}
|
|
3638
|
+
function mergeCoverage(...captures2) {
|
|
3639
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3640
|
+
let incomplete = false;
|
|
3641
|
+
for (const capture of captures2) {
|
|
3642
|
+
if (!capture) continue;
|
|
3643
|
+
if (capture.incomplete) incomplete = true;
|
|
3644
|
+
for (const file of capture.files) {
|
|
3645
|
+
const key = `${file.runtime}:${file.path}`;
|
|
3646
|
+
let entry = entries.get(key);
|
|
3647
|
+
if (!entry) {
|
|
3648
|
+
entry = { path: file.path, runtime: file.runtime, blobs: /* @__PURE__ */ new Set(), functions: [], lines: [] };
|
|
3649
|
+
entries.set(key, entry);
|
|
3650
|
+
}
|
|
3651
|
+
if (file.blob) entry.blobs.add(file.blob);
|
|
3652
|
+
for (const range of file.functions ?? []) entry.functions.push(range);
|
|
3653
|
+
for (const range of file.lines ?? []) entry.lines.push(range);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
if (entries.size === 0) return null;
|
|
3657
|
+
const files = [...entries.values()].map((entry) => {
|
|
3658
|
+
if (entry.blobs.size > 1) incomplete = true;
|
|
3659
|
+
const blob = entry.blobs.size === 1 ? [...entry.blobs][0] : void 0;
|
|
3660
|
+
return fileEntry(
|
|
3661
|
+
entry.path,
|
|
3662
|
+
entry.runtime,
|
|
3663
|
+
blob,
|
|
3664
|
+
normalizeFunctionRanges(entry.functions),
|
|
3665
|
+
normalizeLineRanges(entry.lines)
|
|
3666
|
+
);
|
|
3667
|
+
});
|
|
3668
|
+
return captureOf(files.sort(compareFiles), incomplete);
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
// ../core/src/source-map.ts
|
|
3672
|
+
var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3673
|
+
var BASE64_VALUE = new Int8Array(128).fill(-1);
|
|
3674
|
+
for (let i = 0; i < BASE64.length; i++) BASE64_VALUE[BASE64.charCodeAt(i)] = i;
|
|
3675
|
+
function decodeMappings(mappings, sourceBase) {
|
|
3676
|
+
const lines = [];
|
|
3677
|
+
let current = { columns: [], sources: [], originalLines: [] };
|
|
3678
|
+
let column = 0;
|
|
3679
|
+
let source = 0;
|
|
3680
|
+
let originalLine = 0;
|
|
3681
|
+
const fields = [];
|
|
3682
|
+
let value = 0;
|
|
3683
|
+
let shift = 0;
|
|
3684
|
+
const flush = () => {
|
|
3685
|
+
if (fields.length === 0) return;
|
|
3686
|
+
column += fields[0];
|
|
3687
|
+
if (fields.length >= 4) {
|
|
3688
|
+
source += fields[1];
|
|
3689
|
+
originalLine += fields[2];
|
|
3690
|
+
current.columns.push(column);
|
|
3691
|
+
current.sources.push(sourceBase + source);
|
|
3692
|
+
current.originalLines.push(originalLine);
|
|
3693
|
+
} else {
|
|
3694
|
+
current.columns.push(column);
|
|
3695
|
+
current.sources.push(-1);
|
|
3696
|
+
current.originalLines.push(-1);
|
|
3697
|
+
}
|
|
3698
|
+
fields.length = 0;
|
|
3699
|
+
};
|
|
3700
|
+
for (let i = 0; i <= mappings.length; i++) {
|
|
3701
|
+
const ch = i < mappings.length ? mappings.charCodeAt(i) : 59;
|
|
3702
|
+
if (ch === 59 || ch === 44) {
|
|
3703
|
+
flush();
|
|
3704
|
+
if (ch === 59) {
|
|
3705
|
+
lines.push(current);
|
|
3706
|
+
current = { columns: [], sources: [], originalLines: [] };
|
|
3707
|
+
column = 0;
|
|
3708
|
+
}
|
|
3709
|
+
continue;
|
|
3710
|
+
}
|
|
3711
|
+
const digit = ch < 128 ? BASE64_VALUE[ch] : -1;
|
|
3712
|
+
if (digit < 0) continue;
|
|
3713
|
+
value += (digit & 31) << shift;
|
|
3714
|
+
if (digit & 32) {
|
|
3715
|
+
shift += 5;
|
|
3716
|
+
continue;
|
|
3717
|
+
}
|
|
3718
|
+
fields.push(value & 1 ? -(value >>> 1) : value >>> 1);
|
|
3719
|
+
value = 0;
|
|
3720
|
+
shift = 0;
|
|
3721
|
+
}
|
|
3722
|
+
for (const line of lines) {
|
|
3723
|
+
if (line.columns.every((c, i) => i === 0 || c >= line.columns[i - 1])) continue;
|
|
3724
|
+
const order = line.columns.map((_, i) => i).sort((a, b) => line.columns[a] - line.columns[b]);
|
|
3725
|
+
line.columns = order.map((i) => line.columns[i]);
|
|
3726
|
+
line.sources = order.map((i) => line.sources[i]);
|
|
3727
|
+
line.originalLines = order.map((i) => line.originalLines[i]);
|
|
3728
|
+
}
|
|
3729
|
+
return lines;
|
|
3730
|
+
}
|
|
3731
|
+
function joinRoot(root, source) {
|
|
3732
|
+
if (!root || /^[a-z][\w+.-]*:/i.test(source) || source.startsWith("/")) return source;
|
|
3733
|
+
return root.endsWith("/") ? root + source : `${root}/${source}`;
|
|
3734
|
+
}
|
|
3735
|
+
function decodeLines(raw) {
|
|
3736
|
+
const sources = [];
|
|
3737
|
+
const decoded = [];
|
|
3738
|
+
const add = (map, lineOffset, columnOffset) => {
|
|
3739
|
+
if (map.sections) {
|
|
3740
|
+
for (const section of map.sections) {
|
|
3741
|
+
if (!add(section.map, section.offset.line, section.offset.column)) return false;
|
|
3742
|
+
}
|
|
3743
|
+
return true;
|
|
3744
|
+
}
|
|
3745
|
+
if (typeof map.mappings !== "string" || !Array.isArray(map.sources)) return false;
|
|
3746
|
+
const base = sources.length;
|
|
3747
|
+
for (const s of map.sources) sources.push(joinRoot(map.sourceRoot, s ?? ""));
|
|
3748
|
+
decodeMappings(map.mappings, base).forEach((segments, i) => {
|
|
3749
|
+
const at = lineOffset + i;
|
|
3750
|
+
const shifted = i === 0 && columnOffset ? segments.columns.map((c) => c + columnOffset) : segments.columns;
|
|
3751
|
+
const existing = decoded[at];
|
|
3752
|
+
if (!existing)
|
|
3753
|
+
decoded[at] = { columns: shifted, sources: segments.sources, originalLines: segments.originalLines };
|
|
3754
|
+
else {
|
|
3755
|
+
existing.columns.push(...shifted);
|
|
3756
|
+
existing.sources.push(...segments.sources);
|
|
3757
|
+
existing.originalLines.push(...segments.originalLines);
|
|
3758
|
+
}
|
|
3759
|
+
});
|
|
3760
|
+
return true;
|
|
3761
|
+
};
|
|
3762
|
+
return add(raw, 0, 0) ? { sources, lines: decoded } : null;
|
|
3763
|
+
}
|
|
3764
|
+
function compactSegments(lines) {
|
|
3765
|
+
let total = 0;
|
|
3766
|
+
for (const line of lines) total += line?.columns.length ?? 0;
|
|
3767
|
+
const segments = {
|
|
3768
|
+
lineStarts: new Int32Array(lines.length + 1),
|
|
3769
|
+
columns: new Int32Array(total),
|
|
3770
|
+
sources: new Int32Array(total),
|
|
3771
|
+
originalLines: new Int32Array(total)
|
|
3772
|
+
};
|
|
3773
|
+
let at = 0;
|
|
3774
|
+
for (let n = 0; n < lines.length; n++) {
|
|
3775
|
+
segments.lineStarts[n] = at;
|
|
3776
|
+
const line = lines[n];
|
|
3777
|
+
if (!line) continue;
|
|
3778
|
+
segments.columns.set(line.columns, at);
|
|
3779
|
+
segments.sources.set(line.sources, at);
|
|
3780
|
+
segments.originalLines.set(line.originalLines, at);
|
|
3781
|
+
at += line.columns.length;
|
|
3782
|
+
}
|
|
3783
|
+
segments.lineStarts[lines.length] = at;
|
|
3784
|
+
return segments;
|
|
3785
|
+
}
|
|
3786
|
+
function decodeSourceMap(raw) {
|
|
3787
|
+
if (raw.version !== void 0 && raw.version !== 3) return null;
|
|
3788
|
+
const decoded = decodeLines(raw);
|
|
3789
|
+
if (!decoded) return null;
|
|
3790
|
+
const { sources } = decoded;
|
|
3791
|
+
const segments = compactSegments(decoded.lines);
|
|
3792
|
+
const lineCount = segments.lineStarts.length - 1;
|
|
3793
|
+
const originalAt = (line, column) => {
|
|
3794
|
+
if (line < 0 || line >= lineCount) return null;
|
|
3795
|
+
let lo = segments.lineStarts[line];
|
|
3796
|
+
let hi = segments.lineStarts[line + 1] - 1;
|
|
3797
|
+
if (lo > hi || segments.columns[lo] > column) return null;
|
|
3798
|
+
while (lo < hi) {
|
|
3799
|
+
const mid = lo + hi + 1 >> 1;
|
|
3800
|
+
if (segments.columns[mid] <= column) lo = mid;
|
|
3801
|
+
else hi = mid - 1;
|
|
3802
|
+
}
|
|
3803
|
+
const source = segments.sources[lo];
|
|
3804
|
+
return source < 0 ? null : { source, line: segments.originalLines[lo] };
|
|
3805
|
+
};
|
|
3806
|
+
return {
|
|
3807
|
+
sources,
|
|
3808
|
+
sourceAt(line, column) {
|
|
3809
|
+
return originalAt(line, column)?.source ?? null;
|
|
3810
|
+
},
|
|
3811
|
+
originalAt,
|
|
3812
|
+
firstOriginalLineBetween(source, start, end) {
|
|
3813
|
+
let first = null;
|
|
3814
|
+
eachSegmentBetween(segments, start, end, (i) => {
|
|
3815
|
+
if (segments.sources[i] !== source || segments.originalLines[i] < 0) return false;
|
|
3816
|
+
first = segments.originalLines[i];
|
|
3817
|
+
return true;
|
|
3818
|
+
});
|
|
3819
|
+
return first;
|
|
3820
|
+
},
|
|
3821
|
+
originalLinesBetween(source, start, end, fromLine = 0) {
|
|
3822
|
+
let first = Number.POSITIVE_INFINITY;
|
|
3823
|
+
let last = -1;
|
|
3824
|
+
eachSegmentBetween(segments, start, end, (i) => {
|
|
3825
|
+
const line = segments.originalLines[i];
|
|
3826
|
+
if (segments.sources[i] === source && line >= fromLine) {
|
|
3827
|
+
if (line < first) first = line;
|
|
3828
|
+
if (line > last) last = line;
|
|
3829
|
+
}
|
|
3830
|
+
return false;
|
|
3831
|
+
});
|
|
3832
|
+
return last < 0 ? null : [first, last];
|
|
3833
|
+
}
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3836
|
+
function eachSegmentBetween(segments, start, end, visit) {
|
|
3837
|
+
const lineCount = segments.lineStarts.length - 1;
|
|
3838
|
+
for (let line = Math.max(start.line, 0); line <= end.line && line < lineCount; line++) {
|
|
3839
|
+
let i = segments.lineStarts[line];
|
|
3840
|
+
const stop = segments.lineStarts[line + 1];
|
|
3841
|
+
if (line === start.line) {
|
|
3842
|
+
let hi = stop;
|
|
3843
|
+
while (i < hi) {
|
|
3844
|
+
const mid = i + hi >> 1;
|
|
3845
|
+
if (segments.columns[mid] < start.column) i = mid + 1;
|
|
3846
|
+
else hi = mid;
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
for (; i < stop; i++) {
|
|
3850
|
+
if (line === end.line && segments.columns[i] >= end.column) return;
|
|
3851
|
+
if (visit(i)) return;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
function lineStarts(text) {
|
|
3856
|
+
const starts = [0];
|
|
3857
|
+
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) starts.push(i + 1);
|
|
3858
|
+
return starts;
|
|
3859
|
+
}
|
|
3860
|
+
function offsetToPosition(starts, offset) {
|
|
3861
|
+
let lo = 0;
|
|
3862
|
+
let hi = starts.length - 1;
|
|
3863
|
+
while (lo < hi) {
|
|
3864
|
+
const mid = lo + hi + 1 >> 1;
|
|
3865
|
+
if (starts[mid] <= offset) lo = mid;
|
|
3866
|
+
else hi = mid - 1;
|
|
3867
|
+
}
|
|
3868
|
+
return { line: lo, column: offset - starts[lo] };
|
|
3869
|
+
}
|
|
3870
|
+
function sourceMappingUrl(script) {
|
|
3871
|
+
const tail = script.slice(-4096);
|
|
3872
|
+
const matches = [...tail.matchAll(/(?:\/\/|\/\*)[#@]\s*sourceMappingURL=([^\s*'"]+)/g)];
|
|
3873
|
+
return matches.length ? matches[matches.length - 1][1] : null;
|
|
3874
|
+
}
|
|
3875
|
+
function decodeDataUrl(url) {
|
|
3876
|
+
const m = /^data:([^,]*?)(;base64)?,(.*)$/s.exec(url);
|
|
3877
|
+
if (!m) return null;
|
|
3878
|
+
try {
|
|
3879
|
+
if (!m[2]) return decodeURIComponent(m[3]);
|
|
3880
|
+
const binary = globalThis.atob(m[3]);
|
|
3881
|
+
const bytes = new Uint8Array(binary.length);
|
|
3882
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
3883
|
+
return new TextDecoder().decode(bytes);
|
|
3884
|
+
} catch {
|
|
3885
|
+
return null;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
function normalizeSourcePath(source) {
|
|
3889
|
+
let s = source.replace(/[?#].*$/, "");
|
|
3890
|
+
s = s.replace(/^webpack:\/\/[^/]*\//, "").replace(/^webpack:\/\//, "");
|
|
3891
|
+
s = s.replace(/^(?:vite|rollup|turbopack|ng):\/*/, "");
|
|
3892
|
+
s = s.replace(/^\[project\]\//, "");
|
|
3893
|
+
s = s.replace(/^file:\/\//, "");
|
|
3894
|
+
s = s.replace(/^\/@fs\//, "/");
|
|
3895
|
+
s = s.replace(/^\/@id\//, "");
|
|
3896
|
+
try {
|
|
3897
|
+
s = decodeURIComponent(s);
|
|
3898
|
+
} catch {
|
|
3899
|
+
}
|
|
3900
|
+
s = s.replace(/\\/g, "/");
|
|
3901
|
+
if (/^\/[A-Za-z]:\//.test(s)) s = s.slice(1);
|
|
3902
|
+
s = s.replace(/(^|\/)\.\//g, "$1");
|
|
3903
|
+
return s;
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
// ../core/src/code-reach.ts
|
|
3907
|
+
var MAX_CODE_REACH_FILES = 2e3;
|
|
3908
|
+
function scriptText(source) {
|
|
3909
|
+
return { length: source.length, starts: Uint32Array.from(lineStarts(source)) };
|
|
3910
|
+
}
|
|
3911
|
+
var textOf = (entry, text) => text ?? (entry.source === void 0 ? void 0 : scriptText(entry.source));
|
|
3912
|
+
function executedFunctionRanges(entry, length, all = false) {
|
|
3913
|
+
length ??= entry.source?.length ?? Number.POSITIVE_INFINITY;
|
|
3914
|
+
const out = [];
|
|
3915
|
+
entry.functions.forEach((fn, i) => {
|
|
3916
|
+
const whole = fn.ranges[0];
|
|
3917
|
+
if (!whole || !all && whole.count <= 0) return;
|
|
3918
|
+
const topLevel = whole.startOffset === 0 && (whole.endOffset >= length || i === 0 && fn.functionName === "");
|
|
3919
|
+
if (topLevel) return;
|
|
3920
|
+
out.push({ start: whole.startOffset, end: whole.endOffset });
|
|
3921
|
+
});
|
|
3922
|
+
return out;
|
|
3923
|
+
}
|
|
3924
|
+
function scriptRan(entry, text) {
|
|
3925
|
+
return executedFunctionRanges(entry, text?.length).length > 0;
|
|
3926
|
+
}
|
|
3927
|
+
function reachedFunctionLines(entry, map, text) {
|
|
3928
|
+
return functionLines(entry, map, text, false);
|
|
3929
|
+
}
|
|
3930
|
+
function knownFunctionLines(entry, map, text) {
|
|
3931
|
+
return functionLines(entry, map, text, true);
|
|
3932
|
+
}
|
|
3933
|
+
function functionLines(entry, map, text, all) {
|
|
3934
|
+
const out = /* @__PURE__ */ new Map();
|
|
3935
|
+
const read = textOf(entry, text);
|
|
3936
|
+
const ranges = executedFunctionRanges(entry, read?.length, all);
|
|
3937
|
+
if (!ranges.length || !read) return out;
|
|
3938
|
+
const add = (source, range) => {
|
|
3939
|
+
const list = out.get(source);
|
|
3940
|
+
if (list) list.push(range);
|
|
3941
|
+
else out.set(source, [range]);
|
|
3942
|
+
};
|
|
3943
|
+
const starts = read.starts;
|
|
3944
|
+
for (const range of ranges) {
|
|
3945
|
+
const start = offsetToPosition(starts, range.start);
|
|
3946
|
+
if (!map) {
|
|
3947
|
+
const last = offsetToPosition(starts, Math.max(range.start, range.end - 1));
|
|
3948
|
+
add(-1, [start.line + 1, last.line + 1]);
|
|
3949
|
+
continue;
|
|
3950
|
+
}
|
|
3951
|
+
const at = map.originalAt(start.line, start.column);
|
|
3952
|
+
if (at === null) continue;
|
|
3953
|
+
const end = offsetToPosition(starts, range.end);
|
|
3954
|
+
const first = map.firstOriginalLineBetween(at.source, start, end);
|
|
3955
|
+
if (first === null) continue;
|
|
3956
|
+
const header = first === at.line || first === at.line + 1 ? at.line : first;
|
|
3957
|
+
const lines = map.originalLinesBetween(at.source, start, end, header);
|
|
3958
|
+
if (lines) add(at.source, [header + 1, lines[1] + 1]);
|
|
3959
|
+
}
|
|
3960
|
+
return out;
|
|
3961
|
+
}
|
|
3962
|
+
function finalizeCodeReach(files) {
|
|
3963
|
+
const out = /* @__PURE__ */ new Set();
|
|
3964
|
+
for (const raw of files) {
|
|
3965
|
+
const file = normalizeCoveragePath(raw);
|
|
3966
|
+
if (file !== null) out.add(file);
|
|
3967
|
+
}
|
|
3968
|
+
return [...out].sort().slice(0, MAX_CODE_REACH_FILES);
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
// ../core/src/mask.ts
|
|
3972
|
+
var DATA_URI_RE = /\bdata:[a-z0-9.+-]+\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/=]+/gi;
|
|
3973
|
+
var JWT_RE = /\beyJ[\w-]{10,}\.[\w-]{5,}\.[\w-]{5,}\b/g;
|
|
3974
|
+
var LONG_HEX_RE2 = /\b[0-9a-f]{32,}\b/gi;
|
|
3975
|
+
function maskTokenLike(text) {
|
|
3976
|
+
return text.replace(DATA_URI_RE, "data:[masked]").replace(JWT_RE, "[masked-token]").replace(LONG_HEX_RE2, "[masked-hex]");
|
|
3977
|
+
}
|
|
3978
|
+
var CREDENTIAL_PARAM_RE = /token|code|secret|password|passwd|pwd|key|session|auth|sig|credential|assertion|nonce|otp|jwt/i;
|
|
3979
|
+
function maskUrlFragment(hash) {
|
|
3980
|
+
const body = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
3981
|
+
const query = body.indexOf("?");
|
|
3982
|
+
const route = query >= 0 ? body.slice(0, query) : body.includes("=") ? "" : body;
|
|
3983
|
+
const params = query >= 0 ? body.slice(query + 1) : body.includes("=") ? body : null;
|
|
3984
|
+
const masked = params === null ? null : params.split("&").map((param) => {
|
|
3985
|
+
const eq = param.indexOf("=");
|
|
3986
|
+
if (eq < 0) return maskTokenLike(param);
|
|
3987
|
+
const name = param.slice(0, eq);
|
|
3988
|
+
const value = param.slice(eq + 1);
|
|
3989
|
+
return `${name}=${value && CREDENTIAL_PARAM_RE.test(name) ? "[masked]" : maskTokenLike(value)}`;
|
|
3990
|
+
}).join("&");
|
|
3991
|
+
const out = maskTokenLike(route) + (masked === null ? "" : query >= 0 ? `?${masked}` : masked);
|
|
3992
|
+
return hash.startsWith("#") ? `#${out}` : out;
|
|
3502
3993
|
}
|
|
3503
3994
|
|
|
3504
3995
|
// src/internal/capture/inspect-on-failure.ts
|
|
@@ -3537,8 +4028,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
|
|
|
3537
4028
|
}
|
|
3538
4029
|
|
|
3539
4030
|
// src/internal/capture/pick-on-failure.ts
|
|
3540
|
-
var
|
|
3541
|
-
var ANSI_RE2 = /\x1b\[[0-9;]*m/g;
|
|
4031
|
+
var path4 = __toESM(require("path"));
|
|
3542
4032
|
function parseLeafLocatorExpression(rawExpr) {
|
|
3543
4033
|
const leaf = parseLeafLocatorCall(rawExpr);
|
|
3544
4034
|
return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
|
|
@@ -3547,14 +4037,13 @@ function deriveFailedLocator(testInfo) {
|
|
|
3547
4037
|
const info = testInfo;
|
|
3548
4038
|
const errors = info.errors && info.errors.length > 0 ? info.errors : info.error ? [info.error] : [];
|
|
3549
4039
|
for (const err of errors) {
|
|
3550
|
-
const
|
|
3551
|
-
${err.stack ?? ""}
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
const parsed2 = parseLeafLocatorExpression(line[1].trim());
|
|
4040
|
+
const header = readLocatorHeader(stripAnsi(`${err.message ?? ""}
|
|
4041
|
+
${err.stack ?? ""}`));
|
|
4042
|
+
if (!header) continue;
|
|
4043
|
+
const parsed2 = parseLeafLocatorExpression(header);
|
|
3555
4044
|
if (!parsed2) continue;
|
|
3556
4045
|
const loc = err.location;
|
|
3557
|
-
const location = loc ? `${
|
|
4046
|
+
const location = loc ? `${path4.relative(process.cwd(), loc.file).split(path4.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
|
3558
4047
|
return { method: parsed2.method, args: parsed2.args, location };
|
|
3559
4048
|
}
|
|
3560
4049
|
return null;
|
|
@@ -3707,7 +4196,7 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
3707
4196
|
}
|
|
3708
4197
|
|
|
3709
4198
|
// src/internal/capture/pause-at.ts
|
|
3710
|
-
var
|
|
4199
|
+
var path5 = __toESM(require("path"));
|
|
3711
4200
|
|
|
3712
4201
|
// ../core/src/editor-send.ts
|
|
3713
4202
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
@@ -3751,9 +4240,9 @@ var PauseSet = class {
|
|
|
3751
4240
|
var EMPTY = new PauseSet(/* @__PURE__ */ new Set());
|
|
3752
4241
|
function callSiteFile(file, cwd) {
|
|
3753
4242
|
const slashed = file.replace(/\\/g, "/");
|
|
3754
|
-
const absolute =
|
|
3755
|
-
let rel = absolute ?
|
|
3756
|
-
rel = rel.split(
|
|
4243
|
+
const absolute = path5.isAbsolute(file) || path5.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
|
|
4244
|
+
let rel = absolute ? path5.relative(cwd, file) : path5.normalize(slashed);
|
|
4245
|
+
rel = rel.split(path5.sep).join("/").replace(/\\/g, "/");
|
|
3757
4246
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
3758
4247
|
return rel;
|
|
3759
4248
|
}
|
|
@@ -3909,385 +4398,194 @@ function isServerGranularity(value) {
|
|
|
3909
4398
|
return typeof value === "string" && SERVER_VALUES.has(value);
|
|
3910
4399
|
}
|
|
3911
4400
|
function browserCoverageInWorker() {
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
function serverCoverageInWorker() {
|
|
3915
|
-
return isServerGranularity(process.env[PIWI_ENV_KEYS.coverageServer]);
|
|
3916
|
-
}
|
|
3917
|
-
|
|
3918
|
-
// src/internal/support/aria-sampling.ts
|
|
3919
|
-
var path7 = __toESM(require("path"));
|
|
3920
|
-
var os3 = __toESM(require("os"));
|
|
3921
|
-
var fs4 = __toESM(require("fs"));
|
|
3922
|
-
|
|
3923
|
-
// src/internal/support/instance-id.ts
|
|
3924
|
-
var crypto = __toESM(require("crypto"));
|
|
3925
|
-
var os2 = __toESM(require("os"));
|
|
3926
|
-
function hashForProject(projectName) {
|
|
3927
|
-
return crypto.createHash("sha1").update(projectName).digest("hex").slice(0, 16);
|
|
3928
|
-
}
|
|
3929
|
-
|
|
3930
|
-
// src/internal/support/aria-sampling.ts
|
|
3931
|
-
function ariaSampleIdentity(filePath, title) {
|
|
3932
|
-
return `${filePath}\0${title}`;
|
|
3933
|
-
}
|
|
3934
|
-
function getAriaSampleFilePath(projectName) {
|
|
3935
|
-
return path7.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
3936
|
-
}
|
|
3937
|
-
var cachedSets = /* @__PURE__ */ new Map();
|
|
3938
|
-
function loadAriaSampleSet(projectName) {
|
|
3939
|
-
if (cachedSets.has(projectName)) return cachedSets.get(projectName);
|
|
3940
|
-
let set = null;
|
|
3941
|
-
try {
|
|
3942
|
-
const raw = fs4.readFileSync(getAriaSampleFilePath(projectName), "utf8");
|
|
3943
|
-
const parsed2 = JSON.parse(raw);
|
|
3944
|
-
if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
|
|
3945
|
-
set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
|
|
3946
|
-
}
|
|
3947
|
-
} catch {
|
|
3948
|
-
set = null;
|
|
3949
|
-
}
|
|
3950
|
-
cachedSets.set(projectName, set);
|
|
3951
|
-
return set;
|
|
3952
|
-
}
|
|
3953
|
-
function relativeTestFile2(file) {
|
|
3954
|
-
return path7.relative(process.cwd(), file).split(path7.sep).join("/");
|
|
3955
|
-
}
|
|
3956
|
-
function isDueForAriaSample(testInfo) {
|
|
3957
|
-
const projectName = process.env.PIWI_PROJECT_NAME;
|
|
3958
|
-
if (!projectName) return false;
|
|
3959
|
-
const set = loadAriaSampleSet(projectName);
|
|
3960
|
-
if (!set || set.size === 0) return false;
|
|
3961
|
-
return set.has(ariaSampleIdentity(relativeTestFile2(testInfo.file), testInfo.title));
|
|
3962
|
-
}
|
|
3963
|
-
|
|
3964
|
-
// src/internal/capture/quiet-capture.ts
|
|
3965
|
-
var import_node_module = require("module");
|
|
3966
|
-
var path9 = __toESM(require("path"));
|
|
3967
|
-
|
|
3968
|
-
// src/internal/support/reporter-version.ts
|
|
3969
|
-
var fs5 = __toESM(require("fs"));
|
|
3970
|
-
var path8 = __toESM(require("path"));
|
|
3971
|
-
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
3972
|
-
function findOwnPackageJson(fromDir) {
|
|
3973
|
-
for (const offset of CANDIDATE_OFFSETS) {
|
|
3974
|
-
try {
|
|
3975
|
-
const pkgPath = path8.resolve(fromDir, offset, "package.json");
|
|
3976
|
-
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
3977
|
-
const { name, version } = pkg;
|
|
3978
|
-
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
3979
|
-
return { version, root: path8.dirname(pkgPath) };
|
|
3980
|
-
} catch {
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
return void 0;
|
|
3984
|
-
}
|
|
3985
|
-
|
|
3986
|
-
// src/internal/capture/quiet-capture.ts
|
|
3987
|
-
var BOXED_PREFIX_EXPORTS = [
|
|
3988
|
-
[
|
|
3989
|
-
"playwright-core/lib/coreBundle",
|
|
3990
|
-
(mod) => mod?.utils?.setBoxedStackPrefixes
|
|
3991
|
-
],
|
|
3992
|
-
["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
|
|
3993
|
-
];
|
|
3994
|
-
var boxing;
|
|
3995
|
-
var resolvedPlaywrightPackageJson;
|
|
3996
|
-
function resolvePlaywrightPackageJson() {
|
|
3997
|
-
if (resolvedPlaywrightPackageJson !== void 0) return resolvedPlaywrightPackageJson;
|
|
3998
|
-
try {
|
|
3999
|
-
const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
|
|
4000
|
-
resolvedPlaywrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
|
|
4001
|
-
} catch {
|
|
4002
|
-
resolvedPlaywrightPackageJson = null;
|
|
4003
|
-
}
|
|
4004
|
-
return resolvedPlaywrightPackageJson;
|
|
4005
|
-
}
|
|
4006
|
-
function resolveBoxing() {
|
|
4007
|
-
try {
|
|
4008
|
-
const root = findOwnPackageJson(__dirname)?.root;
|
|
4009
|
-
if (!root) return null;
|
|
4010
|
-
const playwrightPackageJson = resolvePlaywrightPackageJson();
|
|
4011
|
-
if (!playwrightPackageJson) return null;
|
|
4012
|
-
const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
|
|
4013
|
-
for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
|
|
4014
|
-
let set;
|
|
4015
|
-
try {
|
|
4016
|
-
set = pick(requireFromPlaywright(moduleId));
|
|
4017
|
-
} catch {
|
|
4018
|
-
continue;
|
|
4019
|
-
}
|
|
4020
|
-
if (typeof set !== "function") continue;
|
|
4021
|
-
return {
|
|
4022
|
-
set,
|
|
4023
|
-
prefixes: [path9.dirname(playwrightPackageJson), path9.join(root, "dist") + path9.sep]
|
|
4024
|
-
};
|
|
4025
|
-
}
|
|
4026
|
-
} catch {
|
|
4027
|
-
}
|
|
4028
|
-
return null;
|
|
4029
|
-
}
|
|
4030
|
-
function boxCaptureFrames(extraPrefixes = []) {
|
|
4031
|
-
const first = boxing === void 0;
|
|
4032
|
-
if (first) boxing = resolveBoxing();
|
|
4033
|
-
if (!boxing) return;
|
|
4034
|
-
const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
|
|
4035
|
-
if (!first && added.length === 0) return;
|
|
4036
|
-
boxing.prefixes.push(...added);
|
|
4037
|
-
try {
|
|
4038
|
-
boxing.set([...boxing.prefixes]);
|
|
4039
|
-
} catch {
|
|
4040
|
-
}
|
|
4041
|
-
}
|
|
4042
|
-
function callMethod(target, prop, args) {
|
|
4043
|
-
return target[prop](...args);
|
|
4044
|
-
}
|
|
4045
|
-
function internalCall(owner, run) {
|
|
4046
|
-
const wrap = owner?._wrapApiCall;
|
|
4047
|
-
if (typeof wrap !== "function") return run();
|
|
4048
|
-
return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
|
|
4049
|
-
}
|
|
4050
|
-
|
|
4051
|
-
// src/internal/capture/code-reach.ts
|
|
4052
|
-
var crypto2 = __toESM(require("crypto"));
|
|
4053
|
-
var fs6 = __toESM(require("fs"));
|
|
4054
|
-
var path10 = __toESM(require("path"));
|
|
4055
|
-
var import_node_child_process = require("child_process");
|
|
4056
|
-
|
|
4057
|
-
// ../core/src/coverage.ts
|
|
4058
|
-
var MAX_PATH_LENGTH = 500;
|
|
4059
|
-
function normalizeCoveragePath(raw) {
|
|
4060
|
-
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
4061
|
-
const path14 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4062
|
-
if (!path14 || path14.startsWith("../") || path14.startsWith("/") || /^[A-Za-z]:\//.test(path14)) return null;
|
|
4063
|
-
if (/(^|\/)node_modules\//.test(path14)) return null;
|
|
4064
|
-
return path14;
|
|
4401
|
+
const browser = process.env[PIWI_ENV_KEYS.coverageBrowser];
|
|
4402
|
+
return process.env[PIWI_ENV_KEYS.captureCodeReach] === "true" || browser === "files" || browser === "functions";
|
|
4065
4403
|
}
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4069
|
-
var BASE64_VALUE = new Int8Array(128).fill(-1);
|
|
4070
|
-
for (let i = 0; i < BASE64.length; i++) BASE64_VALUE[BASE64.charCodeAt(i)] = i;
|
|
4071
|
-
function decodeMappings(mappings, sourceBase) {
|
|
4072
|
-
const lines = [];
|
|
4073
|
-
let current = { columns: [], sources: [] };
|
|
4074
|
-
let column = 0;
|
|
4075
|
-
let source = 0;
|
|
4076
|
-
const fields = [];
|
|
4077
|
-
let value = 0;
|
|
4078
|
-
let shift = 0;
|
|
4079
|
-
const flush = () => {
|
|
4080
|
-
if (fields.length === 0) return;
|
|
4081
|
-
column += fields[0];
|
|
4082
|
-
if (fields.length >= 4) {
|
|
4083
|
-
source += fields[1];
|
|
4084
|
-
current.columns.push(column);
|
|
4085
|
-
current.sources.push(sourceBase + source);
|
|
4086
|
-
} else {
|
|
4087
|
-
current.columns.push(column);
|
|
4088
|
-
current.sources.push(-1);
|
|
4089
|
-
}
|
|
4090
|
-
fields.length = 0;
|
|
4091
|
-
};
|
|
4092
|
-
for (let i = 0; i <= mappings.length; i++) {
|
|
4093
|
-
const ch = i < mappings.length ? mappings.charCodeAt(i) : 59;
|
|
4094
|
-
if (ch === 59 || ch === 44) {
|
|
4095
|
-
flush();
|
|
4096
|
-
if (ch === 59) {
|
|
4097
|
-
lines.push(current);
|
|
4098
|
-
current = { columns: [], sources: [] };
|
|
4099
|
-
column = 0;
|
|
4100
|
-
}
|
|
4101
|
-
continue;
|
|
4102
|
-
}
|
|
4103
|
-
const digit = ch < 128 ? BASE64_VALUE[ch] : -1;
|
|
4104
|
-
if (digit < 0) continue;
|
|
4105
|
-
value += (digit & 31) << shift;
|
|
4106
|
-
if (digit & 32) {
|
|
4107
|
-
shift += 5;
|
|
4108
|
-
continue;
|
|
4109
|
-
}
|
|
4110
|
-
fields.push(value & 1 ? -(value >>> 1) : value >>> 1);
|
|
4111
|
-
value = 0;
|
|
4112
|
-
shift = 0;
|
|
4113
|
-
}
|
|
4114
|
-
for (const line of lines) {
|
|
4115
|
-
if (line.columns.every((c, i) => i === 0 || c >= line.columns[i - 1])) continue;
|
|
4116
|
-
const order = line.columns.map((_, i) => i).sort((a, b) => line.columns[a] - line.columns[b]);
|
|
4117
|
-
line.columns = order.map((i) => line.columns[i]);
|
|
4118
|
-
line.sources = order.map((i) => line.sources[i]);
|
|
4119
|
-
}
|
|
4120
|
-
return lines;
|
|
4404
|
+
function browserFunctionsInWorker() {
|
|
4405
|
+
return process.env[PIWI_ENV_KEYS.coverageBrowser] === "functions";
|
|
4121
4406
|
}
|
|
4122
|
-
function
|
|
4123
|
-
|
|
4124
|
-
|
|
4407
|
+
function browserFunctionCoverage(files) {
|
|
4408
|
+
return sanitizeCoverage({
|
|
4409
|
+
v: 1,
|
|
4410
|
+
files: [...files].map(([path13, functions]) => ({ path: path13, runtime: "browser", functions }))
|
|
4411
|
+
});
|
|
4125
4412
|
}
|
|
4126
|
-
function
|
|
4127
|
-
const
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
return true;
|
|
4135
|
-
}
|
|
4136
|
-
if (typeof map.mappings !== "string" || !Array.isArray(map.sources)) return false;
|
|
4137
|
-
const base = sources.length;
|
|
4138
|
-
for (const s of map.sources) sources.push(joinRoot(map.sourceRoot, s ?? ""));
|
|
4139
|
-
decodeMappings(map.mappings, base).forEach((segments, i) => {
|
|
4140
|
-
const at = lineOffset + i;
|
|
4141
|
-
const shifted = i === 0 && columnOffset ? segments.columns.map((c) => c + columnOffset) : segments.columns;
|
|
4142
|
-
const existing = lines[at];
|
|
4143
|
-
if (!existing) lines[at] = { columns: shifted, sources: segments.sources };
|
|
4144
|
-
else {
|
|
4145
|
-
existing.columns.push(...shifted);
|
|
4146
|
-
existing.sources.push(...segments.sources);
|
|
4147
|
-
}
|
|
4148
|
-
});
|
|
4149
|
-
return true;
|
|
4150
|
-
};
|
|
4151
|
-
if (raw.version !== void 0 && raw.version !== 3) return null;
|
|
4152
|
-
if (!add(raw, 0, 0)) return null;
|
|
4153
|
-
return {
|
|
4154
|
-
sources,
|
|
4155
|
-
sourceAt(line, column) {
|
|
4156
|
-
const segments = lines[line];
|
|
4157
|
-
if (!segments || segments.columns.length === 0) return null;
|
|
4158
|
-
let lo = 0;
|
|
4159
|
-
let hi = segments.columns.length - 1;
|
|
4160
|
-
if (segments.columns[0] > column) return null;
|
|
4161
|
-
while (lo < hi) {
|
|
4162
|
-
const mid = lo + hi + 1 >> 1;
|
|
4163
|
-
if (segments.columns[mid] <= column) lo = mid;
|
|
4164
|
-
else hi = mid - 1;
|
|
4165
|
-
}
|
|
4166
|
-
const source = segments.sources[lo];
|
|
4167
|
-
return source < 0 ? null : source;
|
|
4168
|
-
},
|
|
4169
|
-
mapsBetween(source, start, end) {
|
|
4170
|
-
for (let line = start.line; line <= end.line && line < lines.length; line++) {
|
|
4171
|
-
const segments = lines[line];
|
|
4172
|
-
if (!segments) continue;
|
|
4173
|
-
let i = 0;
|
|
4174
|
-
if (line === start.line) {
|
|
4175
|
-
let hi = segments.columns.length;
|
|
4176
|
-
while (i < hi) {
|
|
4177
|
-
const mid = i + hi >> 1;
|
|
4178
|
-
if (segments.columns[mid] < start.column) i = mid + 1;
|
|
4179
|
-
else hi = mid;
|
|
4180
|
-
}
|
|
4181
|
-
}
|
|
4182
|
-
for (; i < segments.columns.length; i++) {
|
|
4183
|
-
if (line === end.line && segments.columns[i] >= end.column) return false;
|
|
4184
|
-
if (segments.sources[i] === source) return true;
|
|
4185
|
-
}
|
|
4186
|
-
}
|
|
4187
|
-
return false;
|
|
4188
|
-
}
|
|
4413
|
+
function newKnownFunctions(known, sent) {
|
|
4414
|
+
const parts = [];
|
|
4415
|
+
let files = [];
|
|
4416
|
+
let budget = MAX_COVERAGE_RANGES;
|
|
4417
|
+
const close = () => {
|
|
4418
|
+
if (files.length > 0) parts.push({ v: 1, files: files.sort((a, b) => a.path < b.path ? -1 : 1) });
|
|
4419
|
+
files = [];
|
|
4420
|
+
budget = MAX_COVERAGE_RANGES;
|
|
4189
4421
|
};
|
|
4422
|
+
for (const [raw, ranges] of known) {
|
|
4423
|
+
const path13 = normalizeCoveragePath(raw);
|
|
4424
|
+
if (path13 === null) continue;
|
|
4425
|
+
let named = sent.get(path13);
|
|
4426
|
+
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
4427
|
+
if (fresh.length === 0) continue;
|
|
4428
|
+
if (!named) sent.set(path13, named = /* @__PURE__ */ new Set());
|
|
4429
|
+
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
4430
|
+
while (fresh.length > 0) {
|
|
4431
|
+
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
4432
|
+
const part = fresh.slice(0, budget);
|
|
4433
|
+
fresh = fresh.slice(part.length);
|
|
4434
|
+
budget -= part.length;
|
|
4435
|
+
files.push({ path: path13, allFunctions: part });
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
close();
|
|
4439
|
+
return parts;
|
|
4190
4440
|
}
|
|
4191
|
-
function
|
|
4192
|
-
|
|
4193
|
-
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) starts.push(i + 1);
|
|
4194
|
-
return starts;
|
|
4441
|
+
function serverCoverageInWorker() {
|
|
4442
|
+
return isServerGranularity(process.env[PIWI_ENV_KEYS.coverageServer]);
|
|
4195
4443
|
}
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4444
|
+
|
|
4445
|
+
// src/internal/support/aria-sampling.ts
|
|
4446
|
+
var path6 = __toESM(require("path"));
|
|
4447
|
+
var os3 = __toESM(require("os"));
|
|
4448
|
+
var fs4 = __toESM(require("fs"));
|
|
4449
|
+
|
|
4450
|
+
// src/internal/support/instance-id.ts
|
|
4451
|
+
var crypto = __toESM(require("crypto"));
|
|
4452
|
+
var os2 = __toESM(require("os"));
|
|
4453
|
+
function hashForProject(projectName) {
|
|
4454
|
+
return crypto.createHash("sha1").update(projectName).digest("hex").slice(0, 16);
|
|
4205
4455
|
}
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
return
|
|
4456
|
+
|
|
4457
|
+
// src/internal/support/aria-sampling.ts
|
|
4458
|
+
function ariaSampleIdentity(filePath, title) {
|
|
4459
|
+
return `${filePath}\0${title}`;
|
|
4210
4460
|
}
|
|
4211
|
-
function
|
|
4212
|
-
|
|
4213
|
-
if (!m) return null;
|
|
4214
|
-
try {
|
|
4215
|
-
if (!m[2]) return decodeURIComponent(m[3]);
|
|
4216
|
-
const binary = globalThis.atob(m[3]);
|
|
4217
|
-
const bytes = new Uint8Array(binary.length);
|
|
4218
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
4219
|
-
return new TextDecoder().decode(bytes);
|
|
4220
|
-
} catch {
|
|
4221
|
-
return null;
|
|
4222
|
-
}
|
|
4461
|
+
function getAriaSampleFilePath(projectName) {
|
|
4462
|
+
return path6.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
4223
4463
|
}
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
s = s.replace(/^\[project\]\//, "");
|
|
4229
|
-
s = s.replace(/^file:\/\//, "");
|
|
4230
|
-
s = s.replace(/^\/@fs\//, "/");
|
|
4231
|
-
s = s.replace(/^\/@id\//, "");
|
|
4464
|
+
var cachedSets = /* @__PURE__ */ new Map();
|
|
4465
|
+
function loadAriaSampleSet(projectName) {
|
|
4466
|
+
if (cachedSets.has(projectName)) return cachedSets.get(projectName);
|
|
4467
|
+
let set = null;
|
|
4232
4468
|
try {
|
|
4233
|
-
|
|
4469
|
+
const raw = fs4.readFileSync(getAriaSampleFilePath(projectName), "utf8");
|
|
4470
|
+
const parsed2 = JSON.parse(raw);
|
|
4471
|
+
if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
|
|
4472
|
+
set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
|
|
4473
|
+
}
|
|
4234
4474
|
} catch {
|
|
4475
|
+
set = null;
|
|
4235
4476
|
}
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
s = s.replace(/(^|\/)\.\//g, "$1");
|
|
4239
|
-
return s;
|
|
4477
|
+
cachedSets.set(projectName, set);
|
|
4478
|
+
return set;
|
|
4240
4479
|
}
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
if (!whole || whole.count <= 0) return;
|
|
4250
|
-
const topLevel = whole.startOffset === 0 && (whole.endOffset >= length || i === 0 && fn.functionName === "");
|
|
4251
|
-
if (topLevel) return;
|
|
4252
|
-
out.push({ start: whole.startOffset, end: whole.endOffset });
|
|
4253
|
-
});
|
|
4254
|
-
return out;
|
|
4480
|
+
function isDueForAriaSample(testInfo) {
|
|
4481
|
+
const projectName = process.env.PIWI_PROJECT_NAME;
|
|
4482
|
+
if (!projectName) return false;
|
|
4483
|
+
const set = loadAriaSampleSet(projectName);
|
|
4484
|
+
if (!set || set.size === 0) return false;
|
|
4485
|
+
return set.has(
|
|
4486
|
+
ariaSampleIdentity(relativeTestPath(testInfo.file, configDirFromConfig(testInfo.config)), testInfo.title)
|
|
4487
|
+
);
|
|
4255
4488
|
}
|
|
4256
|
-
|
|
4257
|
-
|
|
4489
|
+
|
|
4490
|
+
// src/internal/capture/quiet-capture.ts
|
|
4491
|
+
var import_node_module = require("module");
|
|
4492
|
+
var path8 = __toESM(require("path"));
|
|
4493
|
+
|
|
4494
|
+
// src/internal/support/reporter-version.ts
|
|
4495
|
+
var fs5 = __toESM(require("fs"));
|
|
4496
|
+
var path7 = __toESM(require("path"));
|
|
4497
|
+
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
4498
|
+
function findOwnPackageJson(fromDir) {
|
|
4499
|
+
for (const offset of CANDIDATE_OFFSETS) {
|
|
4500
|
+
try {
|
|
4501
|
+
const pkgPath = path7.resolve(fromDir, offset, "package.json");
|
|
4502
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
4503
|
+
const { name, version } = pkg;
|
|
4504
|
+
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
4505
|
+
return { version, root: path7.dirname(pkgPath) };
|
|
4506
|
+
} catch {
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
return void 0;
|
|
4258
4510
|
}
|
|
4259
|
-
|
|
4260
|
-
|
|
4511
|
+
|
|
4512
|
+
// src/internal/capture/quiet-capture.ts
|
|
4513
|
+
var BOXED_PREFIX_EXPORTS = [
|
|
4514
|
+
[
|
|
4515
|
+
"playwright-core/lib/coreBundle",
|
|
4516
|
+
(mod) => mod?.utils?.setBoxedStackPrefixes
|
|
4517
|
+
],
|
|
4518
|
+
["playwright-core/lib/utils", (mod) => mod?.setBoxedStackPrefixes]
|
|
4519
|
+
];
|
|
4520
|
+
var boxing;
|
|
4521
|
+
var resolvedPlaywrightPackageJson;
|
|
4522
|
+
function resolvePlaywrightPackageJson() {
|
|
4523
|
+
if (resolvedPlaywrightPackageJson !== void 0) return resolvedPlaywrightPackageJson;
|
|
4524
|
+
try {
|
|
4525
|
+
const testPackageJson = (0, import_node_module.createRequire)(__filename).resolve("@playwright/test/package.json");
|
|
4526
|
+
resolvedPlaywrightPackageJson = (0, import_node_module.createRequire)(testPackageJson).resolve("playwright/package.json");
|
|
4527
|
+
} catch {
|
|
4528
|
+
resolvedPlaywrightPackageJson = null;
|
|
4529
|
+
}
|
|
4530
|
+
return resolvedPlaywrightPackageJson;
|
|
4261
4531
|
}
|
|
4262
|
-
function
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
const
|
|
4269
|
-
const
|
|
4270
|
-
|
|
4271
|
-
|
|
4532
|
+
function resolveBoxing() {
|
|
4533
|
+
try {
|
|
4534
|
+
const root = findOwnPackageJson(__dirname)?.root;
|
|
4535
|
+
if (!root) return null;
|
|
4536
|
+
const playwrightPackageJson = resolvePlaywrightPackageJson();
|
|
4537
|
+
if (!playwrightPackageJson) return null;
|
|
4538
|
+
const requireFromPlaywright = (0, import_node_module.createRequire)(playwrightPackageJson);
|
|
4539
|
+
for (const [moduleId, pick] of BOXED_PREFIX_EXPORTS) {
|
|
4540
|
+
let set;
|
|
4541
|
+
try {
|
|
4542
|
+
set = pick(requireFromPlaywright(moduleId));
|
|
4543
|
+
} catch {
|
|
4544
|
+
continue;
|
|
4545
|
+
}
|
|
4546
|
+
if (typeof set !== "function") continue;
|
|
4547
|
+
return {
|
|
4548
|
+
set,
|
|
4549
|
+
prefixes: [path8.dirname(playwrightPackageJson), path8.join(root, "dist") + path8.sep]
|
|
4550
|
+
};
|
|
4551
|
+
}
|
|
4552
|
+
} catch {
|
|
4272
4553
|
}
|
|
4273
|
-
return
|
|
4554
|
+
return null;
|
|
4274
4555
|
}
|
|
4275
|
-
function
|
|
4276
|
-
const
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4556
|
+
function boxCaptureFrames(extraPrefixes = []) {
|
|
4557
|
+
const first = boxing === void 0;
|
|
4558
|
+
if (first) boxing = resolveBoxing();
|
|
4559
|
+
if (!boxing) return;
|
|
4560
|
+
const added = extraPrefixes.filter((prefix) => !boxing.prefixes.includes(prefix));
|
|
4561
|
+
if (!first && added.length === 0) return;
|
|
4562
|
+
boxing.prefixes.push(...added);
|
|
4563
|
+
try {
|
|
4564
|
+
boxing.set([...boxing.prefixes]);
|
|
4565
|
+
} catch {
|
|
4280
4566
|
}
|
|
4281
|
-
|
|
4567
|
+
}
|
|
4568
|
+
function callMethod(target, prop, args) {
|
|
4569
|
+
return target[prop](...args);
|
|
4570
|
+
}
|
|
4571
|
+
function internalCall(owner, run) {
|
|
4572
|
+
const wrap = owner?._wrapApiCall;
|
|
4573
|
+
if (typeof wrap !== "function") return run();
|
|
4574
|
+
return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
|
|
4282
4575
|
}
|
|
4283
4576
|
|
|
4284
4577
|
// src/internal/capture/code-reach.ts
|
|
4578
|
+
var crypto2 = __toESM(require("crypto"));
|
|
4579
|
+
var fs6 = __toESM(require("fs"));
|
|
4580
|
+
var path9 = __toESM(require("path"));
|
|
4581
|
+
var import_node_child_process = require("child_process");
|
|
4285
4582
|
var MAX_MAP_BYTES = 20 * 1024 * 1024;
|
|
4286
4583
|
var MAP_TIMEOUT_MS = 5e3;
|
|
4287
4584
|
var SOURCE_EXTENSIONS = /\.(?:[cm]?[jt]sx?|vue|svelte|astro|marko|html?)$/i;
|
|
4288
4585
|
var REPLAYED_SCRIPT = /^(?:about|rsc):/i;
|
|
4586
|
+
var NEVER_REACHED = /\/node_modules\/|\/@vite\//;
|
|
4289
4587
|
var BUILD_DIRS = /(?:^|\/)(?:dist|build|out|\.output|\.next|\.nuxt|\.svelte-kit|public|static|wwwroot)\//;
|
|
4290
|
-
var MAX_CACHED_SCRIPTS =
|
|
4588
|
+
var MAX_CACHED_SCRIPTS = 4e3;
|
|
4291
4589
|
var LatestByUrl = class {
|
|
4292
4590
|
constructor() {
|
|
4293
4591
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -4322,13 +4620,37 @@ function repositoryRoot(from) {
|
|
|
4322
4620
|
}).trim();
|
|
4323
4621
|
} catch {
|
|
4324
4622
|
}
|
|
4325
|
-
cachedRepoRoot = { from, root:
|
|
4623
|
+
cachedRepoRoot = { from, root: path9.resolve(root) };
|
|
4326
4624
|
return cachedRepoRoot.root;
|
|
4327
4625
|
}
|
|
4328
4626
|
function codeReachRoots(configDir, configured) {
|
|
4329
4627
|
const repoRoot = repositoryRoot(configDir);
|
|
4330
|
-
const
|
|
4331
|
-
|
|
4628
|
+
const resolve3 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path9.resolve(configDir, r));
|
|
4629
|
+
const global = Array.isArray(configured) ? resolve3(configured) : resolve3(configured?.["*"]);
|
|
4630
|
+
const byOrigin = /* @__PURE__ */ new Map();
|
|
4631
|
+
if (configured && !Array.isArray(configured)) {
|
|
4632
|
+
for (const [key, dirs] of Object.entries(configured)) {
|
|
4633
|
+
const origin = key === "*" ? null : originOf2(key);
|
|
4634
|
+
const resolved = resolve3(dirs);
|
|
4635
|
+
if (origin && resolved.length) byOrigin.set(origin, resolved);
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
const roots = global.length ? global : [configDir];
|
|
4639
|
+
return { roots: [.../* @__PURE__ */ new Set([...roots, repoRoot])], byOrigin, repoRoot };
|
|
4640
|
+
}
|
|
4641
|
+
function originOf2(url) {
|
|
4642
|
+
if (!url) return null;
|
|
4643
|
+
try {
|
|
4644
|
+
const origin = new URL(url).origin;
|
|
4645
|
+
return origin === "null" ? null : origin;
|
|
4646
|
+
} catch {
|
|
4647
|
+
return null;
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
function scriptRoots(roots, scriptUrl, pageUrl) {
|
|
4651
|
+
if (!roots.byOrigin?.size) return roots.roots;
|
|
4652
|
+
const own = roots.byOrigin.get(originOf2(scriptUrl) ?? "") ?? roots.byOrigin.get(originOf2(pageUrl) ?? "");
|
|
4653
|
+
return own ? [.../* @__PURE__ */ new Set([...own, ...roots.roots])] : roots.roots;
|
|
4332
4654
|
}
|
|
4333
4655
|
function isFile(file) {
|
|
4334
4656
|
try {
|
|
@@ -4340,10 +4662,10 @@ function isFile(file) {
|
|
|
4340
4662
|
function resolveSourcePath(source, roots) {
|
|
4341
4663
|
const normalized = normalizeSourcePath(source);
|
|
4342
4664
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
4343
|
-
if (
|
|
4344
|
-
const
|
|
4665
|
+
if (path9.isAbsolute(normalized) && isFile(normalized)) return path9.resolve(normalized);
|
|
4666
|
+
const relative7 = normalized.replace(/^\/+/, "");
|
|
4345
4667
|
for (const root of roots) {
|
|
4346
|
-
const candidate =
|
|
4668
|
+
const candidate = path9.resolve(root, relative7);
|
|
4347
4669
|
if (isFile(candidate)) return candidate;
|
|
4348
4670
|
}
|
|
4349
4671
|
return null;
|
|
@@ -4375,7 +4697,7 @@ function fileForUrl(url, roots, bases) {
|
|
|
4375
4697
|
return file;
|
|
4376
4698
|
}
|
|
4377
4699
|
function relativeToRepo(file, repoRoot) {
|
|
4378
|
-
return
|
|
4700
|
+
return path9.relative(repoRoot, file).split(path9.sep).join("/");
|
|
4379
4701
|
}
|
|
4380
4702
|
function scriptVersion(entry) {
|
|
4381
4703
|
return entry.hash ?? crypto2.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
@@ -4412,50 +4734,96 @@ async function resolveScript(entry, roots, bases, fetchMap) {
|
|
|
4412
4734
|
const version = scriptVersion(entry);
|
|
4413
4735
|
const cached = scriptCache.get(entry.url, version);
|
|
4414
4736
|
if (cached) return cached;
|
|
4415
|
-
const file = fileForUrl(entry.url, roots
|
|
4737
|
+
const file = fileForUrl(entry.url, roots, bases);
|
|
4416
4738
|
const lookup = await loadMap(entry, fetchMap);
|
|
4417
4739
|
let resolution;
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4740
|
+
const text = entry.source === void 0 ? void 0 : scriptText(entry.source);
|
|
4741
|
+
if (file) resolution = { kind: "file", file, lookup, text };
|
|
4742
|
+
else resolution = lookup ? { kind: "map", lookup, files: [], text } : { kind: "none" };
|
|
4743
|
+
if (text) scriptCache.set(entry.url, version, resolution);
|
|
4421
4744
|
return resolution;
|
|
4422
4745
|
}
|
|
4423
|
-
function
|
|
4424
|
-
|
|
4425
|
-
|
|
4746
|
+
function textOf2(entry, resolution) {
|
|
4747
|
+
return resolution.text ?? (entry.source === void 0 ? void 0 : scriptText(entry.source));
|
|
4748
|
+
}
|
|
4749
|
+
async function resolveCodeReach(entries, roots, fetchMap, pageUrl) {
|
|
4750
|
+
return finalizeCodeReach((await resolveReach(entries, roots, fetchMap, pageUrl)).keys());
|
|
4751
|
+
}
|
|
4752
|
+
async function resolveCodeReachFunctions(entries, roots, fetchMap, pageUrl, options = {}) {
|
|
4753
|
+
return resolveFunctions(entries, roots, fetchMap, pageUrl, options.known === true);
|
|
4754
|
+
}
|
|
4755
|
+
async function resolveReach(entries, roots, fetchMap, pageUrl) {
|
|
4756
|
+
return (await resolveFunctions(entries, roots, fetchMap, pageUrl, false)).reached;
|
|
4426
4757
|
}
|
|
4427
|
-
|
|
4428
|
-
|
|
4758
|
+
var knownCache = new LatestByUrl();
|
|
4759
|
+
async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
4760
|
+
const files = /* @__PURE__ */ new Map();
|
|
4761
|
+
const known = /* @__PURE__ */ new Map();
|
|
4762
|
+
const addTo = (into, file, functions) => {
|
|
4763
|
+
const path13 = relativeToRepo(file, roots.repoRoot);
|
|
4764
|
+
const listed = into.get(path13);
|
|
4765
|
+
if (listed) listed.push(...functions);
|
|
4766
|
+
else into.set(path13, [...functions]);
|
|
4767
|
+
};
|
|
4768
|
+
const add = (file, functions) => addTo(files, file, functions);
|
|
4429
4769
|
const bases = viteBases(entries);
|
|
4430
4770
|
const scripts = entries.filter((entry) => entry.url && entry.functions?.length && !REPLAYED_SCRIPT.test(entry.url));
|
|
4431
|
-
const
|
|
4771
|
+
const cached = scripts.map((entry) => scriptCache.get(entry.url, scriptVersion(entry)));
|
|
4432
4772
|
for (const [i, covered] of scripts.entries()) {
|
|
4433
|
-
const
|
|
4773
|
+
const dirs = scriptRoots(roots, covered.url, covered.pageUrl ?? pageUrl);
|
|
4774
|
+
const resolution = cached[i] ?? await resolveScript(covered, dirs, bases, fetchMap);
|
|
4775
|
+
const mapFile = (resolved, index) => {
|
|
4776
|
+
if (resolved.files[index] === void 0) {
|
|
4777
|
+
const source = resolved.lookup.sources[index] ?? "";
|
|
4778
|
+
let absolute = resolveSourcePath(source, dirs);
|
|
4779
|
+
if (!absolute && !/^[a-z][\w+.-]*:/i.test(source)) {
|
|
4780
|
+
try {
|
|
4781
|
+
absolute = fileForUrl(new URL(source, covered.url).href, dirs, bases);
|
|
4782
|
+
} catch {
|
|
4783
|
+
absolute = null;
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
resolved.files[index] = absolute;
|
|
4787
|
+
}
|
|
4788
|
+
return resolved.files[index] ?? null;
|
|
4789
|
+
};
|
|
4434
4790
|
if (resolution.kind === "file") {
|
|
4435
|
-
|
|
4436
|
-
const
|
|
4437
|
-
if (resolution.
|
|
4791
|
+
if (!resolution.file) continue;
|
|
4792
|
+
const text = textOf2(covered, resolution);
|
|
4793
|
+
if (resolution.lookup) {
|
|
4794
|
+
const reached = reachedFunctionLines(covered, resolution.lookup, text);
|
|
4795
|
+
if (reached.size > 0) add(resolution.file, [...reached.values()].flat());
|
|
4796
|
+
} else if (scriptRan(covered, text)) {
|
|
4797
|
+
add(resolution.file, reachedFunctionLines(covered, null, text).get(-1) ?? []);
|
|
4798
|
+
}
|
|
4438
4799
|
} else if (resolution.kind === "map") {
|
|
4439
|
-
const
|
|
4440
|
-
|
|
4441
|
-
if (
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4800
|
+
for (const [index, functions] of reachedFunctionLines(covered, resolution.lookup, textOf2(covered, resolution))) {
|
|
4801
|
+
const file = mapFile(resolution, index);
|
|
4802
|
+
if (file) add(file, functions);
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
if (!withKnown || resolution.kind === "none") continue;
|
|
4806
|
+
const version = scriptVersion(covered);
|
|
4807
|
+
let listed = knownCache.get(covered.url, version);
|
|
4808
|
+
if (!listed || listed.count < covered.functions.length) {
|
|
4809
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
4810
|
+
const text = textOf2(covered, resolution);
|
|
4811
|
+
if (resolution.kind === "file" && resolution.file) {
|
|
4812
|
+
const lines = knownFunctionLines(covered, resolution.lookup, text);
|
|
4813
|
+
const all = resolution.lookup ? [...lines.values()].flat() : lines.get(-1) ?? [];
|
|
4814
|
+
if (all.length > 0) byFile.set(resolution.file, all);
|
|
4815
|
+
} else if (resolution.kind === "map") {
|
|
4816
|
+
for (const [index, functions] of knownFunctionLines(covered, resolution.lookup, text)) {
|
|
4817
|
+
const file = mapFile(resolution, index);
|
|
4818
|
+
if (file) byFile.set(file, [...byFile.get(file) ?? [], ...functions]);
|
|
4452
4819
|
}
|
|
4453
|
-
const file = resolution.files[index];
|
|
4454
|
-
if (file) files.add(relativeToRepo(file, roots.repoRoot));
|
|
4455
4820
|
}
|
|
4821
|
+
listed = { count: covered.functions.length, files: byFile };
|
|
4822
|
+
knownCache.set(covered.url, version, listed);
|
|
4456
4823
|
}
|
|
4824
|
+
for (const [file, functions] of listed.files) addTo(known, file, functions);
|
|
4457
4825
|
}
|
|
4458
|
-
return
|
|
4826
|
+
return { reached: files, known };
|
|
4459
4827
|
}
|
|
4460
4828
|
function pageMapFetcher(page) {
|
|
4461
4829
|
return async (url) => {
|
|
@@ -4479,12 +4847,57 @@ function isChromiumPage(page) {
|
|
|
4479
4847
|
}
|
|
4480
4848
|
}
|
|
4481
4849
|
var captures = /* @__PURE__ */ new WeakMap();
|
|
4850
|
+
var MARK_WORLD = "piwi-code-reach";
|
|
4851
|
+
var MARK_FUNCTION = "piwiCodeReachMark";
|
|
4852
|
+
var MARK_SOURCE = `addEventListener('beforeunload', function ${MARK_FUNCTION}() {});`;
|
|
4853
|
+
var markedDocuments = /* @__PURE__ */ new WeakMap();
|
|
4854
|
+
function marksOf(page) {
|
|
4855
|
+
let marks = markedDocuments.get(page);
|
|
4856
|
+
if (!marks) {
|
|
4857
|
+
marks = /* @__PURE__ */ new Set();
|
|
4858
|
+
markedDocuments.set(page, marks);
|
|
4859
|
+
}
|
|
4860
|
+
return marks;
|
|
4861
|
+
}
|
|
4862
|
+
async function markCurrentDocument(page, cdp) {
|
|
4863
|
+
try {
|
|
4864
|
+
const { frameTree } = await internalCall(page, () => cdp.send("Page.getFrameTree"));
|
|
4865
|
+
const marks = marksOf(page);
|
|
4866
|
+
if (marks.has(frameTree.frame.loaderId)) return;
|
|
4867
|
+
const world = await internalCall(
|
|
4868
|
+
page,
|
|
4869
|
+
() => cdp.send("Page.createIsolatedWorld", { frameId: frameTree.frame.id, worldName: MARK_WORLD })
|
|
4870
|
+
);
|
|
4871
|
+
await internalCall(
|
|
4872
|
+
page,
|
|
4873
|
+
() => cdp.send("Runtime.evaluate", { expression: MARK_SOURCE, contextId: world.executionContextId })
|
|
4874
|
+
);
|
|
4875
|
+
marks.add(frameTree.frame.loaderId);
|
|
4876
|
+
} catch {
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
function onPaused(page, capture, event) {
|
|
4880
|
+
const leaving = event.reason === "EventListener" && event.data?.eventName === "listener:beforeunload" && (event.callFrames?.[0]?.functionName ?? MARK_FUNCTION) === MARK_FUNCTION;
|
|
4881
|
+
const read = leaving ? readCoverage(page, capture.cdp) : null;
|
|
4882
|
+
if (read) {
|
|
4883
|
+
const merging = read.then(
|
|
4884
|
+
(coverage) => mergeCoverageRead(capture.ran, capture.scripts, coverage, capture.keepIdle)
|
|
4885
|
+
);
|
|
4886
|
+
capture.reading.add(merging);
|
|
4887
|
+
void merging.finally(() => capture.reading.delete(merging));
|
|
4888
|
+
}
|
|
4889
|
+
void (read ?? Promise.resolve()).finally(() => {
|
|
4890
|
+
internalCall(page, () => capture.cdp.send("Debugger.resume")).catch(() => {
|
|
4891
|
+
});
|
|
4892
|
+
});
|
|
4893
|
+
}
|
|
4482
4894
|
function versionKey(url, hash) {
|
|
4483
4895
|
return `${hash} ${url}`;
|
|
4484
4896
|
}
|
|
4485
4897
|
function onScriptParsed(page, capture, event) {
|
|
4486
4898
|
if (!event.url || REPLAYED_SCRIPT.test(event.url)) return;
|
|
4487
|
-
capture.scripts.set(event.scriptId, { url: event.url, hash: event.hash });
|
|
4899
|
+
capture.scripts.set(event.scriptId, { url: event.url, hash: event.hash, pageUrl: capture.pageUrl });
|
|
4900
|
+
if (NEVER_REACHED.test(event.url)) return;
|
|
4488
4901
|
const key = versionKey(event.url, event.hash);
|
|
4489
4902
|
if (capture.sources.has(key) || scriptCache.get(event.url, event.hash)) return;
|
|
4490
4903
|
const source = internalCall(
|
|
@@ -4499,6 +4912,44 @@ function onScriptParsed(page, capture, event) {
|
|
|
4499
4912
|
);
|
|
4500
4913
|
capture.sources.set(key, source);
|
|
4501
4914
|
}
|
|
4915
|
+
var READ_WAIT_MS = 5e3;
|
|
4916
|
+
function readCoverage(page, cdp) {
|
|
4917
|
+
let timer;
|
|
4918
|
+
const late = new Promise((resolve3) => {
|
|
4919
|
+
timer = setTimeout(() => resolve3([]), READ_WAIT_MS);
|
|
4920
|
+
});
|
|
4921
|
+
const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
|
|
4922
|
+
(answer) => answer.result,
|
|
4923
|
+
() => []
|
|
4924
|
+
);
|
|
4925
|
+
return Promise.race([taken, late]).finally(() => clearTimeout(timer));
|
|
4926
|
+
}
|
|
4927
|
+
function mergeCoverageRead(ran, scripts, read, keepIdle = true) {
|
|
4928
|
+
for (const covered of read) {
|
|
4929
|
+
const script = scripts.get(covered.scriptId);
|
|
4930
|
+
if (!script) continue;
|
|
4931
|
+
const key = versionKey(script.url, script.hash);
|
|
4932
|
+
let known = ran.get(key);
|
|
4933
|
+
if (!known) {
|
|
4934
|
+
known = { script, functions: [], byRange: /* @__PURE__ */ new Map() };
|
|
4935
|
+
ran.set(key, known);
|
|
4936
|
+
}
|
|
4937
|
+
for (const fn of covered.functions) {
|
|
4938
|
+
const whole = fn.ranges[0];
|
|
4939
|
+
if (!whole) continue;
|
|
4940
|
+
if (!keepIdle && whole.count <= 0) continue;
|
|
4941
|
+
const range = `${whole.startOffset}:${whole.endOffset}`;
|
|
4942
|
+
const seen = known.byRange.get(range);
|
|
4943
|
+
if (seen) {
|
|
4944
|
+
if (whole.count > 0 && seen.ranges[0].count <= 0) seen.ranges[0].count = whole.count;
|
|
4945
|
+
continue;
|
|
4946
|
+
}
|
|
4947
|
+
const copy = { ...fn, ranges: fn.ranges.map((r) => ({ ...r })) };
|
|
4948
|
+
known.byRange.set(range, copy);
|
|
4949
|
+
known.functions.push(copy);
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4502
4953
|
async function detach(page, cdp) {
|
|
4503
4954
|
try {
|
|
4504
4955
|
await internalCall(page, () => cdp.detach());
|
|
@@ -4510,21 +4961,35 @@ async function startCodeReach(page) {
|
|
|
4510
4961
|
let cdp = null;
|
|
4511
4962
|
try {
|
|
4512
4963
|
cdp = await internalCall(page, () => page.context().newCDPSession(page));
|
|
4513
|
-
const capture = {
|
|
4964
|
+
const capture = {
|
|
4965
|
+
cdp,
|
|
4966
|
+
scripts: /* @__PURE__ */ new Map(),
|
|
4967
|
+
sources: /* @__PURE__ */ new Map(),
|
|
4968
|
+
ran: /* @__PURE__ */ new Map(),
|
|
4969
|
+
keepIdle: browserFunctionsInWorker(),
|
|
4970
|
+
reading: /* @__PURE__ */ new Set(),
|
|
4971
|
+
pageUrl: page.url()
|
|
4972
|
+
};
|
|
4973
|
+
const marks = marksOf(page);
|
|
4514
4974
|
cdp.on("Debugger.scriptParsed", (event) => onScriptParsed(page, capture, event));
|
|
4515
|
-
cdp.on("Debugger.paused", () =>
|
|
4516
|
-
|
|
4517
|
-
|
|
4975
|
+
cdp.on("Debugger.paused", (event) => onPaused(page, capture, event));
|
|
4976
|
+
cdp.on("Page.frameNavigated", (event) => {
|
|
4977
|
+
if (event.frame.parentId) return;
|
|
4978
|
+
marks.add(event.frame.loaderId);
|
|
4979
|
+
capture.pageUrl = event.frame.url;
|
|
4518
4980
|
});
|
|
4519
4981
|
await internalCall(
|
|
4520
4982
|
page,
|
|
4521
4983
|
() => Promise.all([
|
|
4522
4984
|
capture.cdp.send("Debugger.enable"),
|
|
4523
|
-
capture.cdp.send("Debugger.setSkipAllPauses", { skip: true }),
|
|
4524
4985
|
capture.cdp.send("Profiler.enable"),
|
|
4525
|
-
capture.cdp.send("Profiler.startPreciseCoverage", { callCount: false, detailed: false })
|
|
4986
|
+
capture.cdp.send("Profiler.startPreciseCoverage", { callCount: false, detailed: false }),
|
|
4987
|
+
capture.cdp.send("Page.enable"),
|
|
4988
|
+
capture.cdp.send("Page.addScriptToEvaluateOnNewDocument", { source: MARK_SOURCE, worldName: MARK_WORLD }),
|
|
4989
|
+
capture.cdp.send("DOMDebugger.setEventListenerBreakpoint", { eventName: "beforeunload" })
|
|
4526
4990
|
])
|
|
4527
4991
|
);
|
|
4992
|
+
await markCurrentDocument(page, capture.cdp);
|
|
4528
4993
|
captures.set(page, capture);
|
|
4529
4994
|
return true;
|
|
4530
4995
|
} catch {
|
|
@@ -4538,17 +5003,17 @@ async function stopCodeReach(page) {
|
|
|
4538
5003
|
captures.delete(page);
|
|
4539
5004
|
const { cdp } = capture;
|
|
4540
5005
|
try {
|
|
4541
|
-
const
|
|
5006
|
+
const last = await internalCall(page, async () => {
|
|
4542
5007
|
const taken = await cdp.send("Profiler.takePreciseCoverage");
|
|
4543
5008
|
await cdp.send("Profiler.stopPreciseCoverage");
|
|
4544
|
-
return taken;
|
|
5009
|
+
return taken.result;
|
|
4545
5010
|
});
|
|
5011
|
+
await Promise.all(capture.reading);
|
|
5012
|
+
mergeCoverageRead(capture.ran, capture.scripts, last, capture.keepIdle);
|
|
4546
5013
|
const entries = [];
|
|
4547
|
-
for (const script of
|
|
4548
|
-
const
|
|
4549
|
-
|
|
4550
|
-
const entry = { url: parsed2.url, hash: parsed2.hash, functions: script.functions };
|
|
4551
|
-
const source = await capture.sources.get(versionKey(parsed2.url, parsed2.hash));
|
|
5014
|
+
for (const [key, { script, functions }] of capture.ran) {
|
|
5015
|
+
const entry = { url: script.url, hash: script.hash, functions, pageUrl: script.pageUrl };
|
|
5016
|
+
const source = await capture.sources.get(key);
|
|
4552
5017
|
if (source != null) entry.source = source;
|
|
4553
5018
|
entries.push(entry);
|
|
4554
5019
|
}
|
|
@@ -4562,7 +5027,7 @@ async function stopCodeReach(page) {
|
|
|
4562
5027
|
|
|
4563
5028
|
// src/internal/capture/resource-ledger.ts
|
|
4564
5029
|
var fs8 = __toESM(require("fs"));
|
|
4565
|
-
var
|
|
5030
|
+
var path11 = __toESM(require("path"));
|
|
4566
5031
|
var import_node_module2 = require("module");
|
|
4567
5032
|
|
|
4568
5033
|
// src/internal/capture/worker-metrics.ts
|
|
@@ -4570,7 +5035,7 @@ var import_node_perf_hooks = require("perf_hooks");
|
|
|
4570
5035
|
|
|
4571
5036
|
// src/internal/support/system-readers.ts
|
|
4572
5037
|
var fs7 = __toESM(require("fs"));
|
|
4573
|
-
var
|
|
5038
|
+
var path10 = __toESM(require("path"));
|
|
4574
5039
|
var CLK_TCK = 100;
|
|
4575
5040
|
function readText(file) {
|
|
4576
5041
|
try {
|
|
@@ -4656,7 +5121,7 @@ var ProcFs = class {
|
|
|
4656
5121
|
}
|
|
4657
5122
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
4658
5123
|
available() {
|
|
4659
|
-
return fs7.existsSync(
|
|
5124
|
+
return fs7.existsSync(path10.join(this.root, "stat"));
|
|
4660
5125
|
}
|
|
4661
5126
|
/** Every process, keyed by pid. */
|
|
4662
5127
|
table() {
|
|
@@ -4669,32 +5134,32 @@ var ProcFs = class {
|
|
|
4669
5134
|
}
|
|
4670
5135
|
for (const name of names) {
|
|
4671
5136
|
if (!/^\d+$/.test(name)) continue;
|
|
4672
|
-
const text = readText(
|
|
5137
|
+
const text = readText(path10.join(this.root, name, "stat"));
|
|
4673
5138
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
4674
5139
|
if (stat) out.set(stat.pid, stat);
|
|
4675
5140
|
}
|
|
4676
5141
|
return out;
|
|
4677
5142
|
}
|
|
4678
5143
|
stat(pid) {
|
|
4679
|
-
const text = readText(
|
|
5144
|
+
const text = readText(path10.join(this.root, String(pid), "stat"));
|
|
4680
5145
|
return text ? parseProcStat(pid, text) : null;
|
|
4681
5146
|
}
|
|
4682
5147
|
runWaitMs(pid) {
|
|
4683
|
-
const text = readText(
|
|
5148
|
+
const text = readText(path10.join(this.root, String(pid), "schedstat"));
|
|
4684
5149
|
return text ? parseSchedstat(text) : null;
|
|
4685
5150
|
}
|
|
4686
5151
|
cmdline(pid) {
|
|
4687
|
-
return (readText(
|
|
5152
|
+
return (readText(path10.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
4688
5153
|
}
|
|
4689
5154
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
4690
5155
|
statusKb(pid, key) {
|
|
4691
|
-
const text = readText(
|
|
5156
|
+
const text = readText(path10.join(this.root, String(pid), "status"));
|
|
4692
5157
|
return text ? parseKbField(text, key) : null;
|
|
4693
5158
|
}
|
|
4694
5159
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
4695
5160
|
async pssKb(pid) {
|
|
4696
5161
|
try {
|
|
4697
|
-
return parseKbField(await fs7.promises.readFile(
|
|
5162
|
+
return parseKbField(await fs7.promises.readFile(path10.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
4698
5163
|
} catch {
|
|
4699
5164
|
return null;
|
|
4700
5165
|
}
|
|
@@ -4702,29 +5167,29 @@ var ProcFs = class {
|
|
|
4702
5167
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
4703
5168
|
resetPeakRss(pid) {
|
|
4704
5169
|
try {
|
|
4705
|
-
fs7.writeFileSync(
|
|
5170
|
+
fs7.writeFileSync(path10.join(this.root, String(pid), "clear_refs"), "5");
|
|
4706
5171
|
return true;
|
|
4707
5172
|
} catch {
|
|
4708
5173
|
return false;
|
|
4709
5174
|
}
|
|
4710
5175
|
}
|
|
4711
5176
|
cpuTicks() {
|
|
4712
|
-
const text = readText(
|
|
5177
|
+
const text = readText(path10.join(this.root, "stat"));
|
|
4713
5178
|
return text ? parseCpuTicks(text) : null;
|
|
4714
5179
|
}
|
|
4715
5180
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
4716
5181
|
pressureUs(resource) {
|
|
4717
|
-
const text = readText(
|
|
5182
|
+
const text = readText(path10.join(this.root, "pressure", resource));
|
|
4718
5183
|
return text ? parsePsiTotal(text) : null;
|
|
4719
5184
|
}
|
|
4720
5185
|
memAvailableKb() {
|
|
4721
|
-
const text = readText(
|
|
5186
|
+
const text = readText(path10.join(this.root, "meminfo"));
|
|
4722
5187
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
4723
5188
|
}
|
|
4724
5189
|
/** Open file descriptors of a process. */
|
|
4725
5190
|
fdCount(pid) {
|
|
4726
5191
|
try {
|
|
4727
|
-
return fs7.readdirSync(
|
|
5192
|
+
return fs7.readdirSync(path10.join(this.root, String(pid), "fd")).length;
|
|
4728
5193
|
} catch {
|
|
4729
5194
|
return null;
|
|
4730
5195
|
}
|
|
@@ -4886,10 +5351,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
|
4886
5351
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
4887
5352
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
4888
5353
|
function relativePath(file) {
|
|
4889
|
-
return
|
|
5354
|
+
return path11.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path11.sep).join("/");
|
|
4890
5355
|
}
|
|
4891
5356
|
function isLibraryFile(file) {
|
|
4892
|
-
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${
|
|
5357
|
+
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path11.sep}node_modules${path11.sep}`) || file.includes("/node_modules/") || file === __filename;
|
|
4893
5358
|
}
|
|
4894
5359
|
function userSite(stack) {
|
|
4895
5360
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
@@ -4937,7 +5402,7 @@ function playwrightTestInfoSource() {
|
|
|
4937
5402
|
try {
|
|
4938
5403
|
const packageJson = resolvePlaywrightPackageJson();
|
|
4939
5404
|
if (!packageJson) return () => null;
|
|
4940
|
-
const globals = (0, import_node_module2.createRequire)(packageJson)(
|
|
5405
|
+
const globals = (0, import_node_module2.createRequire)(packageJson)(path11.join(path11.dirname(packageJson), "lib", "globals.js"));
|
|
4941
5406
|
const read = globals.currentTestInfo;
|
|
4942
5407
|
if (typeof read !== "function") return () => null;
|
|
4943
5408
|
return () => {
|
|
@@ -5721,52 +6186,87 @@ function createSink() {
|
|
|
5721
6186
|
userPick: null,
|
|
5722
6187
|
pause: null,
|
|
5723
6188
|
locatorPages: new LocatorPageLog(),
|
|
5724
|
-
codeReach:
|
|
6189
|
+
codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map() },
|
|
5725
6190
|
serverCoverage: /* @__PURE__ */ new Map(),
|
|
5726
6191
|
baseURL: null
|
|
5727
6192
|
};
|
|
5728
6193
|
}
|
|
5729
|
-
|
|
6194
|
+
var MAX_CODE_REACH_PAGES = 16;
|
|
6195
|
+
var CODE_REACH_START_WAIT_MS = 2e3;
|
|
6196
|
+
function maybeStartCodeReach(page, alreadyOpen = false) {
|
|
5730
6197
|
const sink = currentSink;
|
|
5731
|
-
if (!sink || !browserCoverageInWorker()) return Promise.resolve(false);
|
|
5732
|
-
|
|
6198
|
+
if (!sink || !browserCoverageInWorker() || isPageClosed(page)) return Promise.resolve(false);
|
|
6199
|
+
const known = sink.codeReach.sessions.get(page);
|
|
6200
|
+
if (known) return known.started;
|
|
6201
|
+
if (alreadyOpen) {
|
|
6202
|
+
let recording = 0;
|
|
6203
|
+
for (const [other, session] of sink.codeReach.sessions) if (!session.stopped && !isPageClosed(other)) recording++;
|
|
6204
|
+
if (recording >= MAX_CODE_REACH_PAGES) return Promise.resolve(false);
|
|
6205
|
+
}
|
|
5733
6206
|
const started = startCodeReach(page);
|
|
5734
|
-
sink.codeReach
|
|
6207
|
+
sink.codeReach.sessions.set(page, { started, stopped: false });
|
|
5735
6208
|
return started;
|
|
5736
6209
|
}
|
|
6210
|
+
async function startCodeReachOnOpenPages() {
|
|
6211
|
+
if (!browserCoverageInWorker() || OPEN_PAGES.size === 0) return;
|
|
6212
|
+
const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
|
|
6213
|
+
await Promise.race([started, new Promise((resolve3) => setTimeout(resolve3, CODE_REACH_START_WAIT_MS).unref())]);
|
|
6214
|
+
}
|
|
5737
6215
|
function configuredCodeReachRoots() {
|
|
5738
6216
|
const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
|
|
5739
6217
|
if (!raw) return null;
|
|
6218
|
+
const dirs = (value) => (Array.isArray(value) ? value : [value]).filter((r) => typeof r === "string" && r !== "");
|
|
5740
6219
|
try {
|
|
5741
6220
|
const parsed2 = JSON.parse(raw);
|
|
5742
|
-
if (Array.isArray(parsed2)) return parsed2
|
|
6221
|
+
if (Array.isArray(parsed2)) return dirs(parsed2);
|
|
6222
|
+
if (parsed2 && typeof parsed2 === "object") {
|
|
6223
|
+
return Object.fromEntries(Object.entries(parsed2).map(([origin, value]) => [origin, dirs(value)]));
|
|
6224
|
+
}
|
|
5743
6225
|
} catch {
|
|
5744
6226
|
}
|
|
5745
|
-
return raw.split(
|
|
6227
|
+
return raw.split(path12.delimiter).filter(Boolean);
|
|
5746
6228
|
}
|
|
5747
6229
|
async function stopSinkCodeReach(sink, closing) {
|
|
5748
|
-
const
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
6230
|
+
const stopping = [];
|
|
6231
|
+
const underway = [];
|
|
6232
|
+
for (const [page, session] of sink.codeReach.sessions) {
|
|
6233
|
+
if (session.stopped) {
|
|
6234
|
+
if (!closing && session.stopping) underway.push(session.stopping);
|
|
6235
|
+
continue;
|
|
6236
|
+
}
|
|
6237
|
+
if (closing && closing.page !== page && (!closing.context || pageContext(page) !== closing.context)) continue;
|
|
6238
|
+
session.stopped = true;
|
|
6239
|
+
stopping.push([page, session]);
|
|
6240
|
+
}
|
|
6241
|
+
if (stopping.length > 0) {
|
|
6242
|
+
const info = sink.testInfo;
|
|
6243
|
+
const roots = codeReachRoots(configDirFromConfig(info?.config), configuredCodeReachRoots());
|
|
6244
|
+
for (const [page, session] of stopping) {
|
|
6245
|
+
session.stopping = stopPageCodeReach(sink, page, session, roots);
|
|
6246
|
+
underway.push(session.stopping);
|
|
6247
|
+
}
|
|
5752
6248
|
}
|
|
5753
|
-
|
|
6249
|
+
await Promise.all(underway);
|
|
6250
|
+
}
|
|
6251
|
+
async function stopPageCodeReach(sink, page, session, roots) {
|
|
5754
6252
|
try {
|
|
5755
|
-
if (!await session.started || isPageClosed(
|
|
5756
|
-
const entries = await stopCodeReach(
|
|
6253
|
+
if (!await session.started || isPageClosed(page)) return;
|
|
6254
|
+
const entries = await stopCodeReach(page);
|
|
5757
6255
|
if (!entries) return;
|
|
5758
|
-
const
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
6256
|
+
const { reached, known } = await resolveCodeReachFunctions(entries, roots, pageMapFetcher(page), page.url(), {
|
|
6257
|
+
known: browserFunctionsInWorker()
|
|
6258
|
+
});
|
|
6259
|
+
for (const [file, functions] of reached) {
|
|
6260
|
+
sink.codeReach.files.set(file, [...sink.codeReach.files.get(file) ?? [], ...functions]);
|
|
6261
|
+
}
|
|
6262
|
+
for (const [file, functions] of known) {
|
|
6263
|
+
sink.codeReach.known.set(file, [...sink.codeReach.known.get(file) ?? [], ...functions]);
|
|
6264
|
+
}
|
|
5766
6265
|
} catch {
|
|
5767
6266
|
}
|
|
5768
6267
|
}
|
|
5769
6268
|
var currentSink = null;
|
|
6269
|
+
var KNOWN_FUNCTIONS_SENT = /* @__PURE__ */ new Map();
|
|
5770
6270
|
var PENDING_PROBES = /* @__PURE__ */ new Set();
|
|
5771
6271
|
async function drainPendingProbes(capMs) {
|
|
5772
6272
|
if (PENDING_PROBES.size === 0) return;
|
|
@@ -5882,8 +6382,8 @@ function buildPageState(raw, cookies, interaction) {
|
|
|
5882
6382
|
length: typeof e.length === "number" ? e.length : 0
|
|
5883
6383
|
}));
|
|
5884
6384
|
return {
|
|
5885
|
-
url: raw.url,
|
|
5886
|
-
hash: raw.hash
|
|
6385
|
+
url: raw.url.split("#")[0],
|
|
6386
|
+
hash: raw.hash ? maskUrlFragment(raw.hash) : null,
|
|
5887
6387
|
historyState: historyState || null,
|
|
5888
6388
|
localStorage: capStorage(raw.localStorage),
|
|
5889
6389
|
sessionStorage: capStorage(raw.sessionStorage),
|
|
@@ -6063,6 +6563,7 @@ async function maybeOpenPicker(sink, closing) {
|
|
|
6063
6563
|
applyPickToSnapshots(sink.capturedLocators, pick);
|
|
6064
6564
|
}
|
|
6065
6565
|
var INSTRUMENTED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
6566
|
+
var OPEN_PAGES = /* @__PURE__ */ new Set();
|
|
6066
6567
|
var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
|
|
6067
6568
|
var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
|
|
6068
6569
|
var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
|
|
@@ -6343,6 +6844,10 @@ var CANCELLED_REQUEST = /^net::ERR_ABORTED$|^NS_BINDING_ABORTED$|^NS_ERROR_ABORT
|
|
|
6343
6844
|
function instrumentPage(page) {
|
|
6344
6845
|
if (!page || INSTRUMENTED_PAGES.has(page)) return;
|
|
6345
6846
|
INSTRUMENTED_PAGES.add(page);
|
|
6847
|
+
if (typeof page.once === "function") {
|
|
6848
|
+
OPEN_PAGES.add(page);
|
|
6849
|
+
page.once("close", () => OPEN_PAGES.delete(page));
|
|
6850
|
+
}
|
|
6346
6851
|
void maybeStartCodeReach(page);
|
|
6347
6852
|
observeOwnApiRequests(page);
|
|
6348
6853
|
const probeSink = currentSink;
|
|
@@ -6679,10 +7184,24 @@ async function flushSink(sink, testInfo) {
|
|
|
6679
7184
|
});
|
|
6680
7185
|
}
|
|
6681
7186
|
await stopSinkCodeReach(sink);
|
|
6682
|
-
if (sink.codeReach
|
|
7187
|
+
if (sink.codeReach.files.size > 0) {
|
|
6683
7188
|
await testInfo.attach(ATTACHMENT_NAMES.codeReach, {
|
|
6684
7189
|
contentType: "application/json",
|
|
6685
|
-
body: Buffer.from(JSON.stringify(sink.codeReach.files))
|
|
7190
|
+
body: Buffer.from(JSON.stringify(finalizeCodeReach(sink.codeReach.files.keys())))
|
|
7191
|
+
});
|
|
7192
|
+
const functions = browserFunctionsInWorker() ? browserFunctionCoverage(sink.codeReach.files) : null;
|
|
7193
|
+
if (functions) {
|
|
7194
|
+
await testInfo.attach(ATTACHMENT_NAMES.browserCoverage, {
|
|
7195
|
+
contentType: "application/json",
|
|
7196
|
+
body: Buffer.from(JSON.stringify(functions))
|
|
7197
|
+
});
|
|
7198
|
+
}
|
|
7199
|
+
}
|
|
7200
|
+
const known = browserFunctionsInWorker() ? newKnownFunctions(sink.codeReach.known, KNOWN_FUNCTIONS_SENT) : [];
|
|
7201
|
+
for (const part of known) {
|
|
7202
|
+
await testInfo.attach(ATTACHMENT_NAMES.browserFunctions, {
|
|
7203
|
+
contentType: "application/json",
|
|
7204
|
+
body: Buffer.from(JSON.stringify(part))
|
|
6686
7205
|
});
|
|
6687
7206
|
}
|
|
6688
7207
|
if (sink.serverCoverage.size > 0) {
|
|
@@ -6881,6 +7400,7 @@ var piwiFixtures = {
|
|
|
6881
7400
|
const plan = loadFlakePlan();
|
|
6882
7401
|
const role = flakeRoleForTest(plan, {
|
|
6883
7402
|
file: testInfo.file,
|
|
7403
|
+
base: configDirFromConfig(testInfo.config),
|
|
6884
7404
|
title: testInfo.title,
|
|
6885
7405
|
titlePath: testInfo.titlePath,
|
|
6886
7406
|
project: testInfo.project.name
|
|
@@ -6894,11 +7414,13 @@ var piwiFixtures = {
|
|
|
6894
7414
|
sink.probeItem = probeItemForTest({
|
|
6895
7415
|
title: testInfo.title,
|
|
6896
7416
|
file: testInfo.file,
|
|
6897
|
-
titlePath: testInfo.titlePath
|
|
7417
|
+
titlePath: testInfo.titlePath,
|
|
7418
|
+
base: configDirFromConfig(testInfo.config)
|
|
6898
7419
|
});
|
|
6899
7420
|
activeResourceLedger()?.testStarted(testInfo);
|
|
6900
7421
|
currentSink = sink;
|
|
6901
7422
|
try {
|
|
7423
|
+
await startCodeReachOnOpenPages();
|
|
6902
7424
|
await use();
|
|
6903
7425
|
} finally {
|
|
6904
7426
|
currentSink = null;
|
|
@@ -6937,6 +7459,7 @@ async function recordFlakeAttempt(flake, testInfo) {
|
|
|
6937
7459
|
flakeResultLine(flake.plan, {
|
|
6938
7460
|
role: flake.role,
|
|
6939
7461
|
file: testInfo.file,
|
|
7462
|
+
base: configDirFromConfig(testInfo.config),
|
|
6940
7463
|
title: testInfo.title,
|
|
6941
7464
|
project: testInfo.project.name || null,
|
|
6942
7465
|
browserName: flake.browserName,
|