@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.
@@ -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 path7 = __toESM(require("path"));
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 fs6 = __toESM(require("fs"));
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
- function resolveOptions(raw) {
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
- if (serverUrlUnset && apiKeyUnset) {
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.browser !== void 0 || coverage.server !== void 0) mergedRaw.coverage = 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 path2 = __toESM(require("path"));
660
+ var path3 = __toESM(require("path"));
576
661
  function configDirFromConfig(config) {
577
- if (config?.configFile) return path2.dirname(config.configFile);
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 path3 = __toESM(require("path"));
697
+ var path4 = __toESM(require("path"));
613
698
  var os3 = __toESM(require("os"));
614
- var fs2 = __toESM(require("fs"));
699
+ var fs3 = __toESM(require("fs"));
615
700
  function getSetupFilePath(projectName) {
616
- return path3.join(os3.tmpdir(), `piwi-dashboard-setup-${hashForProject(projectName)}.json`);
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 path4 = __toESM(require("path"));
705
+ var path5 = __toESM(require("path"));
621
706
  var os4 = __toESM(require("os"));
622
- var fs3 = __toESM(require("fs"));
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 path4.join(os4.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
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
- fs3.writeFileSync(getAriaSampleFilePath(projectName), JSON.stringify({ projectName, identities }));
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
- fs3.rmSync(getAriaSampleFilePath(projectName), { force: true });
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(".*")}$`, "i");
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 baseUrlTargets(config, argv = process.argv) {
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
- return ` ${target.url}${used}: ${reason}`;
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 fs4 = __toESM(require("fs"));
792
- var path5 = __toESM(require("path"));
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 = path5.join(configDir, COMMITTED_MANIFEST_FILE);
954
+ const file = path6.join(configDir, COMMITTED_MANIFEST_FILE);
827
955
  try {
828
- if (!fs4.existsSync(file)) return null;
829
- return parseManifestJson(fs4.readFileSync(file, "utf8"));
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 path8 = base.pathname.replace(/\/+$/, "");
840
- return path8 ? [new URL(`${path8}${INSTRUMENTATION_MANIFEST_PATH}`, base).toString(), root] : [root];
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 fs5 = __toESM(require("fs"));
874
- var path6 = __toESM(require("path"));
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 = path7.resolve(__dirname, "./index.js");
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 path7.resolve(require.resolve(r[0])) === piwiReporterPath;
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({ ...inlineReporterOptions, ...options });
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) logger.debug(`Base URL answered: ${target.url}`);
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 path7.resolve(require.resolve(r[0])) === piwiReporterPath;
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
- fs6.writeFileSync(
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+). **Defaults to `false`**. Can
248
- * also be set with `PIWI_CHECK_BASE_URL=true`.
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`. */