@piwitests/reporter 0.55.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 +1339 -1369
- package/dist/global-setup-module.js +32 -22
- package/dist/index.d.ts +9 -0
- package/dist/index.js +460 -331
- package/dist/internal/capture/capture-fixtures.d.ts +2 -1
- package/dist/internal/capture/capture-fixtures.js +1035 -980
- 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);
|
|
@@ -2281,7 +2581,6 @@ var LocatorPageLog = class {
|
|
|
2281
2581
|
|
|
2282
2582
|
// src/internal/probe/mode.ts
|
|
2283
2583
|
var fs2 = __toESM(require("fs"));
|
|
2284
|
-
var path3 = __toESM(require("path"));
|
|
2285
2584
|
|
|
2286
2585
|
// src/internal/config/desktop.ts
|
|
2287
2586
|
var fs = __toESM(require("fs"));
|
|
@@ -2418,6 +2717,17 @@ var ENV_FALLBACK_SPECS = [
|
|
|
2418
2717
|
{ option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
|
|
2419
2718
|
];
|
|
2420
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
|
+
|
|
2421
2731
|
// src/internal/probe/plan.ts
|
|
2422
2732
|
function parseRouteKey(routeKey2) {
|
|
2423
2733
|
const space = routeKey2.indexOf(" ");
|
|
@@ -2425,8 +2735,8 @@ function parseRouteKey(routeKey2) {
|
|
|
2425
2735
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
2426
2736
|
}
|
|
2427
2737
|
function patternToRegex(pattern) {
|
|
2428
|
-
const
|
|
2429
|
-
const escaped =
|
|
2738
|
+
const path13 = pattern.split("?")[0] ?? pattern;
|
|
2739
|
+
const escaped = path13.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
2430
2740
|
return new RegExp(`^${escaped}$`);
|
|
2431
2741
|
}
|
|
2432
2742
|
function suitePathEqual(a, b) {
|
|
@@ -2449,13 +2759,13 @@ function matchProbeItem(plan, test) {
|
|
|
2449
2759
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
2450
2760
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
2451
2761
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
2452
|
-
let
|
|
2762
|
+
let path13;
|
|
2453
2763
|
try {
|
|
2454
|
-
|
|
2764
|
+
path13 = new URL(url).pathname;
|
|
2455
2765
|
} catch {
|
|
2456
|
-
|
|
2766
|
+
path13 = url.split("?")[0] ?? url;
|
|
2457
2767
|
}
|
|
2458
|
-
return patternToRegex(pattern).test(
|
|
2768
|
+
return patternToRegex(pattern).test(path13);
|
|
2459
2769
|
}
|
|
2460
2770
|
function createProbeState() {
|
|
2461
2771
|
return { navigated: false, matches: 0 };
|
|
@@ -2477,10 +2787,10 @@ function isProbeMode() {
|
|
|
2477
2787
|
function loadPlan() {
|
|
2478
2788
|
if (planCache !== void 0) return planCache;
|
|
2479
2789
|
planCache = null;
|
|
2480
|
-
const
|
|
2481
|
-
if (
|
|
2790
|
+
const path13 = process.env[PIWI_PROBE_ENV.plan];
|
|
2791
|
+
if (path13) {
|
|
2482
2792
|
try {
|
|
2483
|
-
planCache = JSON.parse(fs2.readFileSync(
|
|
2793
|
+
planCache = JSON.parse(fs2.readFileSync(path13, "utf8"));
|
|
2484
2794
|
} catch {
|
|
2485
2795
|
planCache = null;
|
|
2486
2796
|
}
|
|
@@ -2490,7 +2800,7 @@ function loadPlan() {
|
|
|
2490
2800
|
function probeItemForTest(test) {
|
|
2491
2801
|
const plan = loadPlan();
|
|
2492
2802
|
if (!plan) return null;
|
|
2493
|
-
const filePath = test.file ?
|
|
2803
|
+
const filePath = test.file ? relativeTestPath(test.file, test.base ?? process.cwd()) : null;
|
|
2494
2804
|
const suitePath = test.titlePath ? test.titlePath.slice(1, -1) : void 0;
|
|
2495
2805
|
return matchProbeItem(plan, { title: test.title, filePath, suitePath });
|
|
2496
2806
|
}
|
|
@@ -2504,10 +2814,10 @@ function outcomeFromStatus(status, applied) {
|
|
|
2504
2814
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
2505
2815
|
}
|
|
2506
2816
|
function recordProbeOutcome(line) {
|
|
2507
|
-
const
|
|
2508
|
-
if (!
|
|
2817
|
+
const path13 = process.env[PIWI_PROBE_ENV.results];
|
|
2818
|
+
if (!path13) return;
|
|
2509
2819
|
try {
|
|
2510
|
-
fs2.appendFileSync(
|
|
2820
|
+
fs2.appendFileSync(path13, `${JSON.stringify(line)}
|
|
2511
2821
|
`);
|
|
2512
2822
|
} catch {
|
|
2513
2823
|
}
|
|
@@ -2541,498 +2851,215 @@ function dropOneField(body) {
|
|
|
2541
2851
|
let parsed2;
|
|
2542
2852
|
try {
|
|
2543
2853
|
parsed2 = JSON.parse(body);
|
|
2544
|
-
} catch {
|
|
2545
|
-
return body;
|
|
2546
|
-
}
|
|
2547
|
-
if (Array.isArray(parsed2)) {
|
|
2548
|
-
let key;
|
|
2549
|
-
for (const el of parsed2) {
|
|
2550
|
-
if (el && typeof el === "object" && !Array.isArray(el)) {
|
|
2551
|
-
key ??= Object.keys(el)[0];
|
|
2552
|
-
if (key) delete el[key];
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
return JSON.stringify(parsed2);
|
|
2556
|
-
}
|
|
2557
|
-
if (parsed2 && typeof parsed2 === "object") {
|
|
2558
|
-
const obj = parsed2;
|
|
2559
|
-
const key = Object.keys(obj)[0];
|
|
2560
|
-
if (key) delete obj[key];
|
|
2561
|
-
return JSON.stringify(obj);
|
|
2562
|
-
}
|
|
2563
|
-
return body;
|
|
2564
|
-
}
|
|
2565
|
-
function computeFault(fault, input) {
|
|
2566
|
-
const base = {
|
|
2567
|
-
status: input.status,
|
|
2568
|
-
body: input.body,
|
|
2569
|
-
contentType: input.contentType ?? null,
|
|
2570
|
-
delayMs: 0,
|
|
2571
|
-
changed: false
|
|
2572
|
-
};
|
|
2573
|
-
switch (fault) {
|
|
2574
|
-
case "status-500":
|
|
2575
|
-
return { ...base, status: 500, changed: input.status !== 500 };
|
|
2576
|
-
case "empty-body":
|
|
2577
|
-
return { ...base, body: "", changed: input.body.length > 0 };
|
|
2578
|
-
case "drop-field": {
|
|
2579
|
-
const body = dropOneField(input.body);
|
|
2580
|
-
return { ...base, body, changed: body !== input.body };
|
|
2581
|
-
}
|
|
2582
|
-
case "stale-value": {
|
|
2583
|
-
const body = input.priorBody ?? input.body;
|
|
2584
|
-
return { ...base, body, changed: body !== input.body };
|
|
2585
|
-
}
|
|
2586
|
-
case "slow":
|
|
2587
|
-
return { ...base, delayMs: SLOW_FAULT_DELAY_MS, changed: true };
|
|
2588
|
-
default:
|
|
2589
|
-
return base;
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
// src/internal/probe/sign.ts
|
|
2594
|
-
var import_node_crypto = require("crypto");
|
|
2595
|
-
function signProbeMessage(secret, nonce, ts, specJson) {
|
|
2596
|
-
return (0, import_node_crypto.createHmac)("sha256", secret).update(`${nonce}.${ts}.${specJson}`).digest("hex");
|
|
2597
|
-
}
|
|
2598
|
-
function buildProbeHeader(secret, spec, now = Date.now()) {
|
|
2599
|
-
const nonce = (0, import_node_crypto.randomBytes)(8).toString("hex");
|
|
2600
|
-
const specJson = JSON.stringify(spec);
|
|
2601
|
-
const sig = signProbeMessage(secret, nonce, now, specJson);
|
|
2602
|
-
return Buffer.from(JSON.stringify({ nonce, ts: now, specJson, sig }), "utf8").toString("base64");
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
// src/internal/probe/interception.ts
|
|
2606
|
-
function parseProbeTrace(traceHeader) {
|
|
2607
|
-
const none = { appliedFault: null, serverError: false };
|
|
2608
|
-
if (!traceHeader) return none;
|
|
2609
|
-
try {
|
|
2610
|
-
const spans = JSON.parse((0, import_node_zlib.gunzipSync)(Buffer.from(traceHeader, "base64")).toString("utf8"));
|
|
2611
|
-
if (!Array.isArray(spans)) return none;
|
|
2612
|
-
const root = spans.find((s) => s && typeof s === "object" && !s.parentId);
|
|
2613
|
-
if (!root) return none;
|
|
2614
|
-
const attrs = root.attrs ?? {};
|
|
2615
|
-
const appliedRaw = attrs["piwi.probe.applied"];
|
|
2616
|
-
const status = Number(attrs["http.status_code"]) || 0;
|
|
2617
|
-
return {
|
|
2618
|
-
appliedFault: appliedRaw != null ? String(appliedRaw) : null,
|
|
2619
|
-
serverError: root.status === "error" || status >= 500
|
|
2620
|
-
};
|
|
2621
|
-
} catch {
|
|
2622
|
-
return none;
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
function fulfillHeaders(responseHeaders, contentType) {
|
|
2626
|
-
const headers = { ...responseHeaders };
|
|
2627
|
-
delete headers["content-length"];
|
|
2628
|
-
delete headers["content-encoding"];
|
|
2629
|
-
if (contentType) headers["content-type"] = contentType;
|
|
2630
|
-
return headers;
|
|
2631
|
-
}
|
|
2632
|
-
function originOf(url) {
|
|
2633
|
-
try {
|
|
2634
|
-
const origin = new URL(url).origin;
|
|
2635
|
-
return origin && origin !== "null" ? origin : null;
|
|
2636
|
-
} catch {
|
|
2637
|
-
return null;
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, Math.max(0, ms)));
|
|
2641
|
-
async function performRouteAction(route, action, startedAt) {
|
|
2642
|
-
switch (action.kind) {
|
|
2643
|
-
case "abort":
|
|
2644
|
-
await route.abort("connectionreset");
|
|
2645
|
-
return;
|
|
2646
|
-
case "status":
|
|
2647
|
-
await route.fulfill({ status: action.status, body: "" });
|
|
2648
|
-
return;
|
|
2649
|
-
case "delay": {
|
|
2650
|
-
const response = await route.fetch();
|
|
2651
|
-
await sleep(startedAt + action.ms - Date.now());
|
|
2652
|
-
await route.fulfill({ response });
|
|
2653
|
-
return;
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
async function installRouteRules(page, rules) {
|
|
2658
|
-
const state = rules.map(() => createProbeState());
|
|
2659
|
-
page.on("framenavigated", (frame) => {
|
|
2660
|
-
if (frame === page.mainFrame()) for (const s of state) markNavigated(s);
|
|
2661
|
-
});
|
|
2662
|
-
await page.route("**/*", async (route) => {
|
|
2663
|
-
const startedAt = Date.now();
|
|
2664
|
-
const request = route.request();
|
|
2665
|
-
for (let i = 0; i < rules.length; i++) {
|
|
2666
|
-
const rule = rules[i];
|
|
2667
|
-
if (rule.done?.() || !rule.matches(request.method(), request.url())) continue;
|
|
2668
|
-
if (!shouldAct(state[i], rule.match)) continue;
|
|
2669
|
-
await rule.handle(route, startedAt);
|
|
2670
|
-
return;
|
|
2671
|
-
}
|
|
2672
|
-
await route.fallback();
|
|
2673
|
-
});
|
|
2674
|
-
return { state };
|
|
2675
|
-
}
|
|
2676
|
-
async function installProbeInterception(page, item) {
|
|
2677
|
-
if (!isFaultAllowed(item.level ?? "client", item.fault)) {
|
|
2678
|
-
return { applied: () => false, appliedFault: () => null, serverError: () => false };
|
|
2679
|
-
}
|
|
2680
|
-
let applied = false;
|
|
2681
|
-
let appliedFault = null;
|
|
2682
|
-
let serverError = false;
|
|
2683
|
-
let appOrigin = null;
|
|
2684
|
-
const probeSecret = process.env[PIWI_PROBE_ENV.secret];
|
|
2685
|
-
page.on("framenavigated", (frame) => {
|
|
2686
|
-
if (frame === page.mainFrame()) {
|
|
2687
|
-
const origin = originOf(page.url());
|
|
2688
|
-
if (origin) appOrigin = origin;
|
|
2689
|
-
}
|
|
2690
|
-
});
|
|
2691
|
-
const handle = async (route, startedAt) => {
|
|
2692
|
-
const request = route.request();
|
|
2693
|
-
if (item.level === "server") {
|
|
2694
|
-
if (!probeSecret || appOrigin !== null && originOf(request.url()) !== appOrigin) {
|
|
2695
|
-
await route.fallback();
|
|
2696
|
-
return;
|
|
2697
|
-
}
|
|
2698
|
-
const spec = { route: item.routeKey, fault: item.fault, nth: item.nth };
|
|
2699
|
-
if (item.dependency) spec.dependency = item.dependency;
|
|
2700
|
-
try {
|
|
2701
|
-
const response = await route.fetch({
|
|
2702
|
-
headers: { ...request.headers(), "x-piwi-probe": buildProbeHeader(probeSecret, spec) }
|
|
2703
|
-
});
|
|
2704
|
-
const trace = parseProbeTrace(response.headers()["x-piwi-trace"]);
|
|
2705
|
-
appliedFault = trace.appliedFault;
|
|
2706
|
-
serverError = trace.serverError;
|
|
2707
|
-
applied = trace.appliedFault != null && trace.appliedFault.split(":", 1)[0] === item.fault;
|
|
2708
|
-
await route.fulfill({ response });
|
|
2709
|
-
} catch {
|
|
2710
|
-
applied = false;
|
|
2711
|
-
await route.fallback();
|
|
2712
|
-
}
|
|
2713
|
-
return;
|
|
2714
|
-
}
|
|
2715
|
-
try {
|
|
2716
|
-
const action = faultRouteAction(item.fault);
|
|
2717
|
-
if (action) {
|
|
2718
|
-
applied = true;
|
|
2719
|
-
appliedFault = item.fault;
|
|
2720
|
-
await performRouteAction(route, action, startedAt);
|
|
2721
|
-
return;
|
|
2722
|
-
}
|
|
2723
|
-
const response = await route.fetch();
|
|
2724
|
-
const body = await response.text();
|
|
2725
|
-
const out = computeFault(item.fault, {
|
|
2726
|
-
status: response.status(),
|
|
2727
|
-
body,
|
|
2728
|
-
contentType: response.headers()["content-type"] ?? null
|
|
2729
|
-
});
|
|
2730
|
-
applied = out.changed;
|
|
2731
|
-
if (applied) appliedFault = item.fault;
|
|
2732
|
-
const headers = fulfillHeaders(response.headers(), out.contentType);
|
|
2733
|
-
await route.fulfill({ status: out.status, body: out.body, headers });
|
|
2734
|
-
} catch {
|
|
2735
|
-
applied = false;
|
|
2736
|
-
await route.fallback();
|
|
2737
|
-
}
|
|
2738
|
-
};
|
|
2739
|
-
await installRouteRules(page, [
|
|
2740
|
-
{
|
|
2741
|
-
matches: (method, url) => requestMatchesRoute(method, url, item.routeKey),
|
|
2742
|
-
match: Math.max(1, item.nth ?? 1),
|
|
2743
|
-
handle,
|
|
2744
|
-
done: () => applied
|
|
2745
|
-
}
|
|
2746
|
-
]);
|
|
2747
|
-
return { applied: () => applied, appliedFault: () => appliedFault, serverError: () => serverError };
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
// src/internal/flake/mode.ts
|
|
2751
|
-
var fs3 = __toESM(require("fs"));
|
|
2752
|
-
var path4 = __toESM(require("path"));
|
|
2753
|
-
|
|
2754
|
-
// ../core/src/locator-chain.ts
|
|
2755
|
-
var LOCATING_METHODS = /* @__PURE__ */ new Set([
|
|
2756
|
-
"getByRole",
|
|
2757
|
-
"getByTestId",
|
|
2758
|
-
"getByText",
|
|
2759
|
-
"getByLabel",
|
|
2760
|
-
"getByPlaceholder",
|
|
2761
|
-
"getByAltText",
|
|
2762
|
-
"getByTitle",
|
|
2763
|
-
"locator",
|
|
2764
|
-
"frameLocator"
|
|
2765
|
-
]);
|
|
2766
|
-
var NARROWING_METHODS = /* @__PURE__ */ new Set([
|
|
2767
|
-
"filter",
|
|
2768
|
-
"first",
|
|
2769
|
-
"last",
|
|
2770
|
-
"nth",
|
|
2771
|
-
"and",
|
|
2772
|
-
"or",
|
|
2773
|
-
"visible",
|
|
2774
|
-
"contentFrame",
|
|
2775
|
-
"owner"
|
|
2776
|
-
]);
|
|
2777
|
-
var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([...LOCATING_METHODS, ...NARROWING_METHODS]);
|
|
2778
|
-
var LocatorParseError = class extends Error {
|
|
2779
|
-
constructor(message) {
|
|
2780
|
-
super(message);
|
|
2781
|
-
this.name = "LocatorParseError";
|
|
2782
|
-
}
|
|
2783
|
-
};
|
|
2784
|
-
var SIMPLE_ESCAPES = {
|
|
2785
|
-
n: "\n",
|
|
2786
|
-
t: " ",
|
|
2787
|
-
r: "\r",
|
|
2788
|
-
b: "\b",
|
|
2789
|
-
f: "\f",
|
|
2790
|
-
v: "\v",
|
|
2791
|
-
"0": "\0"
|
|
2792
|
-
};
|
|
2793
|
-
var Parser = class {
|
|
2794
|
-
constructor(src) {
|
|
2795
|
-
this.src = src;
|
|
2796
|
-
this.i = 0;
|
|
2797
|
-
}
|
|
2798
|
-
parseTopLevel() {
|
|
2799
|
-
const chain = this.parseChain();
|
|
2800
|
-
this.skipSpace();
|
|
2801
|
-
if (this.i < this.src.length) this.fail("unexpected text");
|
|
2802
|
-
return chain;
|
|
2803
|
-
}
|
|
2804
|
-
fail(message) {
|
|
2805
|
-
throw new LocatorParseError(`${message} at "${this.src.slice(this.i, this.i + 20)}"`);
|
|
2806
|
-
}
|
|
2807
|
-
skipSpace() {
|
|
2808
|
-
while (this.i < this.src.length && /\s/.test(this.src[this.i])) this.i++;
|
|
2809
|
-
}
|
|
2810
|
-
peek() {
|
|
2811
|
-
return this.src[this.i];
|
|
2812
|
-
}
|
|
2813
|
-
expect(ch) {
|
|
2814
|
-
this.skipSpace();
|
|
2815
|
-
if (this.src[this.i] !== ch) this.fail(`expected "${ch}"`);
|
|
2816
|
-
this.i++;
|
|
2817
|
-
}
|
|
2818
|
-
identifier() {
|
|
2819
|
-
const m = /^[A-Za-z_$][\w$]*/.exec(this.src.slice(this.i));
|
|
2820
|
-
if (!m) this.fail("expected a name");
|
|
2821
|
-
this.i += m[0].length;
|
|
2822
|
-
return m[0];
|
|
2823
|
-
}
|
|
2824
|
-
/**
|
|
2825
|
-
* A chain of calls joined by `.`. With `lenient`, parsing stops before the
|
|
2826
|
-
* first link that doesn't parse instead of failing, so a chain followed by
|
|
2827
|
-
* other text (an error message) is read up to where it ends. `spans` receives
|
|
2828
|
-
* where each top-level call starts and ends.
|
|
2829
|
-
*/
|
|
2830
|
-
parseChain(opts = {}) {
|
|
2831
|
-
this.skipSpace();
|
|
2832
|
-
const calls = [];
|
|
2833
|
-
for (; ; ) {
|
|
2834
|
-
const before = this.i;
|
|
2835
|
-
if (calls.length > 0) this.i++;
|
|
2836
|
-
this.skipSpace();
|
|
2837
|
-
const start = this.i;
|
|
2838
|
-
try {
|
|
2839
|
-
calls.push(this.parseCall(calls.length === 0));
|
|
2840
|
-
} catch (error) {
|
|
2841
|
-
if (!opts.lenient || calls.length === 0) throw error;
|
|
2842
|
-
this.i = before;
|
|
2843
|
-
break;
|
|
2844
|
-
}
|
|
2845
|
-
opts.spans?.push({ start, end: this.i });
|
|
2846
|
-
const end = this.i;
|
|
2847
|
-
this.skipSpace();
|
|
2848
|
-
if (this.peek() !== ".") {
|
|
2849
|
-
this.i = end;
|
|
2850
|
-
break;
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
return { calls };
|
|
2854
|
-
}
|
|
2855
|
-
/** Where the parser stopped. */
|
|
2856
|
-
get position() {
|
|
2857
|
-
return this.i;
|
|
2858
|
-
}
|
|
2859
|
-
parseCall(first) {
|
|
2860
|
-
this.skipSpace();
|
|
2861
|
-
const method = this.identifier();
|
|
2862
|
-
if (!LOCATOR_CHAIN_METHODS.has(method)) this.fail(`unsupported method ${method}()`);
|
|
2863
|
-
if (first && !LOCATING_METHODS.has(method)) this.fail(`${method}() needs a locator before it`);
|
|
2864
|
-
this.expect("(");
|
|
2865
|
-
const args = [];
|
|
2866
|
-
this.skipSpace();
|
|
2867
|
-
if (this.peek() === ")") {
|
|
2868
|
-
this.i++;
|
|
2869
|
-
return { method, args };
|
|
2870
|
-
}
|
|
2871
|
-
for (; ; ) {
|
|
2872
|
-
args.push(this.parseValue());
|
|
2873
|
-
this.skipSpace();
|
|
2874
|
-
if (this.peek() === ",") {
|
|
2875
|
-
this.i++;
|
|
2876
|
-
this.skipSpace();
|
|
2877
|
-
if (this.peek() === ")") {
|
|
2878
|
-
this.i++;
|
|
2879
|
-
break;
|
|
2880
|
-
}
|
|
2881
|
-
continue;
|
|
2882
|
-
}
|
|
2883
|
-
this.expect(")");
|
|
2884
|
-
break;
|
|
2885
|
-
}
|
|
2886
|
-
return { method, args };
|
|
2887
|
-
}
|
|
2888
|
-
parseValue() {
|
|
2889
|
-
this.skipSpace();
|
|
2890
|
-
const ch = this.peek();
|
|
2891
|
-
if (ch === "'" || ch === '"' || ch === "`") return { type: "string", value: this.parseString() };
|
|
2892
|
-
if (ch === "/") return this.parseRegex();
|
|
2893
|
-
if (ch === "{") return this.parseObject();
|
|
2894
|
-
if (ch !== void 0 && /[-\d]/.test(ch)) {
|
|
2895
|
-
const m = /^-?\d+(?:\.\d+)?/.exec(this.src.slice(this.i));
|
|
2896
|
-
if (!m) this.fail("expected a number");
|
|
2897
|
-
this.i += m[0].length;
|
|
2898
|
-
return { type: "number", value: Number(m[0]) };
|
|
2899
|
-
}
|
|
2900
|
-
const rest = this.src.slice(this.i);
|
|
2901
|
-
if (/^true\b/.test(rest)) {
|
|
2902
|
-
this.i += 4;
|
|
2903
|
-
return { type: "boolean", value: true };
|
|
2904
|
-
}
|
|
2905
|
-
if (/^false\b/.test(rest)) {
|
|
2906
|
-
this.i += 5;
|
|
2907
|
-
return { type: "boolean", value: false };
|
|
2908
|
-
}
|
|
2909
|
-
if (/^[A-Za-z_$]/.test(rest)) return { type: "chain", chain: this.parseChain() };
|
|
2910
|
-
return this.fail("expected a value");
|
|
2911
|
-
}
|
|
2912
|
-
/** A quoted string, decoding the escapes `JSON.stringify` and JavaScript produce. */
|
|
2913
|
-
parseString() {
|
|
2914
|
-
const quote = this.src[this.i];
|
|
2915
|
-
let out = "";
|
|
2916
|
-
this.i++;
|
|
2917
|
-
while (this.i < this.src.length) {
|
|
2918
|
-
const c = this.src[this.i];
|
|
2919
|
-
if (c === quote) {
|
|
2920
|
-
this.i++;
|
|
2921
|
-
return out;
|
|
2922
|
-
}
|
|
2923
|
-
if (c !== "\\") {
|
|
2924
|
-
out += c;
|
|
2925
|
-
this.i++;
|
|
2926
|
-
continue;
|
|
2927
|
-
}
|
|
2928
|
-
const next = this.src[this.i + 1];
|
|
2929
|
-
if (next === void 0) break;
|
|
2930
|
-
const simple = SIMPLE_ESCAPES[next];
|
|
2931
|
-
if (simple !== void 0) {
|
|
2932
|
-
out += simple;
|
|
2933
|
-
this.i += 2;
|
|
2934
|
-
continue;
|
|
2935
|
-
}
|
|
2936
|
-
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;
|
|
2937
|
-
if (hex) {
|
|
2938
|
-
out += String.fromCodePoint(parseInt(hex[1], 16));
|
|
2939
|
-
this.i += 1 + hex[0].length;
|
|
2940
|
-
continue;
|
|
2941
|
-
}
|
|
2942
|
-
if (next === "u" || next === "x") this.fail("malformed escape");
|
|
2943
|
-
out += next;
|
|
2944
|
-
this.i += 2;
|
|
2945
|
-
}
|
|
2946
|
-
return this.fail("unterminated string");
|
|
2947
|
-
}
|
|
2948
|
-
parseRegex() {
|
|
2949
|
-
let j = this.i + 1;
|
|
2950
|
-
let inClass = false;
|
|
2951
|
-
while (j < this.src.length) {
|
|
2952
|
-
const c = this.src[j];
|
|
2953
|
-
if (c === "\\") {
|
|
2954
|
-
j += 2;
|
|
2955
|
-
continue;
|
|
2956
|
-
}
|
|
2957
|
-
if (c === "[") inClass = true;
|
|
2958
|
-
else if (c === "]") inClass = false;
|
|
2959
|
-
else if (c === "/" && !inClass) break;
|
|
2960
|
-
j++;
|
|
2961
|
-
}
|
|
2962
|
-
if (j >= this.src.length) this.fail("unterminated regex");
|
|
2963
|
-
const source = this.src.slice(this.i + 1, j);
|
|
2964
|
-
const flags = /^[a-z]*/.exec(this.src.slice(j + 1))[0];
|
|
2965
|
-
this.i = j + 1 + flags.length;
|
|
2966
|
-
return { type: "regex", source, flags };
|
|
2967
|
-
}
|
|
2968
|
-
parseObject() {
|
|
2969
|
-
this.expect("{");
|
|
2970
|
-
const entries = [];
|
|
2971
|
-
for (; ; ) {
|
|
2972
|
-
this.skipSpace();
|
|
2973
|
-
if (this.peek() === "}") {
|
|
2974
|
-
this.i++;
|
|
2975
|
-
break;
|
|
2976
|
-
}
|
|
2977
|
-
const ch = this.peek();
|
|
2978
|
-
const key = ch === "'" || ch === '"' ? this.parseString() : this.identifier();
|
|
2979
|
-
this.expect(":");
|
|
2980
|
-
entries.push([key, this.parseValue()]);
|
|
2981
|
-
this.skipSpace();
|
|
2982
|
-
if (this.peek() === ",") {
|
|
2983
|
-
this.i++;
|
|
2984
|
-
continue;
|
|
2854
|
+
} catch {
|
|
2855
|
+
return body;
|
|
2856
|
+
}
|
|
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];
|
|
2985
2863
|
}
|
|
2986
|
-
this.expect("}");
|
|
2987
|
-
break;
|
|
2988
2864
|
}
|
|
2989
|
-
return
|
|
2865
|
+
return JSON.stringify(parsed2);
|
|
2990
2866
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
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;
|
|
2996
2874
|
}
|
|
2997
|
-
function
|
|
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 };
|
|
2891
|
+
}
|
|
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;
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
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;
|
|
2998
2919
|
try {
|
|
2999
|
-
|
|
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;
|
|
3000
2946
|
} catch {
|
|
3001
2947
|
return null;
|
|
3002
2948
|
}
|
|
3003
2949
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
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
|
+
}
|
|
3009
2965
|
}
|
|
3010
|
-
return null;
|
|
3011
2966
|
}
|
|
3012
|
-
function
|
|
3013
|
-
const
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
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;
|
|
3028
3007
|
}
|
|
3029
|
-
|
|
3030
|
-
|
|
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();
|
|
3031
3047
|
}
|
|
3032
3048
|
};
|
|
3033
|
-
|
|
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 };
|
|
3034
3058
|
}
|
|
3035
3059
|
|
|
3060
|
+
// src/internal/flake/mode.ts
|
|
3061
|
+
var fs3 = __toESM(require("fs"));
|
|
3062
|
+
|
|
3036
3063
|
// ../core/src/error-parse.ts
|
|
3037
3064
|
var ANSI_RE = new RegExp("\\u001B\\[[0-9;]*m", "g");
|
|
3038
3065
|
function stripAnsi(text) {
|
|
@@ -3055,11 +3082,20 @@ function extractMessageHead(text) {
|
|
|
3055
3082
|
let head = text;
|
|
3056
3083
|
const callLogIdx = head.indexOf("\nCall log:");
|
|
3057
3084
|
if (callLogIdx !== -1) head = head.slice(0, callLogIdx);
|
|
3058
|
-
const stackIdx = head
|
|
3085
|
+
const stackIdx = stackStart(head);
|
|
3059
3086
|
if (stackIdx !== -1) head = head.slice(0, stackIdx);
|
|
3060
3087
|
const lines = head.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
3061
3088
|
return lines.slice(0, 5).join("\n");
|
|
3062
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
|
+
}
|
|
3063
3099
|
function extractSelector(text) {
|
|
3064
3100
|
const match = SELECTOR_FN_RE.exec(text);
|
|
3065
3101
|
if (!match) return null;
|
|
@@ -3080,7 +3116,7 @@ function extractSelector(text) {
|
|
|
3080
3116
|
}
|
|
3081
3117
|
var LEAF_METHODS = new Set(LOCATOR_BUILDER_METHODS);
|
|
3082
3118
|
function extractStackFrames(text) {
|
|
3083
|
-
const frameRe =
|
|
3119
|
+
const frameRe = /^[ \t]+at (?:.*? \()?([^()\s][^()\n]*?):(\d+):(\d+)\)?[ \t]*$/gm;
|
|
3084
3120
|
const frames = [];
|
|
3085
3121
|
let m;
|
|
3086
3122
|
while ((m = frameRe.exec(text)) !== null) {
|
|
@@ -3098,6 +3134,11 @@ function extractTopFrameFile(text) {
|
|
|
3098
3134
|
}
|
|
3099
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";
|
|
3100
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;
|
|
3101
3142
|
|
|
3102
3143
|
// ../core/src/error-signature.ts
|
|
3103
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;
|
|
@@ -3340,12 +3381,12 @@ function loadFlakePlan() {
|
|
|
3340
3381
|
throw new Error(`[Piwi Dashboard] ${errorMessage(error)} (${file})`);
|
|
3341
3382
|
}
|
|
3342
3383
|
}
|
|
3343
|
-
function relativeTestFile(file) {
|
|
3344
|
-
return file ?
|
|
3384
|
+
function relativeTestFile(file, base = process.cwd()) {
|
|
3385
|
+
return file ? relativeTestPath(file, base) : null;
|
|
3345
3386
|
}
|
|
3346
3387
|
function flakeRoleForTest(plan, test) {
|
|
3347
3388
|
const running = {
|
|
3348
|
-
file: relativeTestFile(test.file),
|
|
3389
|
+
file: relativeTestFile(test.file, test.base),
|
|
3349
3390
|
title: test.title,
|
|
3350
3391
|
suite: test.titlePath ? test.titlePath.slice(1, -1) : void 0
|
|
3351
3392
|
};
|
|
@@ -3461,7 +3502,7 @@ function flakeResultLine(plan, attempt) {
|
|
|
3461
3502
|
experimentId: plan.experimentId,
|
|
3462
3503
|
armId: plan.arm.id,
|
|
3463
3504
|
role: attempt.role,
|
|
3464
|
-
file: relativeTestFile(attempt.file),
|
|
3505
|
+
file: relativeTestFile(attempt.file, attempt.base),
|
|
3465
3506
|
title: attempt.title,
|
|
3466
3507
|
project: attempt.project,
|
|
3467
3508
|
browserName: attempt.browserName,
|
|
@@ -3547,13 +3588,13 @@ function normalizeFunctionRanges(ranges) {
|
|
|
3547
3588
|
}
|
|
3548
3589
|
function normalizeCoveragePath(raw) {
|
|
3549
3590
|
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
3550
|
-
const
|
|
3551
|
-
if (!
|
|
3552
|
-
if (/(^|\/)node_modules\//.test(
|
|
3553
|
-
return
|
|
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;
|
|
3554
3595
|
}
|
|
3555
|
-
function fileEntry(
|
|
3556
|
-
const file = { path:
|
|
3596
|
+
function fileEntry(path13, runtime, blob, functions = [], lines = []) {
|
|
3597
|
+
const file = { path: path13, runtime };
|
|
3557
3598
|
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
3558
3599
|
if (functions.length > 0) file.functions = functions;
|
|
3559
3600
|
if (lines.length > 0) file.lines = lines;
|
|
@@ -3572,11 +3613,11 @@ function sanitizeCoverage(raw) {
|
|
|
3572
3613
|
const files = [];
|
|
3573
3614
|
for (const item of raw.files) {
|
|
3574
3615
|
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
3575
|
-
const
|
|
3576
|
-
if (
|
|
3616
|
+
const path13 = normalizeCoveragePath(item.path);
|
|
3617
|
+
if (path13 === null) continue;
|
|
3577
3618
|
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
3578
3619
|
files.push(
|
|
3579
|
-
fileEntry(
|
|
3620
|
+
fileEntry(path13, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
3580
3621
|
);
|
|
3581
3622
|
}
|
|
3582
3623
|
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
@@ -3768,14 +3809,6 @@ function decodeSourceMap(raw) {
|
|
|
3768
3809
|
return originalAt(line, column)?.source ?? null;
|
|
3769
3810
|
},
|
|
3770
3811
|
originalAt,
|
|
3771
|
-
mapsBetween(source, start, end) {
|
|
3772
|
-
let found = false;
|
|
3773
|
-
eachSegmentBetween(segments, start, end, (i) => {
|
|
3774
|
-
found = segments.sources[i] === source;
|
|
3775
|
-
return found;
|
|
3776
|
-
});
|
|
3777
|
-
return found;
|
|
3778
|
-
},
|
|
3779
3812
|
firstOriginalLineBetween(source, start, end) {
|
|
3780
3813
|
let first = null;
|
|
3781
3814
|
eachSegmentBetween(segments, start, end, (i) => {
|
|
@@ -3935,6 +3968,30 @@ function finalizeCodeReach(files) {
|
|
|
3935
3968
|
return [...out].sort().slice(0, MAX_CODE_REACH_FILES);
|
|
3936
3969
|
}
|
|
3937
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;
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3938
3995
|
// src/internal/capture/inspect-on-failure.ts
|
|
3939
3996
|
function isCi(ci) {
|
|
3940
3997
|
return ci !== void 0 && ci !== "" && ci !== "false";
|
|
@@ -3971,8 +4028,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
|
|
|
3971
4028
|
}
|
|
3972
4029
|
|
|
3973
4030
|
// src/internal/capture/pick-on-failure.ts
|
|
3974
|
-
var
|
|
3975
|
-
var ANSI_RE2 = /\x1b\[[0-9;]*m/g;
|
|
4031
|
+
var path4 = __toESM(require("path"));
|
|
3976
4032
|
function parseLeafLocatorExpression(rawExpr) {
|
|
3977
4033
|
const leaf = parseLeafLocatorCall(rawExpr);
|
|
3978
4034
|
return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
|
|
@@ -3981,14 +4037,13 @@ function deriveFailedLocator(testInfo) {
|
|
|
3981
4037
|
const info = testInfo;
|
|
3982
4038
|
const errors = info.errors && info.errors.length > 0 ? info.errors : info.error ? [info.error] : [];
|
|
3983
4039
|
for (const err of errors) {
|
|
3984
|
-
const
|
|
3985
|
-
${err.stack ?? ""}
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
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);
|
|
3989
4044
|
if (!parsed2) continue;
|
|
3990
4045
|
const loc = err.location;
|
|
3991
|
-
const location = loc ? `${
|
|
4046
|
+
const location = loc ? `${path4.relative(process.cwd(), loc.file).split(path4.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
|
3992
4047
|
return { method: parsed2.method, args: parsed2.args, location };
|
|
3993
4048
|
}
|
|
3994
4049
|
return null;
|
|
@@ -4141,7 +4196,7 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
4141
4196
|
}
|
|
4142
4197
|
|
|
4143
4198
|
// src/internal/capture/pause-at.ts
|
|
4144
|
-
var
|
|
4199
|
+
var path5 = __toESM(require("path"));
|
|
4145
4200
|
|
|
4146
4201
|
// ../core/src/editor-send.ts
|
|
4147
4202
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
@@ -4185,9 +4240,9 @@ var PauseSet = class {
|
|
|
4185
4240
|
var EMPTY = new PauseSet(/* @__PURE__ */ new Set());
|
|
4186
4241
|
function callSiteFile(file, cwd) {
|
|
4187
4242
|
const slashed = file.replace(/\\/g, "/");
|
|
4188
|
-
const absolute =
|
|
4189
|
-
let rel = absolute ?
|
|
4190
|
-
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, "/");
|
|
4191
4246
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
4192
4247
|
return rel;
|
|
4193
4248
|
}
|
|
@@ -4352,7 +4407,7 @@ function browserFunctionsInWorker() {
|
|
|
4352
4407
|
function browserFunctionCoverage(files) {
|
|
4353
4408
|
return sanitizeCoverage({
|
|
4354
4409
|
v: 1,
|
|
4355
|
-
files: [...files].map(([
|
|
4410
|
+
files: [...files].map(([path13, functions]) => ({ path: path13, runtime: "browser", functions }))
|
|
4356
4411
|
});
|
|
4357
4412
|
}
|
|
4358
4413
|
function newKnownFunctions(known, sent) {
|
|
@@ -4365,19 +4420,19 @@ function newKnownFunctions(known, sent) {
|
|
|
4365
4420
|
budget = MAX_COVERAGE_RANGES;
|
|
4366
4421
|
};
|
|
4367
4422
|
for (const [raw, ranges] of known) {
|
|
4368
|
-
const
|
|
4369
|
-
if (
|
|
4370
|
-
let named = sent.get(
|
|
4423
|
+
const path13 = normalizeCoveragePath(raw);
|
|
4424
|
+
if (path13 === null) continue;
|
|
4425
|
+
let named = sent.get(path13);
|
|
4371
4426
|
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
4372
4427
|
if (fresh.length === 0) continue;
|
|
4373
|
-
if (!named) sent.set(
|
|
4428
|
+
if (!named) sent.set(path13, named = /* @__PURE__ */ new Set());
|
|
4374
4429
|
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
4375
4430
|
while (fresh.length > 0) {
|
|
4376
4431
|
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
4377
4432
|
const part = fresh.slice(0, budget);
|
|
4378
4433
|
fresh = fresh.slice(part.length);
|
|
4379
4434
|
budget -= part.length;
|
|
4380
|
-
files.push({ path:
|
|
4435
|
+
files.push({ path: path13, allFunctions: part });
|
|
4381
4436
|
}
|
|
4382
4437
|
}
|
|
4383
4438
|
close();
|
|
@@ -4388,7 +4443,7 @@ function serverCoverageInWorker() {
|
|
|
4388
4443
|
}
|
|
4389
4444
|
|
|
4390
4445
|
// src/internal/support/aria-sampling.ts
|
|
4391
|
-
var
|
|
4446
|
+
var path6 = __toESM(require("path"));
|
|
4392
4447
|
var os3 = __toESM(require("os"));
|
|
4393
4448
|
var fs4 = __toESM(require("fs"));
|
|
4394
4449
|
|
|
@@ -4404,7 +4459,7 @@ function ariaSampleIdentity(filePath, title) {
|
|
|
4404
4459
|
return `${filePath}\0${title}`;
|
|
4405
4460
|
}
|
|
4406
4461
|
function getAriaSampleFilePath(projectName) {
|
|
4407
|
-
return
|
|
4462
|
+
return path6.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
4408
4463
|
}
|
|
4409
4464
|
var cachedSets = /* @__PURE__ */ new Map();
|
|
4410
4465
|
function loadAriaSampleSet(projectName) {
|
|
@@ -4422,33 +4477,32 @@ function loadAriaSampleSet(projectName) {
|
|
|
4422
4477
|
cachedSets.set(projectName, set);
|
|
4423
4478
|
return set;
|
|
4424
4479
|
}
|
|
4425
|
-
function relativeTestFile2(file) {
|
|
4426
|
-
return path7.relative(process.cwd(), file).split(path7.sep).join("/");
|
|
4427
|
-
}
|
|
4428
4480
|
function isDueForAriaSample(testInfo) {
|
|
4429
4481
|
const projectName = process.env.PIWI_PROJECT_NAME;
|
|
4430
4482
|
if (!projectName) return false;
|
|
4431
4483
|
const set = loadAriaSampleSet(projectName);
|
|
4432
4484
|
if (!set || set.size === 0) return false;
|
|
4433
|
-
return set.has(
|
|
4485
|
+
return set.has(
|
|
4486
|
+
ariaSampleIdentity(relativeTestPath(testInfo.file, configDirFromConfig(testInfo.config)), testInfo.title)
|
|
4487
|
+
);
|
|
4434
4488
|
}
|
|
4435
4489
|
|
|
4436
4490
|
// src/internal/capture/quiet-capture.ts
|
|
4437
4491
|
var import_node_module = require("module");
|
|
4438
|
-
var
|
|
4492
|
+
var path8 = __toESM(require("path"));
|
|
4439
4493
|
|
|
4440
4494
|
// src/internal/support/reporter-version.ts
|
|
4441
4495
|
var fs5 = __toESM(require("fs"));
|
|
4442
|
-
var
|
|
4496
|
+
var path7 = __toESM(require("path"));
|
|
4443
4497
|
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
4444
4498
|
function findOwnPackageJson(fromDir) {
|
|
4445
4499
|
for (const offset of CANDIDATE_OFFSETS) {
|
|
4446
4500
|
try {
|
|
4447
|
-
const pkgPath =
|
|
4501
|
+
const pkgPath = path7.resolve(fromDir, offset, "package.json");
|
|
4448
4502
|
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
4449
4503
|
const { name, version } = pkg;
|
|
4450
4504
|
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
4451
|
-
return { version, root:
|
|
4505
|
+
return { version, root: path7.dirname(pkgPath) };
|
|
4452
4506
|
} catch {
|
|
4453
4507
|
}
|
|
4454
4508
|
}
|
|
@@ -4492,7 +4546,7 @@ function resolveBoxing() {
|
|
|
4492
4546
|
if (typeof set !== "function") continue;
|
|
4493
4547
|
return {
|
|
4494
4548
|
set,
|
|
4495
|
-
prefixes: [
|
|
4549
|
+
prefixes: [path8.dirname(playwrightPackageJson), path8.join(root, "dist") + path8.sep]
|
|
4496
4550
|
};
|
|
4497
4551
|
}
|
|
4498
4552
|
} catch {
|
|
@@ -4523,7 +4577,7 @@ function internalCall(owner, run) {
|
|
|
4523
4577
|
// src/internal/capture/code-reach.ts
|
|
4524
4578
|
var crypto2 = __toESM(require("crypto"));
|
|
4525
4579
|
var fs6 = __toESM(require("fs"));
|
|
4526
|
-
var
|
|
4580
|
+
var path9 = __toESM(require("path"));
|
|
4527
4581
|
var import_node_child_process = require("child_process");
|
|
4528
4582
|
var MAX_MAP_BYTES = 20 * 1024 * 1024;
|
|
4529
4583
|
var MAP_TIMEOUT_MS = 5e3;
|
|
@@ -4566,12 +4620,12 @@ function repositoryRoot(from) {
|
|
|
4566
4620
|
}).trim();
|
|
4567
4621
|
} catch {
|
|
4568
4622
|
}
|
|
4569
|
-
cachedRepoRoot = { from, root:
|
|
4623
|
+
cachedRepoRoot = { from, root: path9.resolve(root) };
|
|
4570
4624
|
return cachedRepoRoot.root;
|
|
4571
4625
|
}
|
|
4572
4626
|
function codeReachRoots(configDir, configured) {
|
|
4573
4627
|
const repoRoot = repositoryRoot(configDir);
|
|
4574
|
-
const resolve3 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) =>
|
|
4628
|
+
const resolve3 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path9.resolve(configDir, r));
|
|
4575
4629
|
const global = Array.isArray(configured) ? resolve3(configured) : resolve3(configured?.["*"]);
|
|
4576
4630
|
const byOrigin = /* @__PURE__ */ new Map();
|
|
4577
4631
|
if (configured && !Array.isArray(configured)) {
|
|
@@ -4608,10 +4662,10 @@ function isFile(file) {
|
|
|
4608
4662
|
function resolveSourcePath(source, roots) {
|
|
4609
4663
|
const normalized = normalizeSourcePath(source);
|
|
4610
4664
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
4611
|
-
if (
|
|
4612
|
-
const
|
|
4665
|
+
if (path9.isAbsolute(normalized) && isFile(normalized)) return path9.resolve(normalized);
|
|
4666
|
+
const relative7 = normalized.replace(/^\/+/, "");
|
|
4613
4667
|
for (const root of roots) {
|
|
4614
|
-
const candidate =
|
|
4668
|
+
const candidate = path9.resolve(root, relative7);
|
|
4615
4669
|
if (isFile(candidate)) return candidate;
|
|
4616
4670
|
}
|
|
4617
4671
|
return null;
|
|
@@ -4643,7 +4697,7 @@ function fileForUrl(url, roots, bases) {
|
|
|
4643
4697
|
return file;
|
|
4644
4698
|
}
|
|
4645
4699
|
function relativeToRepo(file, repoRoot) {
|
|
4646
|
-
return
|
|
4700
|
+
return path9.relative(repoRoot, file).split(path9.sep).join("/");
|
|
4647
4701
|
}
|
|
4648
4702
|
function scriptVersion(entry) {
|
|
4649
4703
|
return entry.hash ?? crypto2.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
@@ -4706,10 +4760,10 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
4706
4760
|
const files = /* @__PURE__ */ new Map();
|
|
4707
4761
|
const known = /* @__PURE__ */ new Map();
|
|
4708
4762
|
const addTo = (into, file, functions) => {
|
|
4709
|
-
const
|
|
4710
|
-
const listed = into.get(
|
|
4763
|
+
const path13 = relativeToRepo(file, roots.repoRoot);
|
|
4764
|
+
const listed = into.get(path13);
|
|
4711
4765
|
if (listed) listed.push(...functions);
|
|
4712
|
-
else into.set(
|
|
4766
|
+
else into.set(path13, [...functions]);
|
|
4713
4767
|
};
|
|
4714
4768
|
const add = (file, functions) => addTo(files, file, functions);
|
|
4715
4769
|
const bases = viteBases(entries);
|
|
@@ -4973,7 +5027,7 @@ async function stopCodeReach(page) {
|
|
|
4973
5027
|
|
|
4974
5028
|
// src/internal/capture/resource-ledger.ts
|
|
4975
5029
|
var fs8 = __toESM(require("fs"));
|
|
4976
|
-
var
|
|
5030
|
+
var path11 = __toESM(require("path"));
|
|
4977
5031
|
var import_node_module2 = require("module");
|
|
4978
5032
|
|
|
4979
5033
|
// src/internal/capture/worker-metrics.ts
|
|
@@ -4981,7 +5035,7 @@ var import_node_perf_hooks = require("perf_hooks");
|
|
|
4981
5035
|
|
|
4982
5036
|
// src/internal/support/system-readers.ts
|
|
4983
5037
|
var fs7 = __toESM(require("fs"));
|
|
4984
|
-
var
|
|
5038
|
+
var path10 = __toESM(require("path"));
|
|
4985
5039
|
var CLK_TCK = 100;
|
|
4986
5040
|
function readText(file) {
|
|
4987
5041
|
try {
|
|
@@ -5067,7 +5121,7 @@ var ProcFs = class {
|
|
|
5067
5121
|
}
|
|
5068
5122
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
5069
5123
|
available() {
|
|
5070
|
-
return fs7.existsSync(
|
|
5124
|
+
return fs7.existsSync(path10.join(this.root, "stat"));
|
|
5071
5125
|
}
|
|
5072
5126
|
/** Every process, keyed by pid. */
|
|
5073
5127
|
table() {
|
|
@@ -5080,32 +5134,32 @@ var ProcFs = class {
|
|
|
5080
5134
|
}
|
|
5081
5135
|
for (const name of names) {
|
|
5082
5136
|
if (!/^\d+$/.test(name)) continue;
|
|
5083
|
-
const text = readText(
|
|
5137
|
+
const text = readText(path10.join(this.root, name, "stat"));
|
|
5084
5138
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
5085
5139
|
if (stat) out.set(stat.pid, stat);
|
|
5086
5140
|
}
|
|
5087
5141
|
return out;
|
|
5088
5142
|
}
|
|
5089
5143
|
stat(pid) {
|
|
5090
|
-
const text = readText(
|
|
5144
|
+
const text = readText(path10.join(this.root, String(pid), "stat"));
|
|
5091
5145
|
return text ? parseProcStat(pid, text) : null;
|
|
5092
5146
|
}
|
|
5093
5147
|
runWaitMs(pid) {
|
|
5094
|
-
const text = readText(
|
|
5148
|
+
const text = readText(path10.join(this.root, String(pid), "schedstat"));
|
|
5095
5149
|
return text ? parseSchedstat(text) : null;
|
|
5096
5150
|
}
|
|
5097
5151
|
cmdline(pid) {
|
|
5098
|
-
return (readText(
|
|
5152
|
+
return (readText(path10.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
5099
5153
|
}
|
|
5100
5154
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
5101
5155
|
statusKb(pid, key) {
|
|
5102
|
-
const text = readText(
|
|
5156
|
+
const text = readText(path10.join(this.root, String(pid), "status"));
|
|
5103
5157
|
return text ? parseKbField(text, key) : null;
|
|
5104
5158
|
}
|
|
5105
5159
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
5106
5160
|
async pssKb(pid) {
|
|
5107
5161
|
try {
|
|
5108
|
-
return parseKbField(await fs7.promises.readFile(
|
|
5162
|
+
return parseKbField(await fs7.promises.readFile(path10.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
5109
5163
|
} catch {
|
|
5110
5164
|
return null;
|
|
5111
5165
|
}
|
|
@@ -5113,29 +5167,29 @@ var ProcFs = class {
|
|
|
5113
5167
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
5114
5168
|
resetPeakRss(pid) {
|
|
5115
5169
|
try {
|
|
5116
|
-
fs7.writeFileSync(
|
|
5170
|
+
fs7.writeFileSync(path10.join(this.root, String(pid), "clear_refs"), "5");
|
|
5117
5171
|
return true;
|
|
5118
5172
|
} catch {
|
|
5119
5173
|
return false;
|
|
5120
5174
|
}
|
|
5121
5175
|
}
|
|
5122
5176
|
cpuTicks() {
|
|
5123
|
-
const text = readText(
|
|
5177
|
+
const text = readText(path10.join(this.root, "stat"));
|
|
5124
5178
|
return text ? parseCpuTicks(text) : null;
|
|
5125
5179
|
}
|
|
5126
5180
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
5127
5181
|
pressureUs(resource) {
|
|
5128
|
-
const text = readText(
|
|
5182
|
+
const text = readText(path10.join(this.root, "pressure", resource));
|
|
5129
5183
|
return text ? parsePsiTotal(text) : null;
|
|
5130
5184
|
}
|
|
5131
5185
|
memAvailableKb() {
|
|
5132
|
-
const text = readText(
|
|
5186
|
+
const text = readText(path10.join(this.root, "meminfo"));
|
|
5133
5187
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
5134
5188
|
}
|
|
5135
5189
|
/** Open file descriptors of a process. */
|
|
5136
5190
|
fdCount(pid) {
|
|
5137
5191
|
try {
|
|
5138
|
-
return fs7.readdirSync(
|
|
5192
|
+
return fs7.readdirSync(path10.join(this.root, String(pid), "fd")).length;
|
|
5139
5193
|
} catch {
|
|
5140
5194
|
return null;
|
|
5141
5195
|
}
|
|
@@ -5297,10 +5351,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
|
5297
5351
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
5298
5352
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
5299
5353
|
function relativePath(file) {
|
|
5300
|
-
return
|
|
5354
|
+
return path11.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path11.sep).join("/");
|
|
5301
5355
|
}
|
|
5302
5356
|
function isLibraryFile(file) {
|
|
5303
|
-
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;
|
|
5304
5358
|
}
|
|
5305
5359
|
function userSite(stack) {
|
|
5306
5360
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
@@ -5348,7 +5402,7 @@ function playwrightTestInfoSource() {
|
|
|
5348
5402
|
try {
|
|
5349
5403
|
const packageJson = resolvePlaywrightPackageJson();
|
|
5350
5404
|
if (!packageJson) return () => null;
|
|
5351
|
-
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"));
|
|
5352
5406
|
const read = globals.currentTestInfo;
|
|
5353
5407
|
if (typeof read !== "function") return () => null;
|
|
5354
5408
|
return () => {
|
|
@@ -6170,7 +6224,7 @@ function configuredCodeReachRoots() {
|
|
|
6170
6224
|
}
|
|
6171
6225
|
} catch {
|
|
6172
6226
|
}
|
|
6173
|
-
return raw.split(
|
|
6227
|
+
return raw.split(path12.delimiter).filter(Boolean);
|
|
6174
6228
|
}
|
|
6175
6229
|
async function stopSinkCodeReach(sink, closing) {
|
|
6176
6230
|
const stopping = [];
|
|
@@ -6186,9 +6240,7 @@ async function stopSinkCodeReach(sink, closing) {
|
|
|
6186
6240
|
}
|
|
6187
6241
|
if (stopping.length > 0) {
|
|
6188
6242
|
const info = sink.testInfo;
|
|
6189
|
-
const
|
|
6190
|
-
const configDir = configFile ? path13.dirname(configFile) : info?.config.rootDir ?? process.cwd();
|
|
6191
|
-
const roots = codeReachRoots(configDir, configuredCodeReachRoots());
|
|
6243
|
+
const roots = codeReachRoots(configDirFromConfig(info?.config), configuredCodeReachRoots());
|
|
6192
6244
|
for (const [page, session] of stopping) {
|
|
6193
6245
|
session.stopping = stopPageCodeReach(sink, page, session, roots);
|
|
6194
6246
|
underway.push(session.stopping);
|
|
@@ -6330,8 +6382,8 @@ function buildPageState(raw, cookies, interaction) {
|
|
|
6330
6382
|
length: typeof e.length === "number" ? e.length : 0
|
|
6331
6383
|
}));
|
|
6332
6384
|
return {
|
|
6333
|
-
url: raw.url,
|
|
6334
|
-
hash: raw.hash
|
|
6385
|
+
url: raw.url.split("#")[0],
|
|
6386
|
+
hash: raw.hash ? maskUrlFragment(raw.hash) : null,
|
|
6335
6387
|
historyState: historyState || null,
|
|
6336
6388
|
localStorage: capStorage(raw.localStorage),
|
|
6337
6389
|
sessionStorage: capStorage(raw.sessionStorage),
|
|
@@ -7348,6 +7400,7 @@ var piwiFixtures = {
|
|
|
7348
7400
|
const plan = loadFlakePlan();
|
|
7349
7401
|
const role = flakeRoleForTest(plan, {
|
|
7350
7402
|
file: testInfo.file,
|
|
7403
|
+
base: configDirFromConfig(testInfo.config),
|
|
7351
7404
|
title: testInfo.title,
|
|
7352
7405
|
titlePath: testInfo.titlePath,
|
|
7353
7406
|
project: testInfo.project.name
|
|
@@ -7361,7 +7414,8 @@ var piwiFixtures = {
|
|
|
7361
7414
|
sink.probeItem = probeItemForTest({
|
|
7362
7415
|
title: testInfo.title,
|
|
7363
7416
|
file: testInfo.file,
|
|
7364
|
-
titlePath: testInfo.titlePath
|
|
7417
|
+
titlePath: testInfo.titlePath,
|
|
7418
|
+
base: configDirFromConfig(testInfo.config)
|
|
7365
7419
|
});
|
|
7366
7420
|
activeResourceLedger()?.testStarted(testInfo);
|
|
7367
7421
|
currentSink = sink;
|
|
@@ -7405,6 +7459,7 @@ async function recordFlakeAttempt(flake, testInfo) {
|
|
|
7405
7459
|
flakeResultLine(flake.plan, {
|
|
7406
7460
|
role: flake.role,
|
|
7407
7461
|
file: testInfo.file,
|
|
7462
|
+
base: configDirFromConfig(testInfo.config),
|
|
7408
7463
|
title: testInfo.title,
|
|
7409
7464
|
project: testInfo.project.name || null,
|
|
7410
7465
|
browserName: flake.browserName,
|