@piwitests/reporter 0.60.0 → 0.62.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 +437 -229
- package/dist/global-setup-module.js +66 -9
- package/dist/index.d.ts +21 -2
- package/dist/index.js +2534 -371
- package/dist/internal/capture/attachments.d.ts +26 -1
- package/dist/internal/capture/attachments.js +23 -2
- package/dist/internal/capture/capture-fixtures.d.ts +114 -6
- package/dist/internal/capture/capture-fixtures.js +2028 -236
- package/package.json +1 -1
- package/templates/skills/apply-locator-healing/SKILL.md +2 -2
- package/templates/skills/fix-a-reported-bug/SKILL.md +11 -8
- package/templates/skills/investigate-failure/SKILL.md +3 -3
- package/templates/skills/setup-piwi/SKILL.md +11 -8
- package/templates/skills/stabilize-flaky-tests/SKILL.md +3 -3
- package/templates/skills/write-the-missing-test/SKILL.md +13 -10
|
@@ -202,6 +202,7 @@ var PIWI_ENV_KEYS = {
|
|
|
202
202
|
codeReachRoots: "PIWI_CODE_REACH_ROOTS",
|
|
203
203
|
coverageBrowser: "PIWI_COVERAGE_BROWSER",
|
|
204
204
|
coverageServer: "PIWI_COVERAGE_SERVER",
|
|
205
|
+
coverageCss: "PIWI_COVERAGE_CSS",
|
|
205
206
|
externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
|
|
206
207
|
externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
|
|
207
208
|
captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
|
|
@@ -321,10 +322,14 @@ function resolveOptions(raw, configDir) {
|
|
|
321
322
|
const coverage = { ...mergedRaw.coverage };
|
|
322
323
|
const coverageBrowser = env[PIWI_ENV_KEYS.coverageBrowser];
|
|
323
324
|
const coverageServer = env[PIWI_ENV_KEYS.coverageServer];
|
|
325
|
+
const coverageCss = env[PIWI_ENV_KEYS.coverageCss];
|
|
324
326
|
if (coverage.browser === void 0 && coverageBrowser)
|
|
325
327
|
coverage.browser = coverageBrowser;
|
|
326
328
|
if (coverage.server === void 0 && coverageServer) coverage.server = coverageServer;
|
|
327
|
-
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
|
+
}
|
|
328
333
|
const externalId = { ...mergedRaw.externalId };
|
|
329
334
|
const externalIdPattern = env[PIWI_ENV_KEYS.externalIdPattern];
|
|
330
335
|
const externalIdOnDuplicate = env[PIWI_ENV_KEYS.externalIdOnDuplicate];
|
|
@@ -736,6 +741,7 @@ function isListMode(argv = process.argv) {
|
|
|
736
741
|
}
|
|
737
742
|
|
|
738
743
|
// src/internal/support/base-url-check.ts
|
|
744
|
+
var net = __toESM(require("net"));
|
|
739
745
|
var BASE_URL_CHECK_ATTEMPTS = 3;
|
|
740
746
|
var BASE_URL_CHECK_TIMEOUT_MS = 1e4;
|
|
741
747
|
var BASE_URL_CHECK_DELAY_MS = 2e3;
|
|
@@ -757,9 +763,9 @@ function cliProjectNames(argv = process.argv) {
|
|
|
757
763
|
}
|
|
758
764
|
return names;
|
|
759
765
|
}
|
|
760
|
-
function wildcard(pattern) {
|
|
766
|
+
function wildcard(pattern, flags = "i") {
|
|
761
767
|
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
762
|
-
return new RegExp(`^${escaped.join(".*")}$`,
|
|
768
|
+
return new RegExp(`^${escaped.join(".*")}$`, flags);
|
|
763
769
|
}
|
|
764
770
|
function selectedProjects(projects, argv = process.argv, filtered) {
|
|
765
771
|
let chosen;
|
|
@@ -769,7 +775,7 @@ function selectedProjects(projects, argv = process.argv, filtered) {
|
|
|
769
775
|
} else {
|
|
770
776
|
const names = cliProjectNames(argv);
|
|
771
777
|
if (!names || names.length === 0) return projects;
|
|
772
|
-
const patterns = names.map(wildcard);
|
|
778
|
+
const patterns = names.map((name) => wildcard(name));
|
|
773
779
|
chosen = new Set(projects.filter((p) => patterns.some((re) => re.test(p.name ?? ""))));
|
|
774
780
|
}
|
|
775
781
|
if (!argv.includes("--no-deps")) {
|
|
@@ -791,23 +797,58 @@ function asProxy(value) {
|
|
|
791
797
|
const proxy = value;
|
|
792
798
|
return proxy && typeof proxy === "object" && typeof proxy.server === "string" ? proxy : void 0;
|
|
793
799
|
}
|
|
794
|
-
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) {
|
|
795
833
|
const projects = config.projects?.length ? config.projects : [{ name: "", use: config.use }];
|
|
796
834
|
const filtered = config.projects?.length ? config.filteredProjects : void 0;
|
|
797
835
|
const targets = /* @__PURE__ */ new Map();
|
|
798
836
|
for (const project of selectedProjects(projects, argv, filtered)) {
|
|
799
837
|
const url = project.use?.baseURL;
|
|
800
838
|
if (typeof url !== "string" || !/^https?:\/\//i.test(url)) continue;
|
|
839
|
+
const remoteBrowser = reachedRemotely(project.use, url, env);
|
|
801
840
|
const target = targets.get(url);
|
|
802
841
|
if (target) {
|
|
803
842
|
if (project.name) target.projects.push(project.name);
|
|
843
|
+
target.remoteBrowser ||= remoteBrowser;
|
|
804
844
|
continue;
|
|
805
845
|
}
|
|
806
846
|
targets.set(url, {
|
|
807
847
|
url,
|
|
808
848
|
projects: project.name ? [project.name] : [],
|
|
809
849
|
ignoreHTTPSErrors: project.use?.ignoreHTTPSErrors === true,
|
|
810
|
-
proxy: asProxy(project.use?.proxy)
|
|
850
|
+
proxy: asProxy(project.use?.proxy),
|
|
851
|
+
remoteBrowser
|
|
811
852
|
});
|
|
812
853
|
}
|
|
813
854
|
return [...targets.values()];
|
|
@@ -848,14 +889,21 @@ async function checkBaseUrl(target, options = {}) {
|
|
|
848
889
|
}
|
|
849
890
|
return `${reason} (${attempts} ${attempts === 1 ? "attempt" : "attempts"})`;
|
|
850
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
|
+
}
|
|
851
897
|
async function checkBaseUrls(config, options = {}) {
|
|
852
|
-
const targets = baseUrlTargets(config, options.argv);
|
|
898
|
+
const targets = baseUrlTargets(config, options.argv, options.env);
|
|
853
899
|
const reasons = await Promise.all(targets.map((target) => checkBaseUrl(target, options)));
|
|
854
900
|
const down = targets.flatMap((target, i) => reasons[i] ? [{ target, reason: reasons[i] }] : []);
|
|
855
901
|
if (down.length === 0) return targets;
|
|
856
902
|
const lines = down.map(({ target, reason }) => {
|
|
857
903
|
const used = target.projects.length > 0 ? ` (${target.projects.join(", ")})` : "";
|
|
858
|
-
|
|
904
|
+
const note = remoteBrowserNote(target);
|
|
905
|
+
return ` ${target.url}${used}: ${reason}${note ? `
|
|
906
|
+
${note}` : ""}`;
|
|
859
907
|
});
|
|
860
908
|
const message = [
|
|
861
909
|
`[Piwi Dashboard] The app under test did not answer, so no test ran:`,
|
|
@@ -1008,7 +1056,16 @@ function createGlobalSetup(options, userSetup) {
|
|
|
1008
1056
|
}
|
|
1009
1057
|
if (opts.checkBaseUrl === true) {
|
|
1010
1058
|
const checked = await checkBaseUrls(config ?? {});
|
|
1011
|
-
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
|
+
}
|
|
1012
1069
|
}
|
|
1013
1070
|
if (opts.enabled === false || !opts.serverUrl) {
|
|
1014
1071
|
logger.info("Not enabled \u2014 set PIWI_DASHBOARD_URL or serverUrl to enable.");
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { PiwiFixtures, PiwiWorkerFixtures, extendPiwiFixtures, piwiFixtures } fr
|
|
|
5
5
|
import '@piwitests/picker-dom';
|
|
6
6
|
import '@piwitests/core/page-interaction';
|
|
7
7
|
import '@piwitests/core/wire';
|
|
8
|
+
import '@piwitests/core/run-channel';
|
|
8
9
|
import '@piwitests/core/code-reach';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -140,6 +141,19 @@ interface PiwiDashboardOptions {
|
|
|
140
141
|
* `PIWI_COVERAGE_SERVER`.
|
|
141
142
|
*/
|
|
142
143
|
server?: 'off' | 'files' | 'functions' | 'lines';
|
|
144
|
+
/**
|
|
145
|
+
* What the browser's style sheets record: `'rules'`, the rules that
|
|
146
|
+
* matched an element during each test, from Chromium's CSS rule usage, each
|
|
147
|
+
* as its first and last line in the application's style sheet (a `.css`
|
|
148
|
+
* file served as written, a Vite dev server's styles, or a sheet with a
|
|
149
|
+
* source map), sent where a script's functions are; `'off'` records
|
|
150
|
+
* nothing. A change to a rule then selects the tests that rendered it. It
|
|
151
|
+
* costs style recalculation time, a little on most pages and up to half
|
|
152
|
+
* the test's time on a page with large style sheets and frequent
|
|
153
|
+
* re-renders, so it is a choice of its own. Chromium only. Defaults to
|
|
154
|
+
* `'off'`. Can also be set with `PIWI_COVERAGE_CSS`.
|
|
155
|
+
*/
|
|
156
|
+
css?: 'off' | 'rules';
|
|
143
157
|
};
|
|
144
158
|
/**
|
|
145
159
|
* Match each test to its history on the dashboard by an external ID, the ID the test has in your own test
|
|
@@ -256,8 +270,11 @@ interface PiwiDashboardOptions {
|
|
|
256
270
|
* `globalSetup`, so leave it off when that setup is what starts the app. Only
|
|
257
271
|
* the projects the run selects are checked, with their dependencies: the ones
|
|
258
272
|
* `--project` names, or every project but those declared `default: false`
|
|
259
|
-
* (Playwright 1.64+).
|
|
260
|
-
*
|
|
273
|
+
* (Playwright 1.64+). The check runs from this machine's network: for a
|
|
274
|
+
* project whose browser runs on another machine (`connectOptions`) and whose
|
|
275
|
+
* `exposeNetwork` rule does not cover the base URL's host and port, its message says
|
|
276
|
+
* so, since that browser reaches the app from its own network. **Defaults to
|
|
277
|
+
* `false`**. Can also be set with `PIWI_CHECK_BASE_URL=true`.
|
|
261
278
|
*/
|
|
262
279
|
checkBaseUrl?: boolean;
|
|
263
280
|
/**
|
|
@@ -525,6 +542,8 @@ declare class PiwiDashboardReporter {
|
|
|
525
542
|
private resourcesFile;
|
|
526
543
|
/** Samples the run's processes and the machine from `onBegin` to `onEnd`. */
|
|
527
544
|
private sampler;
|
|
545
|
+
/** Posts what the sampler and the censuses add while someone has the run open on the dashboard. */
|
|
546
|
+
private liveResources;
|
|
528
547
|
/** Bytes of the files the tests attached, by kind. */
|
|
529
548
|
private artifactBytes;
|
|
530
549
|
/** The run's resource report for the dashboard, built in `onEnd`. */
|