@piwitests/reporter 0.54.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.
- package/dist/cli/index.js +1442 -294
- package/dist/global-setup-module.js +71 -42
- package/dist/index.d.ts +21 -9
- package/dist/index.js +5953 -5255
- package/dist/internal/capture/attachments.d.ts +2 -0
- package/dist/internal/capture/attachments.js +2 -0
- package/dist/internal/capture/capture-fixtures.d.ts +30 -9
- package/dist/internal/capture/capture-fixtures.js +767 -299
- package/package.json +1 -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 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
|
|
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((
|
|
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
|
-
|
|
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
|
|
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((
|
|
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
|
|
823
|
-
return
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
930
|
-
const wantsManifest = opts.uploadManifest !== false && !!opts.projectName && (!!committedManifest ||
|
|
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
|
-
|
|
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
|
|
951
|
-
|
|
981
|
+
await uploadManifest(
|
|
982
|
+
httpClient,
|
|
983
|
+
auth,
|
|
984
|
+
projectId,
|
|
952
985
|
{ source: "committed", branch, manifest: committedManifest },
|
|
953
|
-
|
|
954
|
-
)
|
|
986
|
+
logger
|
|
987
|
+
);
|
|
955
988
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (
|
|
959
|
-
await httpClient
|
|
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; `'
|
|
106
|
-
*
|
|
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
|
|
174
|
-
* `piwi.manifest.json` next to the Playwright config
|
|
175
|
-
*
|
|
176
|
-
*
|
|
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
|
|
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;
|