@piwitests/reporter 0.53.0 → 0.55.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 path5 = __toESM(require("path"));
38
+ var path6 = __toESM(require("path"));
39
39
 
40
40
  // src/internal/support/errors.ts
41
41
  function errorMessage(error) {
@@ -44,7 +44,7 @@ function errorMessage(error) {
44
44
  }
45
45
 
46
46
  // src/public/global-setup.ts
47
- var fs5 = __toESM(require("fs"));
47
+ var fs6 = __toESM(require("fs"));
48
48
 
49
49
  // src/internal/config/desktop.ts
50
50
  var fs = __toESM(require("fs"));
@@ -443,7 +443,7 @@ var HttpClient = class {
443
443
  }
444
444
  /** Unified request core: transport, headers, auth, response accumulation, timeout. */
445
445
  request(method, pathname, opts) {
446
- return new Promise((resolve2, reject) => {
446
+ return new Promise((resolve3, reject) => {
447
447
  if (this.closedReason !== null) {
448
448
  reject(new Error(this.closedReason));
449
449
  return;
@@ -466,7 +466,7 @@ var HttpClient = class {
466
466
  data += chunk;
467
467
  });
468
468
  res.on("end", () => {
469
- resolve2({ status: res.statusCode ?? 0, text: data, headers: res.headers });
469
+ resolve3({ status: res.statusCode ?? 0, text: data, headers: res.headers });
470
470
  });
471
471
  res.on("error", reject);
472
472
  res.on("close", () => {
@@ -713,8 +713,8 @@ function requestError(error) {
713
713
  return first.replace(/^apiRequestContext\.\w+:\s*/, "");
714
714
  }
715
715
  var playwrightProbe = async (target, timeoutMs) => {
716
- const { request } = await import("@playwright/test");
717
- const context = await request.newContext({ ignoreHTTPSErrors: target.ignoreHTTPSErrors, proxy: target.proxy });
716
+ const { request: request3 } = await import("@playwright/test");
717
+ const context = await request3.newContext({ ignoreHTTPSErrors: target.ignoreHTTPSErrors, proxy: target.proxy });
718
718
  try {
719
719
  const response = await context.get(target.url, { timeout: timeoutMs, maxRedirects: 0, failOnStatusCode: false });
720
720
  return { status: response.status() };
@@ -740,7 +740,7 @@ async function checkBaseUrl(target, options = {}) {
740
740
  } else {
741
741
  reason = outcome.error;
742
742
  }
743
- if (attempt < attempts) await new Promise((resolve2) => setTimeout(resolve2, delayMs));
743
+ if (attempt < attempts) await new Promise((resolve3) => setTimeout(resolve3, delayMs));
744
744
  }
745
745
  return `${reason} (${attempts} ${attempts === 1 ? "attempt" : "attempts"})`;
746
746
  }
@@ -766,6 +766,12 @@ async function checkBaseUrls(config, options = {}) {
766
766
  // src/internal/manifest/collect.ts
767
767
  var fs4 = __toESM(require("fs"));
768
768
  var path4 = __toESM(require("path"));
769
+
770
+ // src/internal/transport/backend-request.ts
771
+ var http2 = __toESM(require("http"));
772
+ var https2 = __toESM(require("https"));
773
+
774
+ // src/internal/manifest/collect.ts
769
775
  var COMMITTED_MANIFEST_FILE = "piwi.manifest.json";
770
776
  var INSTRUMENTATION_MANIFEST_PATH = "/__piwi/manifest";
771
777
  var INSTRUMENTATION_HEADERS = ["x-piwi-logs", "x-piwi-trace"];
@@ -806,29 +812,20 @@ function configDirFromConfig(config) {
806
812
  if (config?.rootDir) return config.rootDir;
807
813
  return process.cwd();
808
814
  }
809
- function baseUrlFromConfig(config) {
810
- const top = config?.use?.baseURL;
811
- if (typeof top === "string" && top) return top;
812
- for (const project of config?.projects ?? []) {
813
- const url = project?.use?.baseURL;
814
- if (typeof url === "string" && url) return url;
815
- }
816
- return null;
817
- }
818
815
  var INSTRUMENTATION_FETCH_TIMEOUT_MS = 5e3;
816
+ var MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
819
817
  function instrumentationManifestUrls(baseUrl) {
820
818
  const base = new URL(baseUrl);
821
819
  const root = new URL(INSTRUMENTATION_MANIFEST_PATH, base).toString();
822
- const path6 = base.pathname.replace(/\/+$/, "");
823
- return path6 ? [new URL(`${path6}${INSTRUMENTATION_MANIFEST_PATH}`, base).toString(), root] : [root];
820
+ const path7 = base.pathname.replace(/\/+$/, "");
821
+ return path7 ? [new URL(`${path7}${INSTRUMENTATION_MANIFEST_PATH}`, base).toString(), root] : [root];
824
822
  }
825
823
  async function fetchInstrumentationManifest(baseUrl, fetchImpl = fetch) {
826
824
  try {
827
825
  const probe = await fetchImpl(baseUrl, { signal: AbortSignal.timeout(INSTRUMENTATION_FETCH_TIMEOUT_MS) });
828
826
  if (!hasInstrumentationHeader(probe.headers)) return null;
829
827
  for (const manifestUrl of instrumentationManifestUrls(baseUrl)) {
830
- const res = await fetchImpl(manifestUrl, { signal: AbortSignal.timeout(INSTRUMENTATION_FETCH_TIMEOUT_MS) });
831
- const manifest = res.ok ? parseManifestJson(await res.text()) : null;
828
+ const manifest = await fetchManifestAt(manifestUrl, fetchImpl);
832
829
  if (manifest) return manifest;
833
830
  }
834
831
  return null;
@@ -836,18 +833,55 @@ async function fetchInstrumentationManifest(baseUrl, fetchImpl = fetch) {
836
833
  return null;
837
834
  }
838
835
  }
836
+ async function fetchManifestAt(manifestUrl, fetchImpl) {
837
+ const res = await fetchImpl(manifestUrl, { signal: AbortSignal.timeout(INSTRUMENTATION_FETCH_TIMEOUT_MS) });
838
+ return res.ok ? parseManifestJson(await res.text()) : null;
839
+ }
840
+
841
+ // src/internal/manifest/run-manifests.ts
842
+ async function lookupProjectId(httpClient, auth, projectName) {
843
+ const menu = await httpClient.getJSON("/api/projects/menu", auth);
844
+ return menu?.items?.find(
845
+ (p) => p.name.toLowerCase() === projectName.toLowerCase()
846
+ )?.id;
847
+ }
848
+ async function uploadManifest(httpClient, auth, projectId, upload, logger) {
849
+ await httpClient.putJSON(`/api/projects/${projectId}/surface/manifest`, upload, auth).catch((e) => logger.debug(`Manifest upload (${upload.source}) skipped: ${errorMessage(e)}`));
850
+ }
851
+
852
+ // src/internal/collect/source-checksums.ts
853
+ var crypto2 = __toESM(require("crypto"));
854
+ var fs5 = __toESM(require("fs"));
855
+ var path5 = __toESM(require("path"));
856
+
857
+ // src/internal/collect/server-coverage.ts
858
+ var DEFAULT_COLLECTOR_LIMITS = {
859
+ batchTests: 25,
860
+ batchDelayMs: 1e3,
861
+ callTimeoutMs: 1e4,
862
+ runTimeoutMs: 3e4,
863
+ maxIdsPerCall: 1e4,
864
+ maxBodyBytes: 1024 * 1024 - 4096,
865
+ maxIdsPerTest: 5e4,
866
+ concurrency: 2
867
+ };
868
+ var MAX_ANSWER_BYTES = 64 * 1024 * 1024;
869
+ function baseUrlsFromConfig(config) {
870
+ const urls = [config?.use?.baseURL, ...(config?.projects ?? []).map((project) => project?.use?.baseURL)];
871
+ return [...new Set(urls.filter((url) => typeof url === "string" && url !== ""))];
872
+ }
839
873
 
840
874
  // src/public/global-setup.ts
841
875
  function createGlobalSetup(options, userSetup) {
842
876
  return async function globalSetupFn(config) {
843
- const piwiReporterPath = path5.resolve(__dirname, "./index.js");
877
+ const piwiReporterPath = path6.resolve(__dirname, "./index.js");
844
878
  let inlineReporterOptions = {};
845
879
  if (Array.isArray(config?.reporter)) {
846
880
  for (const r of config.reporter) {
847
881
  if (!Array.isArray(r) || typeof r[0] !== "string") continue;
848
882
  const isPiwi = r[0].toLowerCase().includes("piwi") || (() => {
849
883
  try {
850
- return path5.resolve(require.resolve(r[0])) === piwiReporterPath;
884
+ return path6.resolve(require.resolve(r[0])) === piwiReporterPath;
851
885
  } catch {
852
886
  return false;
853
887
  }
@@ -883,7 +917,7 @@ function createGlobalSetup(options, userSetup) {
883
917
  if (!Array.isArray(r) || typeof r[0] !== "string") return false;
884
918
  if (r[0].toLowerCase().includes("piwi")) return true;
885
919
  try {
886
- return path5.resolve(require.resolve(r[0])) === piwiReporterPath;
920
+ return path6.resolve(require.resolve(r[0])) === piwiReporterPath;
887
921
  } catch {
888
922
  return false;
889
923
  }
@@ -913,7 +947,7 @@ function createGlobalSetup(options, userSetup) {
913
947
  auth
914
948
  );
915
949
  if (response?.runId && response?.setupToken) {
916
- fs5.writeFileSync(
950
+ fs6.writeFileSync(
917
951
  getSetupFilePath(opts.projectName),
918
952
  JSON.stringify({
919
953
  runId: response.runId,
@@ -926,14 +960,11 @@ function createGlobalSetup(options, userSetup) {
926
960
  if (opts.projectName) clearAriaSampleFile(opts.projectName);
927
961
  const wantsAria = opts.sampleAriaOnPass !== false && !!opts.projectName;
928
962
  const committedManifest = opts.uploadManifest !== false ? readCommittedManifest(configDirFromConfig(config)) : null;
929
- const manifestBaseUrl = opts.uploadManifest !== false ? baseUrlFromConfig(config) : null;
930
- const wantsManifest = opts.uploadManifest !== false && !!opts.projectName && (!!committedManifest || !!manifestBaseUrl);
963
+ const manifestBaseUrls = opts.uploadManifest !== false ? baseUrlsFromConfig(config) : [];
964
+ const wantsManifest = opts.uploadManifest !== false && !!opts.projectName && (!!committedManifest || manifestBaseUrls.length > 0);
931
965
  let projectId;
932
966
  if ((wantsAria || wantsManifest) && opts.projectName) {
933
- const menu = await httpClient.getJSON("/api/projects/menu", auth);
934
- projectId = menu?.items?.find(
935
- (p) => p.name.toLowerCase() === opts.projectName.toLowerCase()
936
- )?.id;
967
+ projectId = await lookupProjectId(httpClient, auth, opts.projectName);
937
968
  }
938
969
  if (wantsAria && projectId != null) {
939
970
  const sampling = await httpClient.getJSON(`/api/projects/${projectId}/aria-sampling`, auth);
@@ -947,20 +978,18 @@ function createGlobalSetup(options, userSetup) {
947
978
  if (wantsManifest && projectId != null) {
948
979
  const branch = resolveScmBranch(process.env) ?? null;
949
980
  if (committedManifest) {
950
- await httpClient.putJSON(
951
- `/api/projects/${projectId}/surface/manifest`,
981
+ await uploadManifest(
982
+ httpClient,
983
+ auth,
984
+ projectId,
952
985
  { source: "committed", branch, manifest: committedManifest },
953
- auth
954
- ).catch((e) => logger.debug(`Manifest upload (committed) skipped: ${errorMessage(e)}`));
986
+ logger
987
+ );
955
988
  }
956
- if (manifestBaseUrl) {
957
- const declared = await fetchInstrumentationManifest(manifestBaseUrl);
958
- if (declared) {
959
- await httpClient.putJSON(
960
- `/api/projects/${projectId}/surface/manifest`,
961
- { source: "instrumentation", branch, manifest: declared },
962
- auth
963
- ).catch((e) => logger.debug(`Manifest upload (instrumentation) skipped: ${errorMessage(e)}`));
989
+ const declared = await Promise.all(manifestBaseUrls.map((url) => fetchInstrumentationManifest(url)));
990
+ for (const manifest of declared) {
991
+ if (manifest) {
992
+ await uploadManifest(httpClient, auth, projectId, { source: "instrumentation", branch, manifest }, logger);
964
993
  }
965
994
  }
966
995
  }
package/dist/index.d.ts CHANGED
@@ -90,8 +90,14 @@ interface PiwiDashboardOptions {
90
90
  * repository root, which is always tried last. Defaults to the Playwright
91
91
  * config's directory. Set it to the dev server's root when that is not the
92
92
  * config's directory (Nuxt: `['app']`).
93
+ *
94
+ * An object gives directories per origin instead, for an application whose
95
+ * tests reach several dev servers that serve modules at the same paths (one per
96
+ * portal): a script resolves against the directories of its own origin, else
97
+ * of the origin of the page that loaded it, then those under `'*'`
98
+ * (`{ 'http://localhost:3001': 'portals/admin', '*': ['app'] }`).
93
99
  */
94
- codeReachRoots?: string[];
100
+ codeReachRoots?: string[] | Record<string, string | string[]>;
95
101
  /**
96
102
  * Record, per test, which source code ran, in the browser and in the
97
103
  * backend, and send it with the test as its coverage. Rides the capture
@@ -102,10 +108,13 @@ interface PiwiDashboardOptions {
102
108
  /**
103
109
  * What the browser side records: `'files'`, the application files whose
104
110
  * functions ran, from Chromium's JavaScript coverage, which is what
105
- * `captureCodeReach: true` records; `'off'` records nothing. Only paths are
106
- * sent. Defaults to `'off'`. Can also be set with `PIWI_COVERAGE_BROWSER`.
111
+ * `captureCodeReach: true` records; `'functions'` adds the start and end
112
+ * line of each function that ran, read through the dev server's modules or
113
+ * the bundle's source maps; `'off'` records nothing. Paths, and line numbers
114
+ * with `'functions'`, are sent, never source code or hit counts. Defaults to
115
+ * `'off'`. Can also be set with `PIWI_COVERAGE_BROWSER`.
107
116
  */
108
- browser?: 'off' | 'files';
117
+ browser?: 'off' | 'files' | 'functions';
109
118
  /**
110
119
  * What the server side records, from a backend built with Piwi's coverage
111
120
  * instrumentation (ASP.NET Core: `-p:PiwiCoverage=true`), which names each
@@ -170,10 +179,11 @@ interface PiwiDashboardOptions {
170
179
  */
171
180
  sampleAriaOnPass?: boolean;
172
181
  /**
173
- * Upload the application's declared surface at run start: a committed
174
- * `piwi.manifest.json` next to the Playwright config, and — when the base URL's
175
- * first response carries an instrumentation header — the instrumentation
176
- * package's `/__piwi/manifest`. A route or page the manifest declares that no
182
+ * Upload the application's declared surface: at run start, a committed
183
+ * `piwi.manifest.json` next to the Playwright config and the instrumentation
184
+ * package's `/__piwi/manifest` of each `baseURL` whose first response carries an
185
+ * instrumentation header; as the run's last test ends, the manifest of every
186
+ * other origin whose responses carried one. A route or page the manifest declares that no
177
187
  * test reaches becomes a "declared, never hit" gap. Defaults to `true`; set to
178
188
  * `false` (or `PIWI_UPLOAD_MANIFEST=false`) to never upload.
179
189
  */
@@ -476,8 +486,10 @@ declare class PiwiDashboardReporter {
476
486
  private readonly coverageSettings;
477
487
  /** Each test's coverage, when browser or server coverage is on; set in `onBegin`. */
478
488
  private coverage;
479
- /** Tells when the run's last test ended, while server coverage is on. */
489
+ /** Tells when the run's last test ended, while server coverage needs it. */
480
490
  private lastTestWatch;
491
+ /** The manifests of the instrumented origins the run reaches beyond its base URLs. */
492
+ private manifests;
481
493
  /** Result events that stream once their test's server coverage is in. */
482
494
  private coverageSends;
483
495
  private httpClient;