@piwitests/reporter 0.59.0 → 0.61.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 +1520 -1307
- package/dist/global-setup-module.js +203 -63
- package/dist/index.d.ts +36 -2
- package/dist/index.js +1973 -584
- package/dist/internal/capture/attachments.d.ts +3 -0
- package/dist/internal/capture/attachments.js +4 -1
- package/dist/internal/capture/capture-fixtures.js +1363 -373
- package/dist/internal/capture/pick-on-failure.d.ts +6 -3
- package/dist/internal/capture/pick-on-failure.js +49 -30
- package/package.json +5 -5
- package/templates/skills/run-the-right-tests/SKILL.md +19 -8
- package/templates/skills/setup-piwi/SKILL.md +4 -1
|
@@ -35,7 +35,7 @@ __export(global_setup_module_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(global_setup_module_exports);
|
|
36
36
|
|
|
37
37
|
// src/public/global-setup.ts
|
|
38
|
-
var
|
|
38
|
+
var path8 = __toESM(require("path"));
|
|
39
39
|
|
|
40
40
|
// src/internal/support/errors.ts
|
|
41
41
|
function errorMessage(error) {
|
|
@@ -44,7 +44,10 @@ function errorMessage(error) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// src/public/global-setup.ts
|
|
47
|
-
var
|
|
47
|
+
var fs7 = __toESM(require("fs"));
|
|
48
|
+
|
|
49
|
+
// src/internal/config/env.ts
|
|
50
|
+
var path2 = __toESM(require("path"));
|
|
48
51
|
|
|
49
52
|
// src/internal/config/desktop.ts
|
|
50
53
|
var fs = __toESM(require("fs"));
|
|
@@ -53,6 +56,14 @@ var path = __toESM(require("path"));
|
|
|
53
56
|
function defaultDesktopConfigPath() {
|
|
54
57
|
return path.join(os.homedir(), ".piwi", "desktop.json");
|
|
55
58
|
}
|
|
59
|
+
function processAlive(pid) {
|
|
60
|
+
try {
|
|
61
|
+
process.kill(pid, 0);
|
|
62
|
+
return true;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return error.code === "EPERM";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
56
67
|
function readDesktopConfig(filePath) {
|
|
57
68
|
try {
|
|
58
69
|
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
@@ -60,12 +71,75 @@ function readDesktopConfig(filePath) {
|
|
|
60
71
|
const token = parsed?.token;
|
|
61
72
|
if (typeof url !== "string" || !url) return null;
|
|
62
73
|
if (typeof token !== "string" || !token) return null;
|
|
74
|
+
if (Number.isInteger(parsed?.pid) && parsed.pid > 0 && !processAlive(parsed.pid)) return null;
|
|
63
75
|
return { url, token };
|
|
64
76
|
} catch {
|
|
65
77
|
return null;
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
80
|
|
|
81
|
+
// src/internal/config/dotenv.ts
|
|
82
|
+
var fs2 = __toESM(require("fs"));
|
|
83
|
+
|
|
84
|
+
// ../core/src/dotenv.ts
|
|
85
|
+
function parseDotEnv(text) {
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
88
|
+
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(raw);
|
|
89
|
+
if (!m) continue;
|
|
90
|
+
let value = m[2];
|
|
91
|
+
const quote = value[0];
|
|
92
|
+
if ((quote === '"' || quote === "'") && value.length > 1 && value.endsWith(quote)) {
|
|
93
|
+
value = value.slice(1, -1);
|
|
94
|
+
if (quote === '"') value = value.replace(/\\n/g, "\n").replace(/\\"/g, '"');
|
|
95
|
+
} else {
|
|
96
|
+
value = value.replace(/\s+#.*$/, "");
|
|
97
|
+
}
|
|
98
|
+
out[m[1]] = value;
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/internal/config/dotenv.ts
|
|
104
|
+
function readDotEnv(filePath) {
|
|
105
|
+
try {
|
|
106
|
+
return parseDotEnv(fs2.readFileSync(filePath, "utf-8"));
|
|
107
|
+
} catch {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/internal/support/ci.ts
|
|
113
|
+
function detectCi(env) {
|
|
114
|
+
return Boolean(
|
|
115
|
+
env.JENKINS_URL || env.GITHUB_ACTIONS || env.GITLAB_CI || env.CIRCLECI || env.TRAVIS || env.TF_BUILD || env.BITBUCKET_BUILD_NUMBER || env.TEAMCITY_VERSION || env.CODEBUILD_BUILD_ID || env.bamboo_buildKey || env.CI && env.CI.toLowerCase() !== "false" && env.CI !== "0"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
function detectCiIds(env) {
|
|
119
|
+
if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID) {
|
|
120
|
+
const run = env.GITHUB_RUN_ATTEMPT ? `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` : env.GITHUB_RUN_ID;
|
|
121
|
+
return { run, job: env.GITHUB_JOB || null };
|
|
122
|
+
}
|
|
123
|
+
if (env.GITLAB_CI && env.CI_PIPELINE_ID) return { run: env.CI_PIPELINE_ID, job: env.CI_JOB_ID || null };
|
|
124
|
+
if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID) return { run: env.CIRCLE_WORKFLOW_ID, job: env.CIRCLE_BUILD_NUM || null };
|
|
125
|
+
if (env.TRAVIS && env.TRAVIS_BUILD_ID) return { run: env.TRAVIS_BUILD_ID, job: env.TRAVIS_JOB_ID || null };
|
|
126
|
+
if (env.TF_BUILD && env.BUILD_BUILDID) return { run: env.BUILD_BUILDID, job: env.SYSTEM_JOBID || null };
|
|
127
|
+
if (env.JENKINS_URL && env.BUILD_ID) return { run: env.BUILD_ID, job: null };
|
|
128
|
+
if (env.BUILDKITE_BUILD_ID) return { run: env.BUILDKITE_BUILD_ID, job: env.BUILDKITE_JOB_ID || null };
|
|
129
|
+
if (env.TEAMCITY_BUILD_ID) return { run: env.TEAMCITY_BUILD_ID, job: null };
|
|
130
|
+
if (env.BITBUCKET_BUILD_NUMBER) return { run: env.BITBUCKET_BUILD_NUMBER, job: env.BITBUCKET_STEP_UUID || null };
|
|
131
|
+
if (env.SEMAPHORE_WORKFLOW_ID) return { run: env.SEMAPHORE_WORKFLOW_ID, job: env.SEMAPHORE_JOB_ID || null };
|
|
132
|
+
if (env.APPVEYOR_BUILD_ID) return { run: env.APPVEYOR_BUILD_ID, job: env.APPVEYOR_JOB_ID || null };
|
|
133
|
+
if (env.DRONE_BUILD_NUMBER) return { run: env.DRONE_BUILD_NUMBER, job: null };
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
function resolveRunLabel(configured, sharded) {
|
|
137
|
+
if (configured) return configured;
|
|
138
|
+
const ci = detectCiIds(process.env);
|
|
139
|
+
if (!ci) return null;
|
|
140
|
+
return !sharded && ci.job ? `${ci.run}|${ci.job}` : ci.run;
|
|
141
|
+
}
|
|
142
|
+
|
|
69
143
|
// src/internal/config/env.ts
|
|
70
144
|
var DEFAULTS = {
|
|
71
145
|
projectName: "default-project",
|
|
@@ -102,6 +176,7 @@ var DEFAULTS = {
|
|
|
102
176
|
};
|
|
103
177
|
var PIWI_ENV_KEYS = {
|
|
104
178
|
serverUrl: "PIWI_DASHBOARD_URL",
|
|
179
|
+
desktopApp: "PIWI_DESKTOP_APP",
|
|
105
180
|
projectName: "PIWI_PROJECT_NAME",
|
|
106
181
|
verbose: "PIWI_VERBOSE",
|
|
107
182
|
apiKey: "PIWI_API_KEY",
|
|
@@ -127,6 +202,7 @@ var PIWI_ENV_KEYS = {
|
|
|
127
202
|
codeReachRoots: "PIWI_CODE_REACH_ROOTS",
|
|
128
203
|
coverageBrowser: "PIWI_COVERAGE_BROWSER",
|
|
129
204
|
coverageServer: "PIWI_COVERAGE_SERVER",
|
|
205
|
+
coverageCss: "PIWI_COVERAGE_CSS",
|
|
130
206
|
externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
|
|
131
207
|
externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
|
|
132
208
|
captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
|
|
@@ -188,8 +264,18 @@ var ENV_FALLBACK_SPECS = [
|
|
|
188
264
|
{ option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" },
|
|
189
265
|
{ option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
|
|
190
266
|
];
|
|
267
|
+
var DOTENV_OPTIONS = ["serverUrl", "apiKey", "projectName"];
|
|
268
|
+
var DESKTOP_APP_MODES = ["prefer", "fallback", "off"];
|
|
269
|
+
function desktopAppMode(option, envValue) {
|
|
270
|
+
const fromEnv = envValue?.trim().toLowerCase();
|
|
271
|
+
if (DESKTOP_APP_MODES.includes(fromEnv)) return fromEnv;
|
|
272
|
+
if (DESKTOP_APP_MODES.includes(option)) return option;
|
|
273
|
+
return "fallback";
|
|
274
|
+
}
|
|
191
275
|
var desktopDiscovered = false;
|
|
192
|
-
|
|
276
|
+
var desktopReplacedServerUrl = null;
|
|
277
|
+
var dotEnvUse = null;
|
|
278
|
+
function resolveOptions(raw, configDir) {
|
|
193
279
|
const env = process.env;
|
|
194
280
|
const mergedRaw = { ...raw };
|
|
195
281
|
for (const spec of ENV_FALLBACK_SPECS) {
|
|
@@ -201,24 +287,49 @@ function resolveOptions(raw) {
|
|
|
201
287
|
mergedRaw[spec.option] = spec.kind === "number" ? Number(value) : value;
|
|
202
288
|
}
|
|
203
289
|
}
|
|
290
|
+
dotEnvUse = null;
|
|
291
|
+
if (configDir) {
|
|
292
|
+
const file = path2.join(configDir, ".env");
|
|
293
|
+
const dotEnv = readDotEnv(file);
|
|
294
|
+
const keys = [];
|
|
295
|
+
for (const option of DOTENV_OPTIONS) {
|
|
296
|
+
const value = dotEnv[PIWI_ENV_KEYS[option]];
|
|
297
|
+
if (mergedRaw[option] !== void 0 || !value) continue;
|
|
298
|
+
mergedRaw[option] = value;
|
|
299
|
+
keys.push(PIWI_ENV_KEYS[option]);
|
|
300
|
+
}
|
|
301
|
+
if (keys.length > 0) dotEnvUse = { file, keys };
|
|
302
|
+
}
|
|
204
303
|
desktopDiscovered = false;
|
|
304
|
+
desktopReplacedServerUrl = null;
|
|
305
|
+
const mode = desktopAppMode(mergedRaw.desktopApp, env[PIWI_ENV_KEYS.desktopApp]);
|
|
306
|
+
mergedRaw.desktopApp = mode;
|
|
205
307
|
const serverUrlUnset = mergedRaw.serverUrl === void 0;
|
|
206
308
|
const apiKeyUnset = mergedRaw.apiKey === void 0 || mergedRaw.apiKey === null;
|
|
207
|
-
|
|
309
|
+
const nothingConfigured = serverUrlUnset && apiKeyUnset;
|
|
310
|
+
const preferLocally = mode === "prefer" && !detectCi(env);
|
|
311
|
+
if (mode !== "off" && (nothingConfigured || preferLocally)) {
|
|
208
312
|
const desktop = readDesktopConfig(env[PIWI_DESKTOP_CONFIG_ENV] || defaultDesktopConfigPath());
|
|
209
313
|
if (desktop) {
|
|
314
|
+
if (!serverUrlUnset && mergedRaw.serverUrl !== desktop.url) desktopReplacedServerUrl = mergedRaw.serverUrl;
|
|
210
315
|
mergedRaw.serverUrl = desktop.url;
|
|
211
316
|
mergedRaw.apiKey = desktop.token;
|
|
317
|
+
mergedRaw.username = null;
|
|
318
|
+
mergedRaw.password = null;
|
|
212
319
|
desktopDiscovered = true;
|
|
213
320
|
}
|
|
214
321
|
}
|
|
215
322
|
const coverage = { ...mergedRaw.coverage };
|
|
216
323
|
const coverageBrowser = env[PIWI_ENV_KEYS.coverageBrowser];
|
|
217
324
|
const coverageServer = env[PIWI_ENV_KEYS.coverageServer];
|
|
325
|
+
const coverageCss = env[PIWI_ENV_KEYS.coverageCss];
|
|
218
326
|
if (coverage.browser === void 0 && coverageBrowser)
|
|
219
327
|
coverage.browser = coverageBrowser;
|
|
220
328
|
if (coverage.server === void 0 && coverageServer) coverage.server = coverageServer;
|
|
221
|
-
if (coverage.
|
|
329
|
+
if (coverage.css === void 0 && coverageCss) coverage.css = coverageCss;
|
|
330
|
+
if (coverage.browser !== void 0 || coverage.server !== void 0 || coverage.css !== void 0) {
|
|
331
|
+
mergedRaw.coverage = coverage;
|
|
332
|
+
}
|
|
222
333
|
const externalId = { ...mergedRaw.externalId };
|
|
223
334
|
const externalIdPattern = env[PIWI_ENV_KEYS.externalIdPattern];
|
|
224
335
|
const externalIdOnDuplicate = env[PIWI_ENV_KEYS.externalIdOnDuplicate];
|
|
@@ -528,32 +639,6 @@ function computeInstanceId(projectName, runLabel) {
|
|
|
528
639
|
return crypto.createHash("sha256").update(seed).digest("hex").slice(0, 16);
|
|
529
640
|
}
|
|
530
641
|
|
|
531
|
-
// src/internal/support/ci.ts
|
|
532
|
-
function detectCiIds(env) {
|
|
533
|
-
if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID) {
|
|
534
|
-
const run = env.GITHUB_RUN_ATTEMPT ? `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` : env.GITHUB_RUN_ID;
|
|
535
|
-
return { run, job: env.GITHUB_JOB || null };
|
|
536
|
-
}
|
|
537
|
-
if (env.GITLAB_CI && env.CI_PIPELINE_ID) return { run: env.CI_PIPELINE_ID, job: env.CI_JOB_ID || null };
|
|
538
|
-
if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID) return { run: env.CIRCLE_WORKFLOW_ID, job: env.CIRCLE_BUILD_NUM || null };
|
|
539
|
-
if (env.TRAVIS && env.TRAVIS_BUILD_ID) return { run: env.TRAVIS_BUILD_ID, job: env.TRAVIS_JOB_ID || null };
|
|
540
|
-
if (env.TF_BUILD && env.BUILD_BUILDID) return { run: env.BUILD_BUILDID, job: env.SYSTEM_JOBID || null };
|
|
541
|
-
if (env.JENKINS_URL && env.BUILD_ID) return { run: env.BUILD_ID, job: null };
|
|
542
|
-
if (env.BUILDKITE_BUILD_ID) return { run: env.BUILDKITE_BUILD_ID, job: env.BUILDKITE_JOB_ID || null };
|
|
543
|
-
if (env.TEAMCITY_BUILD_ID) return { run: env.TEAMCITY_BUILD_ID, job: null };
|
|
544
|
-
if (env.BITBUCKET_BUILD_NUMBER) return { run: env.BITBUCKET_BUILD_NUMBER, job: env.BITBUCKET_STEP_UUID || null };
|
|
545
|
-
if (env.SEMAPHORE_WORKFLOW_ID) return { run: env.SEMAPHORE_WORKFLOW_ID, job: env.SEMAPHORE_JOB_ID || null };
|
|
546
|
-
if (env.APPVEYOR_BUILD_ID) return { run: env.APPVEYOR_BUILD_ID, job: env.APPVEYOR_JOB_ID || null };
|
|
547
|
-
if (env.DRONE_BUILD_NUMBER) return { run: env.DRONE_BUILD_NUMBER, job: null };
|
|
548
|
-
return null;
|
|
549
|
-
}
|
|
550
|
-
function resolveRunLabel(configured, sharded) {
|
|
551
|
-
if (configured) return configured;
|
|
552
|
-
const ci = detectCiIds(process.env);
|
|
553
|
-
if (!ci) return null;
|
|
554
|
-
return !sharded && ci.job ? `${ci.run}|${ci.job}` : ci.run;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
642
|
// src/internal/support/shard-info.ts
|
|
558
643
|
function parseShardSpec(raw) {
|
|
559
644
|
const match = raw?.trim().match(/^(\d+)\s*\/\s*(\d+)$/);
|
|
@@ -572,9 +657,9 @@ function resolveShardInfo(config, env = process.env) {
|
|
|
572
657
|
var import_node_child_process = require("child_process");
|
|
573
658
|
|
|
574
659
|
// src/internal/support/test-paths.ts
|
|
575
|
-
var
|
|
660
|
+
var path3 = __toESM(require("path"));
|
|
576
661
|
function configDirFromConfig(config) {
|
|
577
|
-
if (config?.configFile) return
|
|
662
|
+
if (config?.configFile) return path3.dirname(config.configFile);
|
|
578
663
|
if (config?.rootDir) return config.rootDir;
|
|
579
664
|
return process.cwd();
|
|
580
665
|
}
|
|
@@ -609,32 +694,32 @@ function normalizeRef(ref) {
|
|
|
609
694
|
}
|
|
610
695
|
|
|
611
696
|
// src/internal/support/setup-file.ts
|
|
612
|
-
var
|
|
697
|
+
var path4 = __toESM(require("path"));
|
|
613
698
|
var os3 = __toESM(require("os"));
|
|
614
|
-
var
|
|
699
|
+
var fs3 = __toESM(require("fs"));
|
|
615
700
|
function getSetupFilePath(projectName) {
|
|
616
|
-
return
|
|
701
|
+
return path4.join(os3.tmpdir(), `piwi-dashboard-setup-${hashForProject(projectName)}.json`);
|
|
617
702
|
}
|
|
618
703
|
|
|
619
704
|
// src/internal/support/aria-sampling.ts
|
|
620
|
-
var
|
|
705
|
+
var path5 = __toESM(require("path"));
|
|
621
706
|
var os4 = __toESM(require("os"));
|
|
622
|
-
var
|
|
707
|
+
var fs4 = __toESM(require("fs"));
|
|
623
708
|
function ariaSampleIdentity(filePath, title) {
|
|
624
709
|
return `${filePath}\0${title}`;
|
|
625
710
|
}
|
|
626
711
|
function getAriaSampleFilePath(projectName) {
|
|
627
|
-
return
|
|
712
|
+
return path5.join(os4.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
628
713
|
}
|
|
629
714
|
function writeAriaSampleFile(projectName, identities) {
|
|
630
715
|
try {
|
|
631
|
-
|
|
716
|
+
fs4.writeFileSync(getAriaSampleFilePath(projectName), JSON.stringify({ projectName, identities }));
|
|
632
717
|
} catch {
|
|
633
718
|
}
|
|
634
719
|
}
|
|
635
720
|
function clearAriaSampleFile(projectName) {
|
|
636
721
|
try {
|
|
637
|
-
|
|
722
|
+
fs4.rmSync(getAriaSampleFilePath(projectName), { force: true });
|
|
638
723
|
} catch {
|
|
639
724
|
}
|
|
640
725
|
}
|
|
@@ -656,6 +741,7 @@ function isListMode(argv = process.argv) {
|
|
|
656
741
|
}
|
|
657
742
|
|
|
658
743
|
// src/internal/support/base-url-check.ts
|
|
744
|
+
var net = __toESM(require("net"));
|
|
659
745
|
var BASE_URL_CHECK_ATTEMPTS = 3;
|
|
660
746
|
var BASE_URL_CHECK_TIMEOUT_MS = 1e4;
|
|
661
747
|
var BASE_URL_CHECK_DELAY_MS = 2e3;
|
|
@@ -677,9 +763,9 @@ function cliProjectNames(argv = process.argv) {
|
|
|
677
763
|
}
|
|
678
764
|
return names;
|
|
679
765
|
}
|
|
680
|
-
function wildcard(pattern) {
|
|
766
|
+
function wildcard(pattern, flags = "i") {
|
|
681
767
|
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
682
|
-
return new RegExp(`^${escaped.join(".*")}$`,
|
|
768
|
+
return new RegExp(`^${escaped.join(".*")}$`, flags);
|
|
683
769
|
}
|
|
684
770
|
function selectedProjects(projects, argv = process.argv, filtered) {
|
|
685
771
|
let chosen;
|
|
@@ -689,7 +775,7 @@ function selectedProjects(projects, argv = process.argv, filtered) {
|
|
|
689
775
|
} else {
|
|
690
776
|
const names = cliProjectNames(argv);
|
|
691
777
|
if (!names || names.length === 0) return projects;
|
|
692
|
-
const patterns = names.map(wildcard);
|
|
778
|
+
const patterns = names.map((name) => wildcard(name));
|
|
693
779
|
chosen = new Set(projects.filter((p) => patterns.some((re) => re.test(p.name ?? ""))));
|
|
694
780
|
}
|
|
695
781
|
if (!argv.includes("--no-deps")) {
|
|
@@ -711,23 +797,58 @@ function asProxy(value) {
|
|
|
711
797
|
const proxy = value;
|
|
712
798
|
return proxy && typeof proxy === "object" && typeof proxy.server === "string" ? proxy : void 0;
|
|
713
799
|
}
|
|
714
|
-
function
|
|
800
|
+
function exposeNetworkCovers(rule, url) {
|
|
801
|
+
if (!rule) return false;
|
|
802
|
+
let parsed;
|
|
803
|
+
try {
|
|
804
|
+
parsed = new URL(url);
|
|
805
|
+
} catch {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
const host = parsed.hostname;
|
|
809
|
+
const port = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
|
|
810
|
+
return rule.split(",").some((token) => {
|
|
811
|
+
const match = token.match(/^(.*?)(?::(\d+))?$/);
|
|
812
|
+
if (!match) return false;
|
|
813
|
+
if (match[2] !== void 0 && Number(match[2]) !== port) return false;
|
|
814
|
+
const tokenHost = match[1];
|
|
815
|
+
if (tokenHost === "<loopback>") {
|
|
816
|
+
return host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1" || host === "[::1]";
|
|
817
|
+
}
|
|
818
|
+
if (tokenHost === "*") return true;
|
|
819
|
+
if (net.isIP(tokenHost)) return host === tokenHost;
|
|
820
|
+
if (net.isIP(host)) return false;
|
|
821
|
+
return wildcard(tokenHost.startsWith(".") ? `*${tokenHost}` : tokenHost, "").test(host);
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
function reachedRemotely(use, url, env) {
|
|
825
|
+
if (use?.connectOptions === void 0) {
|
|
826
|
+
return !!env.PW_TEST_CONNECT_WS_ENDPOINT && !exposeNetworkCovers(env.PW_TEST_CONNECT_EXPOSE_NETWORK, url);
|
|
827
|
+
}
|
|
828
|
+
const connect = use.connectOptions;
|
|
829
|
+
if (!connect || typeof connect !== "object" || typeof connect.wsEndpoint !== "string") return false;
|
|
830
|
+
return !exposeNetworkCovers(typeof connect.exposeNetwork === "string" ? connect.exposeNetwork : void 0, url);
|
|
831
|
+
}
|
|
832
|
+
function baseUrlTargets(config, argv = process.argv, env = process.env) {
|
|
715
833
|
const projects = config.projects?.length ? config.projects : [{ name: "", use: config.use }];
|
|
716
834
|
const filtered = config.projects?.length ? config.filteredProjects : void 0;
|
|
717
835
|
const targets = /* @__PURE__ */ new Map();
|
|
718
836
|
for (const project of selectedProjects(projects, argv, filtered)) {
|
|
719
837
|
const url = project.use?.baseURL;
|
|
720
838
|
if (typeof url !== "string" || !/^https?:\/\//i.test(url)) continue;
|
|
839
|
+
const remoteBrowser = reachedRemotely(project.use, url, env);
|
|
721
840
|
const target = targets.get(url);
|
|
722
841
|
if (target) {
|
|
723
842
|
if (project.name) target.projects.push(project.name);
|
|
843
|
+
target.remoteBrowser ||= remoteBrowser;
|
|
724
844
|
continue;
|
|
725
845
|
}
|
|
726
846
|
targets.set(url, {
|
|
727
847
|
url,
|
|
728
848
|
projects: project.name ? [project.name] : [],
|
|
729
849
|
ignoreHTTPSErrors: project.use?.ignoreHTTPSErrors === true,
|
|
730
|
-
proxy: asProxy(project.use?.proxy)
|
|
850
|
+
proxy: asProxy(project.use?.proxy),
|
|
851
|
+
remoteBrowser
|
|
731
852
|
});
|
|
732
853
|
}
|
|
733
854
|
return [...targets.values()];
|
|
@@ -768,14 +889,21 @@ async function checkBaseUrl(target, options = {}) {
|
|
|
768
889
|
}
|
|
769
890
|
return `${reason} (${attempts} ${attempts === 1 ? "attempt" : "attempts"})`;
|
|
770
891
|
}
|
|
892
|
+
function remoteBrowserNote(target) {
|
|
893
|
+
if (!target.remoteBrowser) return null;
|
|
894
|
+
const { host } = new URL(target.url);
|
|
895
|
+
return `The check ran from this machine's network, but a project's remote browser reaches ${host} from its own, since its exposeNetwork rule does not cover that address.`;
|
|
896
|
+
}
|
|
771
897
|
async function checkBaseUrls(config, options = {}) {
|
|
772
|
-
const targets = baseUrlTargets(config, options.argv);
|
|
898
|
+
const targets = baseUrlTargets(config, options.argv, options.env);
|
|
773
899
|
const reasons = await Promise.all(targets.map((target) => checkBaseUrl(target, options)));
|
|
774
900
|
const down = targets.flatMap((target, i) => reasons[i] ? [{ target, reason: reasons[i] }] : []);
|
|
775
901
|
if (down.length === 0) return targets;
|
|
776
902
|
const lines = down.map(({ target, reason }) => {
|
|
777
903
|
const used = target.projects.length > 0 ? ` (${target.projects.join(", ")})` : "";
|
|
778
|
-
|
|
904
|
+
const note = remoteBrowserNote(target);
|
|
905
|
+
return ` ${target.url}${used}: ${reason}${note ? `
|
|
906
|
+
${note}` : ""}`;
|
|
779
907
|
});
|
|
780
908
|
const message = [
|
|
781
909
|
`[Piwi Dashboard] The app under test did not answer, so no test ran:`,
|
|
@@ -788,8 +916,8 @@ async function checkBaseUrls(config, options = {}) {
|
|
|
788
916
|
}
|
|
789
917
|
|
|
790
918
|
// src/internal/manifest/collect.ts
|
|
791
|
-
var
|
|
792
|
-
var
|
|
919
|
+
var fs5 = __toESM(require("fs"));
|
|
920
|
+
var path6 = __toESM(require("path"));
|
|
793
921
|
|
|
794
922
|
// src/internal/transport/backend-request.ts
|
|
795
923
|
var http2 = __toESM(require("http"));
|
|
@@ -823,10 +951,10 @@ function hasInstrumentationHeader(headers) {
|
|
|
823
951
|
return INSTRUMENTATION_HEADERS.some((h) => get(h) != null && get(h) !== "");
|
|
824
952
|
}
|
|
825
953
|
function readCommittedManifest(configDir) {
|
|
826
|
-
const file =
|
|
954
|
+
const file = path6.join(configDir, COMMITTED_MANIFEST_FILE);
|
|
827
955
|
try {
|
|
828
|
-
if (!
|
|
829
|
-
return parseManifestJson(
|
|
956
|
+
if (!fs5.existsSync(file)) return null;
|
|
957
|
+
return parseManifestJson(fs5.readFileSync(file, "utf8"));
|
|
830
958
|
} catch {
|
|
831
959
|
return null;
|
|
832
960
|
}
|
|
@@ -836,8 +964,8 @@ var MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
|
|
|
836
964
|
function instrumentationManifestUrls(baseUrl) {
|
|
837
965
|
const base = new URL(baseUrl);
|
|
838
966
|
const root = new URL(INSTRUMENTATION_MANIFEST_PATH, base).toString();
|
|
839
|
-
const
|
|
840
|
-
return
|
|
967
|
+
const path9 = base.pathname.replace(/\/+$/, "");
|
|
968
|
+
return path9 ? [new URL(`${path9}${INSTRUMENTATION_MANIFEST_PATH}`, base).toString(), root] : [root];
|
|
841
969
|
}
|
|
842
970
|
async function fetchInstrumentationManifest(baseUrl, fetchImpl = fetch) {
|
|
843
971
|
try {
|
|
@@ -870,8 +998,8 @@ async function uploadManifest(httpClient, auth, projectId, upload, logger) {
|
|
|
870
998
|
|
|
871
999
|
// src/internal/collect/source-checksums.ts
|
|
872
1000
|
var crypto2 = __toESM(require("crypto"));
|
|
873
|
-
var
|
|
874
|
-
var
|
|
1001
|
+
var fs6 = __toESM(require("fs"));
|
|
1002
|
+
var path7 = __toESM(require("path"));
|
|
875
1003
|
|
|
876
1004
|
// src/internal/collect/server-coverage.ts
|
|
877
1005
|
var DEFAULT_COLLECTOR_LIMITS = {
|
|
@@ -893,14 +1021,14 @@ function baseUrlsFromConfig(config) {
|
|
|
893
1021
|
// src/public/global-setup.ts
|
|
894
1022
|
function createGlobalSetup(options, userSetup) {
|
|
895
1023
|
return async function globalSetupFn(config) {
|
|
896
|
-
const piwiReporterPath =
|
|
1024
|
+
const piwiReporterPath = path8.resolve(__dirname, "./index.js");
|
|
897
1025
|
let inlineReporterOptions = {};
|
|
898
1026
|
if (Array.isArray(config?.reporter)) {
|
|
899
1027
|
for (const r of config.reporter) {
|
|
900
1028
|
if (!Array.isArray(r) || typeof r[0] !== "string") continue;
|
|
901
1029
|
const isPiwi = r[0].toLowerCase().includes("piwi") || (() => {
|
|
902
1030
|
try {
|
|
903
|
-
return
|
|
1031
|
+
return path8.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
904
1032
|
} catch {
|
|
905
1033
|
return false;
|
|
906
1034
|
}
|
|
@@ -911,7 +1039,10 @@ function createGlobalSetup(options, userSetup) {
|
|
|
911
1039
|
}
|
|
912
1040
|
}
|
|
913
1041
|
}
|
|
914
|
-
const opts = resolveOptions(
|
|
1042
|
+
const opts = resolveOptions(
|
|
1043
|
+
{ ...inlineReporterOptions, ...options },
|
|
1044
|
+
configDirFromConfig(config)
|
|
1045
|
+
);
|
|
915
1046
|
const logger = new Logger(opts.verbose ?? false);
|
|
916
1047
|
if (isUiMode()) {
|
|
917
1048
|
logger.debug("UI mode detected \u2014 skipping run registration.");
|
|
@@ -925,7 +1056,16 @@ function createGlobalSetup(options, userSetup) {
|
|
|
925
1056
|
}
|
|
926
1057
|
if (opts.checkBaseUrl === true) {
|
|
927
1058
|
const checked = await checkBaseUrls(config ?? {});
|
|
928
|
-
for (const target of checked)
|
|
1059
|
+
for (const target of checked) {
|
|
1060
|
+
const note = remoteBrowserNote(target);
|
|
1061
|
+
if (note) {
|
|
1062
|
+
logger.info(
|
|
1063
|
+
`Base URL answered: ${target.url}. ${note} A navigation failure from that browser can mean its machine cannot reach the app, not that the app is down.`
|
|
1064
|
+
);
|
|
1065
|
+
} else {
|
|
1066
|
+
logger.debug(`Base URL answered: ${target.url}`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
929
1069
|
}
|
|
930
1070
|
if (opts.enabled === false || !opts.serverUrl) {
|
|
931
1071
|
logger.info("Not enabled \u2014 set PIWI_DASHBOARD_URL or serverUrl to enable.");
|
|
@@ -936,7 +1076,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
936
1076
|
if (!Array.isArray(r) || typeof r[0] !== "string") return false;
|
|
937
1077
|
if (r[0].toLowerCase().includes("piwi")) return true;
|
|
938
1078
|
try {
|
|
939
|
-
return
|
|
1079
|
+
return path8.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
940
1080
|
} catch {
|
|
941
1081
|
return false;
|
|
942
1082
|
}
|
|
@@ -966,7 +1106,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
966
1106
|
auth
|
|
967
1107
|
);
|
|
968
1108
|
if (response?.runId && response?.setupToken) {
|
|
969
|
-
|
|
1109
|
+
fs7.writeFileSync(
|
|
970
1110
|
getSetupFilePath(opts.projectName),
|
|
971
1111
|
JSON.stringify({
|
|
972
1112
|
runId: response.runId,
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,18 @@ interface PiwiDashboardOptions {
|
|
|
19
19
|
enabled?: boolean;
|
|
20
20
|
/** URL of the Piwi Dashboard server */
|
|
21
21
|
serverUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* When results go to the Piwi desktop app running on this machine instead.
|
|
24
|
+
* `'fallback'` uses the app only when nothing sets a server: no `serverUrl`,
|
|
25
|
+
* no `apiKey`, and neither `PIWI_DASHBOARD_URL` nor `PIWI_API_KEY`.
|
|
26
|
+
* `'prefer'` uses it whenever it is running and the run is not on CI, even
|
|
27
|
+
* with `serverUrl` set, so local runs land in the app while CI runs, and
|
|
28
|
+
* local runs with the app closed, go to `serverUrl` with its credentials.
|
|
29
|
+
* `'off'` never uses it. Defaults to `'fallback'`. `PIWI_DESKTOP_APP`
|
|
30
|
+
* overrides this option, so setting it to `off` for one run sends that
|
|
31
|
+
* local run to `serverUrl`.
|
|
32
|
+
*/
|
|
33
|
+
desktopApp?: 'prefer' | 'fallback' | 'off';
|
|
22
34
|
/** Name of the project to report results under. Defaults to `'default-project'`. */
|
|
23
35
|
projectName?: string;
|
|
24
36
|
/** Optional description of the project */
|
|
@@ -128,6 +140,19 @@ interface PiwiDashboardOptions {
|
|
|
128
140
|
* `PIWI_COVERAGE_SERVER`.
|
|
129
141
|
*/
|
|
130
142
|
server?: 'off' | 'files' | 'functions' | 'lines';
|
|
143
|
+
/**
|
|
144
|
+
* What the browser's style sheets record: `'rules'`, the rules that
|
|
145
|
+
* matched an element during each test, from Chromium's CSS rule usage, each
|
|
146
|
+
* as its first and last line in the application's style sheet (a `.css`
|
|
147
|
+
* file served as written, a Vite dev server's styles, or a sheet with a
|
|
148
|
+
* source map), sent where a script's functions are; `'off'` records
|
|
149
|
+
* nothing. A change to a rule then selects the tests that rendered it. It
|
|
150
|
+
* costs style recalculation time, a little on most pages and up to half
|
|
151
|
+
* the test's time on a page with large style sheets and frequent
|
|
152
|
+
* re-renders, so it is a choice of its own. Chromium only. Defaults to
|
|
153
|
+
* `'off'`. Can also be set with `PIWI_COVERAGE_CSS`.
|
|
154
|
+
*/
|
|
155
|
+
css?: 'off' | 'rules';
|
|
131
156
|
};
|
|
132
157
|
/**
|
|
133
158
|
* Match each test to its history on the dashboard by an external ID, the ID the test has in your own test
|
|
@@ -244,8 +269,11 @@ interface PiwiDashboardOptions {
|
|
|
244
269
|
* `globalSetup`, so leave it off when that setup is what starts the app. Only
|
|
245
270
|
* the projects the run selects are checked, with their dependencies: the ones
|
|
246
271
|
* `--project` names, or every project but those declared `default: false`
|
|
247
|
-
* (Playwright 1.64+).
|
|
248
|
-
*
|
|
272
|
+
* (Playwright 1.64+). The check runs from this machine's network: for a
|
|
273
|
+
* project whose browser runs on another machine (`connectOptions`) and whose
|
|
274
|
+
* `exposeNetwork` rule does not cover the base URL's host and port, its message says
|
|
275
|
+
* so, since that browser reaches the app from its own network. **Defaults to
|
|
276
|
+
* `false`**. Can also be set with `PIWI_CHECK_BASE_URL=true`.
|
|
249
277
|
*/
|
|
250
278
|
checkBaseUrl?: boolean;
|
|
251
279
|
/**
|
|
@@ -495,6 +523,10 @@ declare class PiwiDashboardReporter {
|
|
|
495
523
|
private readonly listMode;
|
|
496
524
|
/** True when the server URL and API key came from the desktop app, not from config. */
|
|
497
525
|
private viaDesktopApp;
|
|
526
|
+
/** The configured server `desktopApp: 'prefer'` set aside for the desktop app, if any. */
|
|
527
|
+
private readonly desktopReplaced;
|
|
528
|
+
/** The config folder's `.env` file and the connection variables taken from it, if any. */
|
|
529
|
+
private readonly viaDotEnv;
|
|
498
530
|
private isFullRun;
|
|
499
531
|
private filterDetails;
|
|
500
532
|
/** Configured `maxFailures` (0 = unlimited) — disambiguates an interrupted run's unrun reason. */
|
|
@@ -509,6 +541,8 @@ declare class PiwiDashboardReporter {
|
|
|
509
541
|
private resourcesFile;
|
|
510
542
|
/** Samples the run's processes and the machine from `onBegin` to `onEnd`. */
|
|
511
543
|
private sampler;
|
|
544
|
+
/** Posts what the sampler and the censuses add while someone has the run open on the dashboard. */
|
|
545
|
+
private liveResources;
|
|
512
546
|
/** Bytes of the files the tests attached, by kind. */
|
|
513
547
|
private artifactBytes;
|
|
514
548
|
/** The run's resource report for the dashboard, built in `onEnd`. */
|