@piwitests/reporter 0.24.0 → 0.26.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/README.md +7 -7
- package/dist/cli/index.js +430 -8
- package/dist/global-setup-module.js +4 -1
- package/dist/index.d.ts +51 -2
- package/dist/index.js +348 -51
- package/dist/internal/capture/capture-fixtures.js +106 -23
- package/dist/internal/capture/locator-healing.d.ts +28 -1
- package/dist/internal/capture/locator-healing.js +44 -0
- package/package.json +7 -2
- package/templates/skills/investigate-failure/SKILL.md +1 -1
- package/templates/skills/run-the-right-tests/SKILL.md +38 -0
- package/templates/skills/setup-piwi/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
extendPiwiFixtures: () => extendPiwiFixtures,
|
|
38
38
|
piwiAiFixtures: () => piwiAiFixtures,
|
|
39
39
|
piwiFixtures: () => piwiFixtures,
|
|
40
|
+
resolveSelection: () => resolveSelection,
|
|
40
41
|
wrapConfig: () => wrapConfig
|
|
41
42
|
});
|
|
42
43
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -79,6 +80,7 @@ var DEFAULTS = {
|
|
|
79
80
|
streaming: true,
|
|
80
81
|
streamingBatchSize: 5,
|
|
81
82
|
streamingBatchDelay: 2e3,
|
|
83
|
+
failOnFlakyTests: false,
|
|
82
84
|
username: null,
|
|
83
85
|
password: null,
|
|
84
86
|
apiKey: null,
|
|
@@ -98,6 +100,7 @@ var PIWI_ENV_KEYS = {
|
|
|
98
100
|
streamingBatchSize: "PIWI_STREAMING_BATCH_SIZE",
|
|
99
101
|
streamingBatchDelay: "PIWI_STREAMING_BATCH_DELAY",
|
|
100
102
|
liveFileUploads: "PIWI_LIVE_FILE_UPLOADS",
|
|
103
|
+
failOnFlakyTests: "PIWI_FAIL_ON_FLAKY_TESTS",
|
|
101
104
|
uploadTraces: "PIWI_UPLOAD_TRACES",
|
|
102
105
|
uploadReport: "PIWI_UPLOAD_REPORT",
|
|
103
106
|
captureLocators: "PIWI_CAPTURE_LOCATORS",
|
|
@@ -116,6 +119,12 @@ var PIWI_ENV_KEYS = {
|
|
|
116
119
|
aiScreenshotFallback: "PIWI_AI_SCREENSHOT_FALLBACK"
|
|
117
120
|
};
|
|
118
121
|
var PIWI_DESKTOP_CONFIG_ENV = "PIWI_DESKTOP_CONFIG";
|
|
122
|
+
var PIWI_SELECTION_ENV = {
|
|
123
|
+
key: "PIWI_SELECTION",
|
|
124
|
+
version: "PIWI_SELECTION_VERSION",
|
|
125
|
+
hash: "PIWI_SELECTION_HASH",
|
|
126
|
+
count: "PIWI_SELECTION_COUNT"
|
|
127
|
+
};
|
|
119
128
|
function readBool(val) {
|
|
120
129
|
if (val === void 0) return void 0;
|
|
121
130
|
return val === "true";
|
|
@@ -133,6 +142,7 @@ var ENV_FALLBACK_SPECS = [
|
|
|
133
142
|
{ option: "streamingBatchSize", env: PIWI_ENV_KEYS.streamingBatchSize, kind: "number" },
|
|
134
143
|
{ option: "streamingBatchDelay", env: PIWI_ENV_KEYS.streamingBatchDelay, kind: "number" },
|
|
135
144
|
{ option: "liveFileUploads", env: PIWI_ENV_KEYS.liveFileUploads, kind: "bool" },
|
|
145
|
+
{ option: "failOnFlakyTests", env: PIWI_ENV_KEYS.failOnFlakyTests, kind: "bool" },
|
|
136
146
|
{ option: "uploadTraces", env: PIWI_ENV_KEYS.uploadTraces, kind: "bool" },
|
|
137
147
|
{ option: "uploadReport", env: PIWI_ENV_KEYS.uploadReport, kind: "bool" },
|
|
138
148
|
{ option: "captureLocators", env: PIWI_ENV_KEYS.captureLocators, kind: "bool" },
|
|
@@ -430,8 +440,8 @@ function resolveOverallStatus(result, counters) {
|
|
|
430
440
|
const STATUS_MAP = {
|
|
431
441
|
passed: "passed",
|
|
432
442
|
failed: "failed",
|
|
433
|
-
timedout: "
|
|
434
|
-
interrupted: "
|
|
443
|
+
timedout: "timedout",
|
|
444
|
+
interrupted: "interrupted"
|
|
435
445
|
};
|
|
436
446
|
if (result?.status) return STATUS_MAP[result.status] ?? "failed";
|
|
437
447
|
if (counters.failedTests === 0 && counters.timedOutTests === 0 && counters.totalTests > 0) return "passed";
|
|
@@ -448,6 +458,7 @@ function toWireTestCase(tc) {
|
|
|
448
458
|
timeout: rest.timeout ?? null,
|
|
449
459
|
error: rest.error,
|
|
450
460
|
retries: rest.retries,
|
|
461
|
+
attempts: rest.attempts || null,
|
|
451
462
|
workerIndex: rest.workerIndex ?? null,
|
|
452
463
|
shardIndex: rest.shardIndex ?? null,
|
|
453
464
|
startedAt: rest.startedAt ?? null,
|
|
@@ -470,7 +481,9 @@ function toWireTestCase(tc) {
|
|
|
470
481
|
testAnnotations: rest.testAnnotations ?? null,
|
|
471
482
|
tags: rest.tags ?? null,
|
|
472
483
|
testMeta: rest.testMeta ?? null,
|
|
473
|
-
locatorSnapshots: rest.locatorSnapshots || null
|
|
484
|
+
locatorSnapshots: rest.locatorSnapshots || null,
|
|
485
|
+
didNotRunReason: rest.didNotRunReason ?? null,
|
|
486
|
+
blockedBy: rest.blockedBy ?? null
|
|
474
487
|
};
|
|
475
488
|
}
|
|
476
489
|
function serializeRun(payload, opts) {
|
|
@@ -523,8 +536,8 @@ var Uploader = class {
|
|
|
523
536
|
auth
|
|
524
537
|
);
|
|
525
538
|
this.logger.info(`Successfully uploaded test results`);
|
|
526
|
-
if (response.
|
|
527
|
-
this.logger.info(`Test Run ID: ${response.
|
|
539
|
+
if (response.runId) {
|
|
540
|
+
this.logger.info(`Test Run ID: ${response.runId}, Project ID: ${response.projectId}`);
|
|
528
541
|
}
|
|
529
542
|
return response;
|
|
530
543
|
}
|
|
@@ -538,8 +551,8 @@ var Uploader = class {
|
|
|
538
551
|
await this.appendFilesToForm(form, payload.testCases, reportOptions.uploadTraces);
|
|
539
552
|
const response = await this.httpClient.postFormData("/api/test-runs/upload", form, auth);
|
|
540
553
|
this.logger.info(`Successfully uploaded test results with files`);
|
|
541
|
-
if (response.
|
|
542
|
-
this.logger.info(`Test Run ID: ${response.
|
|
554
|
+
if (response.runId) {
|
|
555
|
+
this.logger.info(`Test Run ID: ${response.runId}, Project ID: ${response.projectId}`);
|
|
543
556
|
}
|
|
544
557
|
if (response.reports) {
|
|
545
558
|
for (const r of response.reports) this.logger.info(`${r.label}: ${r.path}`);
|
|
@@ -549,7 +562,7 @@ var Uploader = class {
|
|
|
549
562
|
/** Upload report files for an already-submitted streaming run */
|
|
550
563
|
async uploadReportsForStreamingRun(projectName, runId, reportOptions, startTime, auth) {
|
|
551
564
|
const form = new import_form_data2.default();
|
|
552
|
-
form.append("
|
|
565
|
+
form.append("runId", String(runId));
|
|
553
566
|
form.append("projectName", projectName);
|
|
554
567
|
form.append(
|
|
555
568
|
"testRun",
|
|
@@ -1012,6 +1025,39 @@ var FileHandler = class {
|
|
|
1012
1025
|
|
|
1013
1026
|
// src/internal/collect/metadata-collector.ts
|
|
1014
1027
|
var import_node_child_process = require("child_process");
|
|
1028
|
+
var DETACHED_HEAD = "HEAD";
|
|
1029
|
+
function resolveScmBranch(env, gitBranch) {
|
|
1030
|
+
const pick = (...names) => {
|
|
1031
|
+
for (const name of names) {
|
|
1032
|
+
const value = env[name]?.trim();
|
|
1033
|
+
if (value) return value;
|
|
1034
|
+
}
|
|
1035
|
+
return void 0;
|
|
1036
|
+
};
|
|
1037
|
+
const override = env.PIWI_BRANCH?.trim();
|
|
1038
|
+
if (override) return override;
|
|
1039
|
+
let provider;
|
|
1040
|
+
if (env.JENKINS_URL) provider = pick("CHANGE_BRANCH", "BRANCH_NAME");
|
|
1041
|
+
else if (env.GITHUB_ACTIONS) provider = pick("GITHUB_HEAD_REF", "GITHUB_REF_NAME");
|
|
1042
|
+
else if (env.GITLAB_CI) provider = pick("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME", "CI_COMMIT_REF_NAME");
|
|
1043
|
+
else if (env.CIRCLECI) provider = pick("CIRCLE_BRANCH");
|
|
1044
|
+
else if (env.TRAVIS) provider = pick("TRAVIS_PULL_REQUEST_BRANCH", "TRAVIS_BRANCH");
|
|
1045
|
+
else if (env.TF_BUILD) provider = pick("SYSTEM_PULLREQUEST_SOURCEBRANCH", "BUILD_SOURCEBRANCHNAME");
|
|
1046
|
+
else if (env.BITBUCKET_BUILD_NUMBER) provider = pick("BITBUCKET_BRANCH");
|
|
1047
|
+
if (provider) return normalizeRef(provider);
|
|
1048
|
+
const trimmed = gitBranch?.trim();
|
|
1049
|
+
if (trimmed && trimmed !== DETACHED_HEAD) return trimmed;
|
|
1050
|
+
return void 0;
|
|
1051
|
+
}
|
|
1052
|
+
function resolveScmPrNumber(env) {
|
|
1053
|
+
const githubRef = env.GITHUB_ACTIONS ? env.GITHUB_REF?.match(/^refs\/pull\/(\d+)\/(?:merge|head)$/) : void 0;
|
|
1054
|
+
const raw = githubRef?.[1] ?? env.CI_MERGE_REQUEST_IID ?? env.BITBUCKET_PR_ID ?? env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER ?? env.CHANGE_ID;
|
|
1055
|
+
const trimmed = raw?.trim();
|
|
1056
|
+
return trimmed && /^\d+$/.test(trimmed) ? trimmed : void 0;
|
|
1057
|
+
}
|
|
1058
|
+
function normalizeRef(ref) {
|
|
1059
|
+
return ref.replace(/^refs\/heads\//, "");
|
|
1060
|
+
}
|
|
1015
1061
|
var MetadataCollector = class {
|
|
1016
1062
|
constructor(logger = new Logger()) {
|
|
1017
1063
|
this.logger = logger;
|
|
@@ -1117,6 +1163,7 @@ var MetadataCollector = class {
|
|
|
1117
1163
|
}
|
|
1118
1164
|
collectScmInfo(_options) {
|
|
1119
1165
|
const scm = {};
|
|
1166
|
+
let gitBranch;
|
|
1120
1167
|
try {
|
|
1121
1168
|
const execOpts = {
|
|
1122
1169
|
encoding: "utf8",
|
|
@@ -1125,7 +1172,7 @@ var MetadataCollector = class {
|
|
|
1125
1172
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1126
1173
|
};
|
|
1127
1174
|
scm.commit = (0, import_node_child_process.execSync)("git rev-parse HEAD", execOpts).trim();
|
|
1128
|
-
|
|
1175
|
+
gitBranch = (0, import_node_child_process.execSync)("git rev-parse --abbrev-ref HEAD", execOpts).trim();
|
|
1129
1176
|
scm.author = (0, import_node_child_process.execSync)('git log -1 --pretty=format:"%an"', execOpts).trim();
|
|
1130
1177
|
scm.commitMessage = (0, import_node_child_process.execSync)('git log -1 --pretty=format:"%s"', execOpts).trim();
|
|
1131
1178
|
try {
|
|
@@ -1135,6 +1182,10 @@ var MetadataCollector = class {
|
|
|
1135
1182
|
} catch (error) {
|
|
1136
1183
|
this.logger.debug(`Git info not available: ${errorMessage(error)}`);
|
|
1137
1184
|
}
|
|
1185
|
+
const branch = resolveScmBranch(process.env, gitBranch);
|
|
1186
|
+
if (branch) scm.branch = branch;
|
|
1187
|
+
const prNumber = resolveScmPrNumber(process.env);
|
|
1188
|
+
if (prNumber) scm.prNumber = prNumber;
|
|
1138
1189
|
return Object.keys(scm).length > 0 ? scm : void 0;
|
|
1139
1190
|
}
|
|
1140
1191
|
collectCiInfo() {
|
|
@@ -1421,8 +1472,8 @@ var StreamManager = class {
|
|
|
1421
1472
|
if (this.pendingBeginEvents.length > 0) {
|
|
1422
1473
|
this.pendingEvents = [...this.pendingBeginEvents, ...this.pendingEvents];
|
|
1423
1474
|
this.pendingBeginEvents = [];
|
|
1424
|
-
this.flush();
|
|
1425
1475
|
}
|
|
1476
|
+
if (this.pendingEvents.length > 0) this.flush();
|
|
1426
1477
|
}
|
|
1427
1478
|
} catch (error) {
|
|
1428
1479
|
if (error instanceof HttpError && (error.status === 401 || error.status === 403)) {
|
|
@@ -1934,6 +1985,17 @@ function detectCliFileFilters(argv = process.argv) {
|
|
|
1934
1985
|
return files;
|
|
1935
1986
|
}
|
|
1936
1987
|
|
|
1988
|
+
// src/internal/support/selection-env.ts
|
|
1989
|
+
function readSelectionStamp(env = process.env) {
|
|
1990
|
+
const key = env[PIWI_SELECTION_ENV.key];
|
|
1991
|
+
if (!key) return null;
|
|
1992
|
+
const version = Number(env[PIWI_SELECTION_ENV.version]);
|
|
1993
|
+
const resolvedCount = Number(env[PIWI_SELECTION_ENV.count]);
|
|
1994
|
+
const resolvedHash = env[PIWI_SELECTION_ENV.hash] ?? "";
|
|
1995
|
+
if (!Number.isFinite(version) || !Number.isFinite(resolvedCount) || !resolvedHash) return null;
|
|
1996
|
+
return { key, version, resolvedHash, resolvedCount };
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1937
1999
|
// src/public/global-setup.ts
|
|
1938
2000
|
var path10 = __toESM(require("path"));
|
|
1939
2001
|
var fs11 = __toESM(require("fs"));
|
|
@@ -2024,7 +2086,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
2024
2086
|
projectName: opts.projectName
|
|
2025
2087
|
})
|
|
2026
2088
|
);
|
|
2027
|
-
logger.debug(`Global setup:
|
|
2089
|
+
logger.debug(`Global setup: initializing run #${response.runId}`);
|
|
2028
2090
|
}
|
|
2029
2091
|
} catch (error) {
|
|
2030
2092
|
logger.warn(`Could not register global setup: ${errorMessage(error)}`);
|
|
@@ -2067,8 +2129,12 @@ function wrapConfig(config, piwiOptions) {
|
|
|
2067
2129
|
globalSetupModules.push(...orig);
|
|
2068
2130
|
}
|
|
2069
2131
|
globalSetupModules.push(resolveSetupModule());
|
|
2132
|
+
const forwarded = {};
|
|
2133
|
+
const failOnFlaky = piwiOptions?.failOnFlakyTests ?? readBool(process.env[PIWI_ENV_KEYS.failOnFlakyTests]);
|
|
2134
|
+
if (failOnFlaky === true) forwarded.failOnFlakyTests = true;
|
|
2070
2135
|
return {
|
|
2071
2136
|
...config,
|
|
2137
|
+
...forwarded,
|
|
2072
2138
|
reporter: injectReporter(config.reporter, piwiOptions),
|
|
2073
2139
|
globalSetup: globalSetupModules.length === 1 ? globalSetupModules[0] : globalSetupModules
|
|
2074
2140
|
};
|
|
@@ -2093,6 +2159,45 @@ function classifyStatus(rawStatus, annotations) {
|
|
|
2093
2159
|
const intentional = annotations.some((a) => a.type === "skip" || a.type === "fixme");
|
|
2094
2160
|
return intentional ? "skipped" : "didnotrun";
|
|
2095
2161
|
}
|
|
2162
|
+
function resolveUnrunReason(runStatus, opts) {
|
|
2163
|
+
if (runStatus === "timedout") return "global-timeout";
|
|
2164
|
+
if (opts.maxFailures > 0 && opts.failures >= opts.maxFailures) return "max-failures";
|
|
2165
|
+
return "interrupted";
|
|
2166
|
+
}
|
|
2167
|
+
function fileOf(location) {
|
|
2168
|
+
return location.replace(/:\d+:\d+$/, "");
|
|
2169
|
+
}
|
|
2170
|
+
function suiteKey(suitePath) {
|
|
2171
|
+
return (suitePath ?? []).join("");
|
|
2172
|
+
}
|
|
2173
|
+
function sharedPrefixDepth(a, b) {
|
|
2174
|
+
let depth = 0;
|
|
2175
|
+
while (depth < a.length && depth < b.length && a[depth] === b[depth]) depth++;
|
|
2176
|
+
return depth;
|
|
2177
|
+
}
|
|
2178
|
+
function linkBlockedTests(cases) {
|
|
2179
|
+
const blockers = cases.filter((c) => c.status === "failed" || c.status === "timedOut" || c.status === "timedout");
|
|
2180
|
+
if (blockers.length === 0) return;
|
|
2181
|
+
for (const c of cases) {
|
|
2182
|
+
if (c.didNotRunReason !== "previous-failure") continue;
|
|
2183
|
+
const file = fileOf(c.location);
|
|
2184
|
+
const inFile = blockers.filter((b) => fileOf(b.location) === file);
|
|
2185
|
+
if (inFile.length === 0) continue;
|
|
2186
|
+
const key = suiteKey(c.suitePath);
|
|
2187
|
+
let blocker = inFile.find((b) => suiteKey(b.suitePath) === key);
|
|
2188
|
+
if (!blocker) {
|
|
2189
|
+
let bestDepth = 0;
|
|
2190
|
+
for (const b of inFile) {
|
|
2191
|
+
const depth = sharedPrefixDepth(c.suitePath ?? [], b.suitePath ?? []);
|
|
2192
|
+
if (depth > bestDepth) {
|
|
2193
|
+
bestDepth = depth;
|
|
2194
|
+
blocker = b;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
if (blocker) c.blockedBy = blocker.location;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2096
2201
|
|
|
2097
2202
|
// ../core/src/test-meta.ts
|
|
2098
2203
|
var PIWI_ANNOTATION_PREFIX = "piwi:";
|
|
@@ -2462,7 +2567,7 @@ var RunSubmitter = class {
|
|
|
2462
2567
|
auth
|
|
2463
2568
|
);
|
|
2464
2569
|
this.recovery.clear();
|
|
2465
|
-
return { done: true, output: this.buildOutput(response?.
|
|
2570
|
+
return { done: true, output: this.buildOutput(response?.runId, response?.projectId, run, overallStatus) };
|
|
2466
2571
|
} catch (error) {
|
|
2467
2572
|
if (error instanceof HttpError && error.status === 401 && !auth) {
|
|
2468
2573
|
this.logAuthRequired(run.options.serverUrl);
|
|
@@ -2478,7 +2583,7 @@ var RunSubmitter = class {
|
|
|
2478
2583
|
try {
|
|
2479
2584
|
const response = await this.uploader.uploadJSON(payload, auth);
|
|
2480
2585
|
this.recovery.clear();
|
|
2481
|
-
return { done: true, output: this.buildOutput(response?.
|
|
2586
|
+
return { done: true, output: this.buildOutput(response?.runId, response?.projectId, run, overallStatus) };
|
|
2482
2587
|
} catch (error) {
|
|
2483
2588
|
if (error instanceof HttpError && error.status === 401 && !auth) {
|
|
2484
2589
|
this.logAuthRequired(run.options.serverUrl);
|
|
@@ -2505,7 +2610,7 @@ function testLocation(test) {
|
|
|
2505
2610
|
const relativeFilePath = path14.relative(process.cwd(), test.location.file).split(path14.sep).join("/");
|
|
2506
2611
|
return `${relativeFilePath}:${test.location.line}:${test.location.column}`;
|
|
2507
2612
|
}
|
|
2508
|
-
var PiwiDashboardReporter = class {
|
|
2613
|
+
var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
2509
2614
|
constructor(rawOptions = {}) {
|
|
2510
2615
|
this.testCases = [];
|
|
2511
2616
|
this.startTime = null;
|
|
@@ -2521,11 +2626,15 @@ var PiwiDashboardReporter = class {
|
|
|
2521
2626
|
this.plannedTests = [];
|
|
2522
2627
|
/** Ids of tests that actually reported via `onTestEnd`, to find the ones that never ran. */
|
|
2523
2628
|
this.reportedTestIds = /* @__PURE__ */ new Set();
|
|
2629
|
+
/** Per-test attempt history, keyed by `test.id`; snapshotted onto every attempt's payload. */
|
|
2630
|
+
this.attemptsByTest = /* @__PURE__ */ new Map();
|
|
2524
2631
|
this.runLabel = null;
|
|
2525
2632
|
this.shardInfo = null;
|
|
2526
2633
|
this.metadata = {};
|
|
2527
2634
|
this.isFullRun = true;
|
|
2528
2635
|
this.filterDetails = null;
|
|
2636
|
+
/** Configured `maxFailures` (0 = unlimited) — disambiguates an interrupted run's unrun reason. */
|
|
2637
|
+
this.maxFailures = 0;
|
|
2529
2638
|
this.streamManager = null;
|
|
2530
2639
|
/** Track suite-level setup steps (beforeAll/afterAll) not tied to any test */
|
|
2531
2640
|
this.setupSteps = [];
|
|
@@ -2573,6 +2682,7 @@ var PiwiDashboardReporter = class {
|
|
|
2573
2682
|
}
|
|
2574
2683
|
this.startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
2575
2684
|
this.playwrightVersion = config.version;
|
|
2685
|
+
this.maxFailures = config.maxFailures ?? 0;
|
|
2576
2686
|
this.logger.info(
|
|
2577
2687
|
`Starting test run for project: ${this.options.projectName} (Playwright v${this.playwrightVersion})`
|
|
2578
2688
|
);
|
|
@@ -2582,14 +2692,18 @@ var PiwiDashboardReporter = class {
|
|
|
2582
2692
|
const grep = grepRe && grepRe.source !== ".*" ? grepRe.source : void 0;
|
|
2583
2693
|
const grepInvert = grepInvertRe?.source;
|
|
2584
2694
|
const fileFilters = detectCliFileFilters();
|
|
2585
|
-
|
|
2695
|
+
const selection = readSelectionStamp();
|
|
2696
|
+
if (grep || grepInvert || fileFilters.length > 0 || selection) {
|
|
2586
2697
|
this.isFullRun = false;
|
|
2587
2698
|
this.filterDetails = {
|
|
2588
2699
|
...grep ? { grep } : {},
|
|
2589
2700
|
...grepInvert ? { grepInvert } : {},
|
|
2590
|
-
...fileFilters.length > 0 ? { files: fileFilters } : {}
|
|
2701
|
+
...fileFilters.length > 0 ? { files: fileFilters } : {},
|
|
2702
|
+
...selection ? { selection } : {}
|
|
2591
2703
|
};
|
|
2592
|
-
this.logger.info(
|
|
2704
|
+
this.logger.info(
|
|
2705
|
+
selection ? `Selection run detected (${selection.key})` : "Partial run detected (filter active)"
|
|
2706
|
+
);
|
|
2593
2707
|
}
|
|
2594
2708
|
this.metadata = this.metadataCollector.collect(config, suite, this.options);
|
|
2595
2709
|
this.plannedTests = suite.allTests();
|
|
@@ -2626,11 +2740,19 @@ var PiwiDashboardReporter = class {
|
|
|
2626
2740
|
this.streamManager.queueBeginEvent(toWireTestCase(beginEvent));
|
|
2627
2741
|
}
|
|
2628
2742
|
}
|
|
2743
|
+
static {
|
|
2744
|
+
/**
|
|
2745
|
+
* Step categories streamed live while the run executes. `pw:assert` is
|
|
2746
|
+
* excluded: it is the polling noise of `expect()`, not a step a human
|
|
2747
|
+
* watches; the meaningful readout is the `pw:expect` wrapper around it.
|
|
2748
|
+
*/
|
|
2749
|
+
this.LIVE_STEP_CATEGORIES = /* @__PURE__ */ new Set(["hook", "fixture", "pw:api", "pw:expect"]);
|
|
2750
|
+
}
|
|
2629
2751
|
/** Playwright reporter hook: called when a step (including hook/fixture) begins */
|
|
2630
2752
|
onStepBegin(test, _result, step) {
|
|
2631
|
-
if (!this.enabled || !this.streamManager
|
|
2753
|
+
if (!this.enabled || !this.streamManager) return;
|
|
2632
2754
|
const cat = step.category;
|
|
2633
|
-
if (cat
|
|
2755
|
+
if (!_PiwiDashboardReporter.LIVE_STEP_CATEGORIES.has(cat)) return;
|
|
2634
2756
|
const event = {
|
|
2635
2757
|
type: "step-begin",
|
|
2636
2758
|
title: step.title,
|
|
@@ -2644,10 +2766,9 @@ var PiwiDashboardReporter = class {
|
|
|
2644
2766
|
}
|
|
2645
2767
|
/** Playwright reporter hook: called when a step (including hook/fixture) ends */
|
|
2646
2768
|
onStepEnd(test, _result, step) {
|
|
2769
|
+
if (!this.enabled || !this.streamManager) return;
|
|
2647
2770
|
const cat = step.category;
|
|
2648
|
-
if (cat
|
|
2649
|
-
if (!this.enabled || !this.streamManager?.enabled) return;
|
|
2650
|
-
if (cat !== "hook" && cat !== "fixture") return;
|
|
2771
|
+
if (!_PiwiDashboardReporter.LIVE_STEP_CATEGORIES.has(cat)) return;
|
|
2651
2772
|
const workerIndex = workerIndexOf(_result);
|
|
2652
2773
|
const startedAt = step.startTime instanceof Date ? step.startTime.getTime() : null;
|
|
2653
2774
|
const event = {
|
|
@@ -2662,7 +2783,7 @@ var PiwiDashboardReporter = class {
|
|
|
2662
2783
|
startedAt
|
|
2663
2784
|
};
|
|
2664
2785
|
this.streamManager?.queueEvent(event);
|
|
2665
|
-
if (!test && startedAt) {
|
|
2786
|
+
if (!test && startedAt && (cat === "hook" || cat === "fixture")) {
|
|
2666
2787
|
this.setupSteps.push({
|
|
2667
2788
|
title: step.title,
|
|
2668
2789
|
category: cat,
|
|
@@ -2682,6 +2803,14 @@ var PiwiDashboardReporter = class {
|
|
|
2682
2803
|
const annotations = mergeAnnotations(test, result);
|
|
2683
2804
|
const status = classifyStatus(result.status, annotations);
|
|
2684
2805
|
const tags = collectTestTags(test);
|
|
2806
|
+
const attempts = this.attemptsByTest.get(test.id) ?? [];
|
|
2807
|
+
attempts.push({
|
|
2808
|
+
retry: result.retry,
|
|
2809
|
+
status,
|
|
2810
|
+
duration: result.duration,
|
|
2811
|
+
startedAt: result.startTime ? result.startTime.getTime() : null
|
|
2812
|
+
});
|
|
2813
|
+
this.attemptsByTest.set(test.id, attempts);
|
|
2685
2814
|
const testCase = {
|
|
2686
2815
|
type: "complete",
|
|
2687
2816
|
title: test.title,
|
|
@@ -2693,6 +2822,7 @@ var PiwiDashboardReporter = class {
|
|
|
2693
2822
|
timeout: test.timeout ?? null,
|
|
2694
2823
|
error: buildErrorText(result),
|
|
2695
2824
|
retries: result.retry,
|
|
2825
|
+
attempts: attempts.map((a) => ({ ...a })),
|
|
2696
2826
|
workerIndex: workerIndexOf(result),
|
|
2697
2827
|
shardIndex: this.shardInfo?.current ?? null,
|
|
2698
2828
|
startedAt: result.startTime ? result.startTime.getTime() : null,
|
|
@@ -2702,7 +2832,10 @@ var PiwiDashboardReporter = class {
|
|
|
2702
2832
|
suiteConfig,
|
|
2703
2833
|
testAnnotations: annotations.length ? annotations : null,
|
|
2704
2834
|
tags: tags.length ? tags : null,
|
|
2705
|
-
testMeta: collectTestMetadata(annotations)
|
|
2835
|
+
testMeta: collectTestMetadata(annotations),
|
|
2836
|
+
// An annotation-less skip reclassified to `didnotrun` is a serial-group
|
|
2837
|
+
// cascade: an earlier test failed and Playwright skipped the rest.
|
|
2838
|
+
didNotRunReason: status === "didnotrun" ? "previous-failure" : null
|
|
2706
2839
|
};
|
|
2707
2840
|
if (result.status === "failed" || result.status === "timedOut") {
|
|
2708
2841
|
const failingLine = extractFailingLine(testCase.error, test.location.file, test.location.line);
|
|
@@ -2746,6 +2879,7 @@ var PiwiDashboardReporter = class {
|
|
|
2746
2879
|
break;
|
|
2747
2880
|
}
|
|
2748
2881
|
this.testCases.push(testCase);
|
|
2882
|
+
if (status === "didnotrun") linkBlockedTests(this.testCases);
|
|
2749
2883
|
if (this.streamManager) {
|
|
2750
2884
|
this.streamManager.queueEvent(toWireTestCase(testCase));
|
|
2751
2885
|
if (this.options.liveFileUploads) this.streamManager.scheduleLiveUpload(testCase);
|
|
@@ -2756,9 +2890,10 @@ var PiwiDashboardReporter = class {
|
|
|
2756
2890
|
* (no `onTestEnd`) — typically because `maxFailures` cut the run short. These
|
|
2757
2891
|
* carry no result, so they're emitted with zero duration and no error. In
|
|
2758
2892
|
* streaming mode they're queued as complete events so the pre-finish drain
|
|
2759
|
-
* sends them alongside the rest.
|
|
2893
|
+
* sends them alongside the rest. The `reason` (global timeout / max failures /
|
|
2894
|
+
* interrupted) is resolved once from the overall run status by the caller.
|
|
2760
2895
|
*/
|
|
2761
|
-
materializeUnrunTests() {
|
|
2896
|
+
materializeUnrunTests(reason) {
|
|
2762
2897
|
for (const test of this.plannedTests) {
|
|
2763
2898
|
if (this.reportedTestIds.has(test.id)) continue;
|
|
2764
2899
|
const { suitePath, suiteConfig } = this.metadataCollector.getSuiteInfo(test);
|
|
@@ -2782,7 +2917,8 @@ var PiwiDashboardReporter = class {
|
|
|
2782
2917
|
suiteConfig,
|
|
2783
2918
|
testAnnotations: declaredAnnotations.length ? declaredAnnotations : null,
|
|
2784
2919
|
tags: tags.length ? tags : null,
|
|
2785
|
-
testMeta: collectTestMetadata(declaredAnnotations)
|
|
2920
|
+
testMeta: collectTestMetadata(declaredAnnotations),
|
|
2921
|
+
didNotRunReason: reason
|
|
2786
2922
|
};
|
|
2787
2923
|
this.testCases.push(testCase);
|
|
2788
2924
|
this.totalTests++;
|
|
@@ -2795,7 +2931,11 @@ var PiwiDashboardReporter = class {
|
|
|
2795
2931
|
/** Playwright reporter hook: called when the full test run finishes */
|
|
2796
2932
|
async onEnd(result) {
|
|
2797
2933
|
if (!this.enabled) return;
|
|
2798
|
-
|
|
2934
|
+
const unrunReason = resolveUnrunReason(result?.status, {
|
|
2935
|
+
maxFailures: this.maxFailures,
|
|
2936
|
+
failures: this.failedTests + this.timedOutTests
|
|
2937
|
+
});
|
|
2938
|
+
this.materializeUnrunTests(unrunReason);
|
|
2799
2939
|
await this.submitter.submit(
|
|
2800
2940
|
{
|
|
2801
2941
|
options: this.options,
|
|
@@ -2821,6 +2961,79 @@ var PiwiDashboardReporter = class {
|
|
|
2821
2961
|
}
|
|
2822
2962
|
};
|
|
2823
2963
|
|
|
2964
|
+
// src/internal/support/selection-client.ts
|
|
2965
|
+
function authHeaders(apiKey) {
|
|
2966
|
+
return apiKey ? { "X-API-Key": apiKey } : {};
|
|
2967
|
+
}
|
|
2968
|
+
async function resolveProjectId(options) {
|
|
2969
|
+
if (!options.project) throw new Error("No project \u2014 pass --project or set PIWI_PROJECT_NAME");
|
|
2970
|
+
if (/^\d+$/.test(options.project)) return Number(options.project);
|
|
2971
|
+
const res = await fetch(`${options.serverUrl}/api/projects/menu`, { headers: authHeaders(options.apiKey) });
|
|
2972
|
+
if (!res.ok) throw new Error(`Could not list projects (dashboard returned ${res.status})`);
|
|
2973
|
+
const body = await res.json();
|
|
2974
|
+
const match = body.items?.find((p) => p.name.toLowerCase() === options.project.toLowerCase());
|
|
2975
|
+
if (!match) throw new Error(`No project named "${options.project}" on this dashboard`);
|
|
2976
|
+
return match.id;
|
|
2977
|
+
}
|
|
2978
|
+
async function fetchResolution(options, projectId) {
|
|
2979
|
+
const params = new URLSearchParams({ format: options.format ?? "args" });
|
|
2980
|
+
if (options.pkgRunner) params.set("pkgRunner", options.pkgRunner);
|
|
2981
|
+
if (options.budgetMs != null) params.set("budgetMs", String(options.budgetMs));
|
|
2982
|
+
if (options.shard) params.set("shard", options.shard);
|
|
2983
|
+
if (options.order) params.set("order", options.order);
|
|
2984
|
+
const url = `${options.serverUrl}/api/projects/${projectId}/selections/${encodeURIComponent(options.key)}/resolve?${params}`;
|
|
2985
|
+
const res = await fetch(url, { headers: authHeaders(options.apiKey) });
|
|
2986
|
+
if (!res.ok) {
|
|
2987
|
+
const body = await res.json().catch(() => ({}));
|
|
2988
|
+
throw new Error(body.message || `Dashboard returned ${res.status} resolving "${options.key}"`);
|
|
2989
|
+
}
|
|
2990
|
+
return await res.json();
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
// src/public/selection.ts
|
|
2994
|
+
async function resolveSelection(options = {}) {
|
|
2995
|
+
const env = options.env ?? process.env;
|
|
2996
|
+
const key = options.key ?? env[PIWI_SELECTION_ENV.key];
|
|
2997
|
+
if (!key) return void 0;
|
|
2998
|
+
const serverUrl = (options.serverUrl ?? env[PIWI_ENV_KEYS.serverUrl] ?? "").replace(/\/$/, "");
|
|
2999
|
+
const apiKey = options.apiKey ?? env[PIWI_ENV_KEYS.apiKey] ?? null;
|
|
3000
|
+
const project = options.project ?? env[PIWI_ENV_KEYS.projectName] ?? "";
|
|
3001
|
+
if (!serverUrl) {
|
|
3002
|
+
if (options.strict) throw new Error("resolveSelection: no dashboard URL (set PIWI_DASHBOARD_URL)");
|
|
3003
|
+
return void 0;
|
|
3004
|
+
}
|
|
3005
|
+
const clientOptions = { serverUrl, apiKey, project, key, format: "grep", budgetMs: options.budgetMs ?? null };
|
|
3006
|
+
try {
|
|
3007
|
+
const projectId = await resolveProjectId(clientOptions);
|
|
3008
|
+
const resolution = await fetchResolution(clientOptions, projectId);
|
|
3009
|
+
if (resolution.estimate.count === 0) {
|
|
3010
|
+
if (options.strict) throw new Error(`resolveSelection: "${key}" resolved to 0 tests`);
|
|
3011
|
+
console.warn(`[Piwi Dashboard] selection "${key}" resolved to 0 tests \u2014 running the full suite`);
|
|
3012
|
+
return void 0;
|
|
3013
|
+
}
|
|
3014
|
+
env[PIWI_SELECTION_ENV.key] = resolution.key ?? key;
|
|
3015
|
+
env[PIWI_SELECTION_ENV.version] = String(resolution.version ?? 0);
|
|
3016
|
+
env[PIWI_SELECTION_ENV.hash] = resolution.resolvedHash;
|
|
3017
|
+
env[PIWI_SELECTION_ENV.count] = String(resolution.estimate.count);
|
|
3018
|
+
const args = resolution.materialization.args;
|
|
3019
|
+
const result = {
|
|
3020
|
+
key: resolution.key ?? key,
|
|
3021
|
+
version: resolution.version ?? 0,
|
|
3022
|
+
resolvedHash: resolution.resolvedHash,
|
|
3023
|
+
resolvedCount: resolution.estimate.count
|
|
3024
|
+
};
|
|
3025
|
+
if (args[0] === "--grep") result.grep = args[1];
|
|
3026
|
+
else result.files = args;
|
|
3027
|
+
return result;
|
|
3028
|
+
} catch (e) {
|
|
3029
|
+
if (options.strict) throw e;
|
|
3030
|
+
console.warn(
|
|
3031
|
+
`[Piwi Dashboard] selection "${key}" could not be resolved \u2014 running the full suite (${e.message})`
|
|
3032
|
+
);
|
|
3033
|
+
return void 0;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
|
|
2824
3037
|
// src/internal/capture/capture-fixtures.ts
|
|
2825
3038
|
var import_node_zlib = require("zlib");
|
|
2826
3039
|
|
|
@@ -3092,7 +3305,16 @@ function probeElementAttrs(el, arg) {
|
|
|
3092
3305
|
}
|
|
3093
3306
|
}
|
|
3094
3307
|
const hasLabel = !!(el.labels && el.labels.length > 0);
|
|
3095
|
-
|
|
3308
|
+
let labelText;
|
|
3309
|
+
if (includeLabelText) {
|
|
3310
|
+
labelText = null;
|
|
3311
|
+
try {
|
|
3312
|
+
const label = hasLabel ? el.labels[0] : null;
|
|
3313
|
+
labelText = (label && label.textContent || "").replace(/\s+/g, " ").trim().slice(0, 120) || null;
|
|
3314
|
+
} catch {
|
|
3315
|
+
labelText = null;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3096
3318
|
return {
|
|
3097
3319
|
tagName: el.tagName?.toLowerCase?.() ?? "unknown",
|
|
3098
3320
|
attributes: attrMap,
|
|
@@ -3796,6 +4018,7 @@ var CAPTURED_ATTRIBUTES = [
|
|
|
3796
4018
|
"alt",
|
|
3797
4019
|
"title",
|
|
3798
4020
|
"aria-label",
|
|
4021
|
+
"aria-labelledby",
|
|
3799
4022
|
"aria-level",
|
|
3800
4023
|
"role",
|
|
3801
4024
|
"type",
|
|
@@ -4387,6 +4610,43 @@ function extractAccessibleName(ariaSnapshot) {
|
|
|
4387
4610
|
if (match) return match[1];
|
|
4388
4611
|
return null;
|
|
4389
4612
|
}
|
|
4613
|
+
var FORM_FIELD_TAGS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
|
|
4614
|
+
var NAME_FROM_CONTENT_ROLES = /* @__PURE__ */ new Set([
|
|
4615
|
+
"button",
|
|
4616
|
+
"cell",
|
|
4617
|
+
"checkbox",
|
|
4618
|
+
"columnheader",
|
|
4619
|
+
"gridcell",
|
|
4620
|
+
"heading",
|
|
4621
|
+
"link",
|
|
4622
|
+
"menuitem",
|
|
4623
|
+
"menuitemcheckbox",
|
|
4624
|
+
"menuitemradio",
|
|
4625
|
+
"option",
|
|
4626
|
+
"radio",
|
|
4627
|
+
"row",
|
|
4628
|
+
"rowheader",
|
|
4629
|
+
"switch",
|
|
4630
|
+
"tab",
|
|
4631
|
+
"tooltip",
|
|
4632
|
+
"treeitem"
|
|
4633
|
+
]);
|
|
4634
|
+
var PROBE_TEXT_CAP = 80;
|
|
4635
|
+
var PROBE_LABEL_TEXT_CAP = 120;
|
|
4636
|
+
function exactAccessibleName(attrs, role) {
|
|
4637
|
+
if (attrs.attributes["aria-labelledby"]) return null;
|
|
4638
|
+
const ariaLabel = attrs.attributes["aria-label"];
|
|
4639
|
+
if (ariaLabel) return ariaLabel;
|
|
4640
|
+
if (FORM_FIELD_TAGS.has(attrs.tagName)) {
|
|
4641
|
+
const labelText = attrs.labelText;
|
|
4642
|
+
return labelText && labelText.length < PROBE_LABEL_TEXT_CAP ? labelText : null;
|
|
4643
|
+
}
|
|
4644
|
+
if (role && NAME_FROM_CONTENT_ROLES.has(role)) {
|
|
4645
|
+
const text = attrs.textContent;
|
|
4646
|
+
return text && text.length < PROBE_TEXT_CAP ? text : null;
|
|
4647
|
+
}
|
|
4648
|
+
return null;
|
|
4649
|
+
}
|
|
4390
4650
|
var NAME_BASED_METHODS = /* @__PURE__ */ new Set([
|
|
4391
4651
|
"getByText",
|
|
4392
4652
|
"getByRole",
|
|
@@ -4831,7 +5091,7 @@ function createSink() {
|
|
|
4831
5091
|
capturedLocators: [],
|
|
4832
5092
|
capturePromises: [],
|
|
4833
5093
|
failedLocators: [],
|
|
4834
|
-
|
|
5094
|
+
probedLocations: /* @__PURE__ */ new Set(),
|
|
4835
5095
|
lastActivePage: null,
|
|
4836
5096
|
testInfo: null,
|
|
4837
5097
|
stashedWebVitals: null,
|
|
@@ -5058,7 +5318,6 @@ var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
|
|
|
5058
5318
|
var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
|
|
5059
5319
|
var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
|
|
5060
5320
|
var ACTION_METHOD_SET = new Set(ACTION_METHODS);
|
|
5061
|
-
var FORM_FIELD_TAGS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
|
|
5062
5321
|
var CAPTURED_ATTRS_ARG = {
|
|
5063
5322
|
keep: [...CAPTURED_ATTRIBUTES],
|
|
5064
5323
|
tagRoles: TAG_TO_ROLE,
|
|
@@ -5067,11 +5326,32 @@ var CAPTURED_ATTRS_ARG = {
|
|
|
5067
5326
|
// special-cased logic in the probe, so add them explicitly).
|
|
5068
5327
|
roleSources: [.../* @__PURE__ */ new Set(["[role]", "input", "select", ...Object.keys(TAG_TO_ROLE)])].join(","),
|
|
5069
5328
|
// The reporter always wants ancestor-anchored alternatives (the picker's
|
|
5070
|
-
// anchors step and generateAnchoredAlternatives both need them)
|
|
5071
|
-
//
|
|
5329
|
+
// anchors step and generateAnchoredAlternatives both need them). `labelText`
|
|
5330
|
+
// is what names a form field, and the probe reads it from `el.labels` in the
|
|
5331
|
+
// same pass — so asking for it here is free and settles the accessible name
|
|
5332
|
+
// of a labeled field without a second round trip (`exactAccessibleName`).
|
|
5072
5333
|
includeStructural: true,
|
|
5073
|
-
includeLabelText:
|
|
5334
|
+
includeLabelText: true
|
|
5074
5335
|
};
|
|
5336
|
+
var PROBE_GLOBAL = "__piwiProbeElement";
|
|
5337
|
+
var PROBE_INIT_SCRIPT = `(() => {
|
|
5338
|
+
const probe = ${probeElementAttrs.toString()};
|
|
5339
|
+
const arg = ${JSON.stringify(CAPTURED_ATTRS_ARG)};
|
|
5340
|
+
globalThis[${JSON.stringify(PROBE_GLOBAL)}] = (el) => probe(el, arg);
|
|
5341
|
+
})();`;
|
|
5342
|
+
var PROBE_UNSEEDED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
5343
|
+
function probeElement(page, target) {
|
|
5344
|
+
const shipSource = () => target.evaluate(probeElementAttrs, CAPTURED_ATTRS_ARG);
|
|
5345
|
+
if (!page || PROBE_UNSEEDED_PAGES.has(page)) return shipSource();
|
|
5346
|
+
return target.evaluate((el, name) => {
|
|
5347
|
+
const seeded = globalThis[name];
|
|
5348
|
+
return typeof seeded === "function" ? seeded(el) : null;
|
|
5349
|
+
}, PROBE_GLOBAL).then((attrs) => {
|
|
5350
|
+
if (attrs) return attrs;
|
|
5351
|
+
PROBE_UNSEEDED_PAGES.add(page);
|
|
5352
|
+
return shipSource();
|
|
5353
|
+
});
|
|
5354
|
+
}
|
|
5075
5355
|
async function ariaSnapshotBestEffort(target, timeout) {
|
|
5076
5356
|
if (typeof target.ariaSnapshot !== "function") return null;
|
|
5077
5357
|
try {
|
|
@@ -5090,8 +5370,8 @@ async function ariaSnapshotBestEffort(target, timeout) {
|
|
|
5090
5370
|
}
|
|
5091
5371
|
}
|
|
5092
5372
|
}
|
|
5093
|
-
function startElementCapture(sink, target, seq, callerLocation, used) {
|
|
5094
|
-
const probe =
|
|
5373
|
+
function startElementCapture(sink, page, target, seq, callerLocation, used) {
|
|
5374
|
+
const probe = probeElement(page, target);
|
|
5095
5375
|
const settledProbe = probe.then(
|
|
5096
5376
|
() => void 0,
|
|
5097
5377
|
() => void 0
|
|
@@ -5110,8 +5390,9 @@ function startElementCapture(sink, target, seq, callerLocation, used) {
|
|
|
5110
5390
|
]);
|
|
5111
5391
|
const role = resolveAriaRole({ ...attrs, accessibleName: null });
|
|
5112
5392
|
const isFormField = FORM_FIELD_TAGS.has(attrs.tagName);
|
|
5113
|
-
const
|
|
5114
|
-
const
|
|
5393
|
+
const exactName = exactAccessibleName(attrs, role);
|
|
5394
|
+
const aria = exactName === null && (role || isFormField) ? await ariaSnapshotBestEffort(target, 500) : null;
|
|
5395
|
+
const accessibleName = exactName ?? (extractAccessibleName(aria) || approximateAccessibleName({ ...attrs, accessibleName: null }));
|
|
5115
5396
|
sink.capturedLocators[seq] = {
|
|
5116
5397
|
location: callerLocation,
|
|
5117
5398
|
used,
|
|
@@ -5131,6 +5412,7 @@ function startElementCapture(sink, target, seq, callerLocation, used) {
|
|
|
5131
5412
|
alternatives: generateAlternatives({ ...attrs, accessibleName })
|
|
5132
5413
|
};
|
|
5133
5414
|
} catch {
|
|
5415
|
+
if (callerLocation) sink.probedLocations.delete(callerLocation);
|
|
5134
5416
|
} finally {
|
|
5135
5417
|
clearTimeout(deadline);
|
|
5136
5418
|
}
|
|
@@ -5167,17 +5449,17 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
5167
5449
|
args: originArgs,
|
|
5168
5450
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
5169
5451
|
};
|
|
5170
|
-
const
|
|
5452
|
+
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
5171
5453
|
let seq = -1;
|
|
5172
|
-
if (!
|
|
5454
|
+
if (!alreadyProbed) {
|
|
5173
5455
|
seq = sink.capturedLocators.length;
|
|
5174
5456
|
sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
|
|
5175
5457
|
}
|
|
5176
5458
|
const result = await fn.apply(target, callArgs);
|
|
5177
5459
|
const matches = result?.matches;
|
|
5178
|
-
if (matches === true && !
|
|
5179
|
-
if (callerLocation) sink.
|
|
5180
|
-
startElementCapture(sink, target, seq, callerLocation, used);
|
|
5460
|
+
if (matches === true && !alreadyProbed) {
|
|
5461
|
+
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
5462
|
+
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
5181
5463
|
} else if (matches === false) {
|
|
5182
5464
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
5183
5465
|
}
|
|
@@ -5189,19 +5471,23 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
5189
5471
|
const sink = currentSink;
|
|
5190
5472
|
if (!sink) return fn.apply(target, callArgs);
|
|
5191
5473
|
sink.lastActivePage = page;
|
|
5192
|
-
const seq = sink.capturedLocators.length;
|
|
5193
5474
|
const callerLocation = captureCallerLocation();
|
|
5194
5475
|
const used = {
|
|
5195
5476
|
method: originMethod,
|
|
5196
5477
|
args: originArgs,
|
|
5197
5478
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
5198
5479
|
};
|
|
5199
|
-
sink.
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5480
|
+
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
5481
|
+
let seq = -1;
|
|
5482
|
+
if (!alreadyProbed) {
|
|
5483
|
+
seq = sink.capturedLocators.length;
|
|
5484
|
+
sink.capturedLocators.push({
|
|
5485
|
+
location: callerLocation,
|
|
5486
|
+
used,
|
|
5487
|
+
element: null,
|
|
5488
|
+
alternatives: []
|
|
5489
|
+
});
|
|
5490
|
+
}
|
|
5205
5491
|
let result;
|
|
5206
5492
|
try {
|
|
5207
5493
|
result = await fn.apply(target, callArgs);
|
|
@@ -5209,7 +5495,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
5209
5495
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
5210
5496
|
throw error;
|
|
5211
5497
|
}
|
|
5212
|
-
|
|
5498
|
+
if (!alreadyProbed) {
|
|
5499
|
+
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
5500
|
+
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
5501
|
+
}
|
|
5213
5502
|
return result;
|
|
5214
5503
|
};
|
|
5215
5504
|
}
|
|
@@ -5242,6 +5531,9 @@ function instrumentPage(page) {
|
|
|
5242
5531
|
return wrapLocator(page, original(...args), method, args);
|
|
5243
5532
|
};
|
|
5244
5533
|
}
|
|
5534
|
+
if (typeof page.on === "function") {
|
|
5535
|
+
page.on("framenavigated", () => PROBE_UNSEEDED_PAGES.delete(page));
|
|
5536
|
+
}
|
|
5245
5537
|
}
|
|
5246
5538
|
page.on("console", (msg) => {
|
|
5247
5539
|
const sink = currentSink;
|
|
@@ -5306,6 +5598,10 @@ function instrumentPage(page) {
|
|
|
5306
5598
|
function instrumentContext(context) {
|
|
5307
5599
|
if (!context || INSTRUMENTED_CONTEXTS.has(context)) return;
|
|
5308
5600
|
INSTRUMENTED_CONTEXTS.add(context);
|
|
5601
|
+
if (process.env.PIWI_CAPTURE_LOCATORS !== "false" && typeof context.addInitScript === "function") {
|
|
5602
|
+
void Promise.resolve(context.addInitScript({ content: PROBE_INIT_SCRIPT })).catch(() => {
|
|
5603
|
+
});
|
|
5604
|
+
}
|
|
5309
5605
|
const originalNewPage = context.newPage.bind(context);
|
|
5310
5606
|
context.newPage = async (...args) => {
|
|
5311
5607
|
const page = await originalNewPage(...args);
|
|
@@ -6279,5 +6575,6 @@ var index_default = PiwiDashboardReporter;
|
|
|
6279
6575
|
extendPiwiFixtures,
|
|
6280
6576
|
piwiAiFixtures,
|
|
6281
6577
|
piwiFixtures,
|
|
6578
|
+
resolveSelection,
|
|
6282
6579
|
wrapConfig
|
|
6283
6580
|
});
|