@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
package/dist/cli/index.js
CHANGED
|
@@ -647,11 +647,11 @@ function spawnPlaywright(playwrightArgs2, env, options = {}) {
|
|
|
647
647
|
let fd = output === "inherit" ? null : fs3.openSync(output.file, "a");
|
|
648
648
|
const stdio = fd === null ? "inherit" : ["ignore", fd, fd];
|
|
649
649
|
const child = (0, import_node_child_process.spawn)(invocation.file, invocation.args, { stdio, env, cwd });
|
|
650
|
-
return new Promise((
|
|
650
|
+
return new Promise((resolve11) => {
|
|
651
651
|
const done = (code) => {
|
|
652
652
|
if (fd !== null) fs3.closeSync(fd);
|
|
653
653
|
fd = null;
|
|
654
|
-
|
|
654
|
+
resolve11(code);
|
|
655
655
|
};
|
|
656
656
|
child.on("error", (err) => {
|
|
657
657
|
console.error(`${label}: could not start Playwright \u2014 ${err.message}`);
|
|
@@ -788,9 +788,8 @@ async function runAi(argv, env = process.env) {
|
|
|
788
788
|
}
|
|
789
789
|
|
|
790
790
|
// src/cli/bug.ts
|
|
791
|
-
var
|
|
792
|
-
var
|
|
793
|
-
var import_node_child_process2 = require("child_process");
|
|
791
|
+
var fs7 = __toESM(require("fs"));
|
|
792
|
+
var path10 = __toESM(require("path"));
|
|
794
793
|
|
|
795
794
|
// src/internal/config/env.ts
|
|
796
795
|
var path8 = __toESM(require("path"));
|
|
@@ -870,6 +869,13 @@ function readDotEnv(filePath2) {
|
|
|
870
869
|
}
|
|
871
870
|
}
|
|
872
871
|
|
|
872
|
+
// src/internal/support/ci.ts
|
|
873
|
+
function detectCi(env) {
|
|
874
|
+
return Boolean(
|
|
875
|
+
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"
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
873
879
|
// src/internal/config/env.ts
|
|
874
880
|
var DEFAULTS = {
|
|
875
881
|
projectName: "default-project",
|
|
@@ -932,6 +938,7 @@ var PIWI_ENV_KEYS = {
|
|
|
932
938
|
codeReachRoots: "PIWI_CODE_REACH_ROOTS",
|
|
933
939
|
coverageBrowser: "PIWI_COVERAGE_BROWSER",
|
|
934
940
|
coverageServer: "PIWI_COVERAGE_SERVER",
|
|
941
|
+
coverageCss: "PIWI_COVERAGE_CSS",
|
|
935
942
|
externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
|
|
936
943
|
externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
|
|
937
944
|
captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
|
|
@@ -1006,6 +1013,82 @@ var ENV_FALLBACK_SPECS = [
|
|
|
1006
1013
|
{ option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" },
|
|
1007
1014
|
{ option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
|
|
1008
1015
|
];
|
|
1016
|
+
var DESKTOP_APP_MODES = ["prefer", "fallback", "off"];
|
|
1017
|
+
function desktopAppMode(option, envValue) {
|
|
1018
|
+
const fromEnv = envValue?.trim().toLowerCase();
|
|
1019
|
+
if (DESKTOP_APP_MODES.includes(fromEnv)) return fromEnv;
|
|
1020
|
+
if (DESKTOP_APP_MODES.includes(option)) return option;
|
|
1021
|
+
return "fallback";
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/internal/support/connection.ts
|
|
1025
|
+
var fs6 = __toESM(require("fs"));
|
|
1026
|
+
var path9 = __toESM(require("path"));
|
|
1027
|
+
var import_node_child_process2 = require("child_process");
|
|
1028
|
+
var CONFIG_NAMES = ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs", "playwright.config.cjs"];
|
|
1029
|
+
function findPlaywrightRoot(from, root) {
|
|
1030
|
+
const top = root ? path9.resolve(root) : null;
|
|
1031
|
+
let dir = path9.resolve(from);
|
|
1032
|
+
for (; ; ) {
|
|
1033
|
+
if (CONFIG_NAMES.some((name) => fs6.existsSync(path9.join(dir, name)))) return dir;
|
|
1034
|
+
const parent = path9.dirname(dir);
|
|
1035
|
+
if (dir === top || parent === dir) return null;
|
|
1036
|
+
dir = parent;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function gitRoot(cwd) {
|
|
1040
|
+
try {
|
|
1041
|
+
const out = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
1042
|
+
cwd,
|
|
1043
|
+
encoding: "utf-8",
|
|
1044
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1045
|
+
}).trim();
|
|
1046
|
+
return out ? path9.resolve(out) : null;
|
|
1047
|
+
} catch {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function connectionFolders(cwd) {
|
|
1052
|
+
const root = gitRoot(cwd);
|
|
1053
|
+
const dirs = [findPlaywrightRoot(cwd, root) ?? cwd, cwd, root];
|
|
1054
|
+
return [...new Set(dirs.filter((dir) => dir !== null).map((dir) => path9.resolve(dir)))];
|
|
1055
|
+
}
|
|
1056
|
+
function projectOf(source) {
|
|
1057
|
+
const project = source[PIWI_ENV_KEYS.projectName];
|
|
1058
|
+
return project ? { [PIWI_ENV_KEYS.projectName]: project } : {};
|
|
1059
|
+
}
|
|
1060
|
+
function readDotEnvConnection(dirs) {
|
|
1061
|
+
const files = dirs.map((dir) => readDotEnv(path9.join(dir, ".env")));
|
|
1062
|
+
const withUrl = files.find((file) => file[PIWI_ENV_KEYS.serverUrl]);
|
|
1063
|
+
const vars = {};
|
|
1064
|
+
if (withUrl) {
|
|
1065
|
+
vars[PIWI_ENV_KEYS.serverUrl] = withUrl[PIWI_ENV_KEYS.serverUrl];
|
|
1066
|
+
if (withUrl[PIWI_ENV_KEYS.apiKey]) vars[PIWI_ENV_KEYS.apiKey] = withUrl[PIWI_ENV_KEYS.apiKey];
|
|
1067
|
+
}
|
|
1068
|
+
const project = files.find((file) => file[PIWI_ENV_KEYS.projectName])?.[PIWI_ENV_KEYS.projectName];
|
|
1069
|
+
if (project) vars[PIWI_ENV_KEYS.projectName] = project;
|
|
1070
|
+
return { vars, anyKey: files.some((file) => file[PIWI_ENV_KEYS.apiKey]) };
|
|
1071
|
+
}
|
|
1072
|
+
function resolveCliConnection(flags, env, dirs, command = "piwi") {
|
|
1073
|
+
const { vars: dotEnv, anyKey } = readDotEnvConnection(dirs);
|
|
1074
|
+
const mode = desktopAppMode(void 0, env[PIWI_ENV_KEYS.desktopApp]);
|
|
1075
|
+
const preferLocally = mode === "prefer" && !flags.serverUrl && !detectCi(env);
|
|
1076
|
+
const urlSet = Boolean(flags.serverUrl || env[PIWI_ENV_KEYS.serverUrl] || dotEnv[PIWI_ENV_KEYS.serverUrl]);
|
|
1077
|
+
const keySet = Boolean(flags.apiKey || env[PIWI_ENV_KEYS.apiKey] || anyKey);
|
|
1078
|
+
const readDesktop = mode !== "off" && (preferLocally || urlSet || !keySet);
|
|
1079
|
+
const desktop = readDesktop ? readDesktopConfig(env[PIWI_DESKTOP_CONFIG_ENV] || defaultDesktopConfigPath()) : null;
|
|
1080
|
+
const preferDesktop = desktop !== null && preferLocally;
|
|
1081
|
+
const connection = resolvePiwiConnection(
|
|
1082
|
+
preferDesktop ? { flags, env: projectOf(env), dotEnv: projectOf(dotEnv), desktop } : { flags, env, dotEnv, desktop }
|
|
1083
|
+
);
|
|
1084
|
+
if (connection && flags.apiKey && connection.apiKey !== flags.apiKey) {
|
|
1085
|
+
const from = !preferDesktop && dotEnv[PIWI_ENV_KEYS.serverUrl] ? "a .env file" : "the desktop app";
|
|
1086
|
+
console.error(
|
|
1087
|
+
`${command}: --api-key is not sent to ${connection.serverUrl}, which comes from ${from}: a key goes only to the URL given with it. Pass --server-url to name the instance the key is for.`
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
return connection;
|
|
1091
|
+
}
|
|
1009
1092
|
|
|
1010
1093
|
// src/cli/bug.ts
|
|
1011
1094
|
var EXIT_OK2 = 0;
|
|
@@ -1028,8 +1111,8 @@ Options:
|
|
|
1028
1111
|
--no-run With --write or --out: write it without running it
|
|
1029
1112
|
--force Replace an existing file
|
|
1030
1113
|
--run-mode The spec without test.fail(), as a reproduction runs it
|
|
1031
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
1032
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
1114
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
1115
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
1033
1116
|
|
|
1034
1117
|
Exit codes: 0 printed or written (and, when run, the test behaved as its
|
|
1035
1118
|
test.fail() expects: the bug reproduces), 1 the run did not (the bug may be
|
|
@@ -1043,7 +1126,7 @@ function readOption2(argv, name) {
|
|
|
1043
1126
|
const value = argv[index + 1];
|
|
1044
1127
|
return value !== void 0 && !value.startsWith("--") ? value : void 0;
|
|
1045
1128
|
}
|
|
1046
|
-
function parseBugArgs(argv, env) {
|
|
1129
|
+
function parseBugArgs(argv, env, dirs = []) {
|
|
1047
1130
|
let idText;
|
|
1048
1131
|
for (let i = 0; i < argv.length; i++) {
|
|
1049
1132
|
const token = argv[i];
|
|
@@ -1059,8 +1142,13 @@ function parseBugArgs(argv, env) {
|
|
|
1059
1142
|
for (const flag of VALUE_FLAGS) {
|
|
1060
1143
|
if (argv.includes(flag) && readOption2(argv, flag) === void 0) throw new Error(`${flag} expects a value`);
|
|
1061
1144
|
}
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1145
|
+
const connection = resolveCliConnection(
|
|
1146
|
+
{ serverUrl: readOption2(argv, "--server-url"), apiKey: readOption2(argv, "--api-key") },
|
|
1147
|
+
env,
|
|
1148
|
+
dirs,
|
|
1149
|
+
"piwi bug"
|
|
1150
|
+
);
|
|
1151
|
+
if (!connection) throw new Error("No dashboard \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
1064
1152
|
const out = readOption2(argv, "--out") ?? null;
|
|
1065
1153
|
const write = argv.includes("--write") || out !== null;
|
|
1066
1154
|
return {
|
|
@@ -1070,8 +1158,8 @@ function parseBugArgs(argv, env) {
|
|
|
1070
1158
|
run: write && !argv.includes("--no-run"),
|
|
1071
1159
|
force: argv.includes("--force"),
|
|
1072
1160
|
mode: argv.includes("--run-mode") ? "run" : "commit",
|
|
1073
|
-
serverUrl,
|
|
1074
|
-
apiKey:
|
|
1161
|
+
serverUrl: connection.serverUrl,
|
|
1162
|
+
apiKey: connection.apiKey
|
|
1075
1163
|
};
|
|
1076
1164
|
}
|
|
1077
1165
|
async function fetchBugSpec(args, specDir) {
|
|
@@ -1093,35 +1181,23 @@ async function fetchBugSpec(args, specDir) {
|
|
|
1093
1181
|
if (typeof body.code !== "string" || typeof body.path !== "string") throw new Error("the dashboard sent no spec");
|
|
1094
1182
|
return { code: body.code, path: body.path, warnings: Array.isArray(body.warnings) ? body.warnings : [] };
|
|
1095
1183
|
}
|
|
1096
|
-
function repositoryRoot(cwd) {
|
|
1097
|
-
try {
|
|
1098
|
-
const out = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
1099
|
-
cwd,
|
|
1100
|
-
encoding: "utf-8",
|
|
1101
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1102
|
-
}).trim();
|
|
1103
|
-
return out ? path9.resolve(out) : null;
|
|
1104
|
-
} catch {
|
|
1105
|
-
return null;
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
1184
|
function realFolder(dir) {
|
|
1109
1185
|
const rest = [];
|
|
1110
1186
|
let current = dir;
|
|
1111
|
-
while (!
|
|
1112
|
-
rest.unshift(
|
|
1113
|
-
current =
|
|
1187
|
+
while (!fs7.existsSync(current) && path10.dirname(current) !== current) {
|
|
1188
|
+
rest.unshift(path10.basename(current));
|
|
1189
|
+
current = path10.dirname(current);
|
|
1114
1190
|
}
|
|
1115
|
-
return
|
|
1191
|
+
return path10.join(fs7.realpathSync(current), ...rest);
|
|
1116
1192
|
}
|
|
1117
1193
|
function folderInRepository(root, dir) {
|
|
1118
|
-
const relative9 =
|
|
1119
|
-
if (relative9.startsWith("..") ||
|
|
1120
|
-
return relative9.split(
|
|
1194
|
+
const relative9 = path10.relative(realFolder(root), realFolder(dir));
|
|
1195
|
+
if (relative9.startsWith("..") || path10.isAbsolute(relative9)) return void 0;
|
|
1196
|
+
return relative9.split(path10.sep).join("/");
|
|
1121
1197
|
}
|
|
1122
1198
|
function fileFilter(file) {
|
|
1123
1199
|
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1124
|
-
return `${escape(
|
|
1200
|
+
return `${escape(path10.basename(path10.dirname(file)))}[\\\\/]${escape(path10.basename(file))}$`;
|
|
1125
1201
|
}
|
|
1126
1202
|
function runPlaywright(file, cwd, id, env) {
|
|
1127
1203
|
return spawnPlaywright([fileFilter(file)], { ...env, ...originEnv("bug", id) }, { cwd, command: "piwi bug" });
|
|
@@ -1133,18 +1209,18 @@ async function runBug(argv, env = process.env, cwd = process.cwd()) {
|
|
|
1133
1209
|
}
|
|
1134
1210
|
let args;
|
|
1135
1211
|
try {
|
|
1136
|
-
args = parseBugArgs(argv, env);
|
|
1212
|
+
args = parseBugArgs(argv, env, readOption2(argv, "--server-url") ? [] : connectionFolders(cwd));
|
|
1137
1213
|
} catch (e) {
|
|
1138
1214
|
console.error(`piwi bug: ${e.message}
|
|
1139
1215
|
|
|
1140
1216
|
${USAGE2}`);
|
|
1141
1217
|
return EXIT_ERROR2;
|
|
1142
1218
|
}
|
|
1143
|
-
const root = args.write ?
|
|
1144
|
-
const outFile = args.out !== null ?
|
|
1219
|
+
const root = args.write ? gitRoot(cwd) : null;
|
|
1220
|
+
const outFile = args.out !== null ? path10.resolve(cwd, args.out) : null;
|
|
1145
1221
|
let spec;
|
|
1146
1222
|
try {
|
|
1147
|
-
spec = await fetchBugSpec(args, outFile && root ? folderInRepository(root,
|
|
1223
|
+
spec = await fetchBugSpec(args, outFile && root ? folderInRepository(root, path10.dirname(outFile)) : void 0);
|
|
1148
1224
|
} catch (e) {
|
|
1149
1225
|
console.error(`piwi bug: ${e.message}`);
|
|
1150
1226
|
return EXIT_ERROR2;
|
|
@@ -1159,16 +1235,16 @@ ${USAGE2}`);
|
|
|
1159
1235
|
if (!outFile && !root) {
|
|
1160
1236
|
console.error("piwi bug: not in a git repository \u2014 the bugs folder is read from the working directory");
|
|
1161
1237
|
}
|
|
1162
|
-
const file = outFile ??
|
|
1238
|
+
const file = outFile ?? path10.resolve(root ?? cwd, spec.path);
|
|
1163
1239
|
try {
|
|
1164
|
-
|
|
1165
|
-
|
|
1240
|
+
fs7.mkdirSync(path10.dirname(file), { recursive: true });
|
|
1241
|
+
fs7.writeFileSync(file, spec.code, { flag: args.force ? "w" : "wx" });
|
|
1166
1242
|
} catch (e) {
|
|
1167
1243
|
const exists = e.code === "EEXIST";
|
|
1168
1244
|
console.error(`piwi bug: ${exists ? `${file} exists \u2014 pass --force to replace it` : e.message}`);
|
|
1169
1245
|
return EXIT_ERROR2;
|
|
1170
1246
|
}
|
|
1171
|
-
const shown =
|
|
1247
|
+
const shown = path10.relative(cwd, file) || file;
|
|
1172
1248
|
console.error(`piwi bug: wrote ${shown}`);
|
|
1173
1249
|
if (!args.run) return EXIT_OK2;
|
|
1174
1250
|
const code = await runPlaywright(file, cwd, args.id, env);
|
|
@@ -1186,8 +1262,8 @@ ${USAGE2}`);
|
|
|
1186
1262
|
}
|
|
1187
1263
|
|
|
1188
1264
|
// src/cli/codegen.ts
|
|
1189
|
-
var
|
|
1190
|
-
var
|
|
1265
|
+
var fs8 = __toESM(require("fs"));
|
|
1266
|
+
var path11 = __toESM(require("path"));
|
|
1191
1267
|
|
|
1192
1268
|
// ../core/src/recording.ts
|
|
1193
1269
|
var ASSERTION_MATCHERS = [
|
|
@@ -1878,6 +1954,44 @@ function locatorForStep(step, index, ctx) {
|
|
|
1878
1954
|
}
|
|
1879
1955
|
return `${ctx.page}.${chosen.text}`;
|
|
1880
1956
|
}
|
|
1957
|
+
function escapedInTemplate(code) {
|
|
1958
|
+
return code < 32 && code !== 9 || code === 127 || code === 8232 || code === 8233;
|
|
1959
|
+
}
|
|
1960
|
+
function templateText(text) {
|
|
1961
|
+
let out = "";
|
|
1962
|
+
for (let i = 0; i < text.length; i++) {
|
|
1963
|
+
const c = text[i];
|
|
1964
|
+
const code = c.charCodeAt(0);
|
|
1965
|
+
if (c === "\\" || c === "`" || c === "$" && text[i + 1] === "{") out += `\\${c}`;
|
|
1966
|
+
else if (escapedInTemplate(code)) out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
1967
|
+
else out += c;
|
|
1968
|
+
}
|
|
1969
|
+
return out;
|
|
1970
|
+
}
|
|
1971
|
+
function ariaSnapshotLiteral(snapshot, indent) {
|
|
1972
|
+
const lines = snapshot.split(/\r\n?|\n/).filter((line) => line.trim());
|
|
1973
|
+
const common = Math.min(...lines.map((line) => line.length - line.replace(/^ +/, "").length));
|
|
1974
|
+
return ["`", ...lines.map((line) => `${indent} ${templateText(line.slice(common).trimEnd())}`), `${indent}\``];
|
|
1975
|
+
}
|
|
1976
|
+
function renderAriaSnapshotStep(step, index, ctx, not) {
|
|
1977
|
+
const snapshot = step.assertion?.expected ?? "";
|
|
1978
|
+
if (!snapshot.trim()) {
|
|
1979
|
+
ctx.warnings.push({
|
|
1980
|
+
step: index,
|
|
1981
|
+
code: "incomplete-assertion",
|
|
1982
|
+
detail: "toMatchAriaSnapshot",
|
|
1983
|
+
message: "toMatchAriaSnapshot needs the ARIA snapshot to match."
|
|
1984
|
+
});
|
|
1985
|
+
return [` // Step ${index + 1}: toMatchAriaSnapshot without a snapshot.`];
|
|
1986
|
+
}
|
|
1987
|
+
const [open, ...rest] = ariaSnapshotLiteral(snapshot, " ");
|
|
1988
|
+
const close = rest.pop();
|
|
1989
|
+
return [
|
|
1990
|
+
` await expect(${locatorForStep(step, index, ctx)})${not}.toMatchAriaSnapshot(${open}`,
|
|
1991
|
+
...rest,
|
|
1992
|
+
`${close});`
|
|
1993
|
+
];
|
|
1994
|
+
}
|
|
1881
1995
|
function renderAssertStep(step, index, ctx) {
|
|
1882
1996
|
const assertion = step.action === "assertVisible" ? null : step.assertion;
|
|
1883
1997
|
if (step.action === "assert" && !assertion) {
|
|
@@ -1888,6 +2002,7 @@ function renderAssertStep(step, index, ctx) {
|
|
|
1888
2002
|
const not = assertion?.negated ? ".not" : "";
|
|
1889
2003
|
const lines = [];
|
|
1890
2004
|
if (assertion?.note) lines.push(` // ${commentText(assertion.note)}`);
|
|
2005
|
+
if (matcher === "toMatchAriaSnapshot") return [...lines, ...renderAriaSnapshotStep(step, index, ctx, not)];
|
|
1891
2006
|
let arg = "";
|
|
1892
2007
|
if (VALUE_MATCHERS.has(matcher)) {
|
|
1893
2008
|
if (assertion?.expected == null) {
|
|
@@ -2320,6 +2435,8 @@ var STEPS_LIMITS = {
|
|
|
2320
2435
|
/** Titles, names, texts, locators. */
|
|
2321
2436
|
textLength: 500,
|
|
2322
2437
|
viewports: 50,
|
|
2438
|
+
/** The options a manual step's instruction names. */
|
|
2439
|
+
options: 50,
|
|
2323
2440
|
/** The widest and tallest viewport, in CSS pixels. */
|
|
2324
2441
|
viewportSize: 1e4,
|
|
2325
2442
|
/** The browser's zoom factors a viewport may name, as browsers allow them. */
|
|
@@ -2491,12 +2608,71 @@ function checkManual(c, v, path25) {
|
|
|
2491
2608
|
}
|
|
2492
2609
|
const description = c.text(v.description, `${path25}.description`, STEPS_LIMITS.valueLength, false);
|
|
2493
2610
|
if (description != null && !description.trim()) c.fail(`${path25}.description`, "must not be empty");
|
|
2611
|
+
const instruction = readInstruction(v.instruction);
|
|
2494
2612
|
return {
|
|
2495
2613
|
description: description ?? "",
|
|
2496
2614
|
location: c.text(v.location, `${path25}.location`, STEPS_LIMITS.textLength, true),
|
|
2497
|
-
call: c.text(v.call, `${path25}.call`, STEPS_LIMITS.textLength, true)
|
|
2615
|
+
call: c.text(v.call, `${path25}.call`, STEPS_LIMITS.textLength, true),
|
|
2616
|
+
...instruction ? { instruction } : {}
|
|
2498
2617
|
};
|
|
2499
2618
|
}
|
|
2619
|
+
var isText = (v) => typeof v === "string" && v.length <= STEPS_LIMITS.valueLength;
|
|
2620
|
+
var isTextOrNull = (v) => v == null || isText(v);
|
|
2621
|
+
var isCount = (v) => Number.isInteger(v) && v >= 0;
|
|
2622
|
+
var INSTRUCTION_FIELDS = {
|
|
2623
|
+
locator: isText,
|
|
2624
|
+
call: isText,
|
|
2625
|
+
matcher: isText,
|
|
2626
|
+
what: isText,
|
|
2627
|
+
expected: isTextOrNull,
|
|
2628
|
+
received: isTextOrNull,
|
|
2629
|
+
x: (v) => typeof v === "number" && Number.isFinite(v),
|
|
2630
|
+
y: (v) => typeof v === "number" && Number.isFinite(v),
|
|
2631
|
+
count: isCount,
|
|
2632
|
+
limit: isCount,
|
|
2633
|
+
options: (v) => Array.isArray(v) && v.length > 0 && v.length <= STEPS_LIMITS.options && v.every((o) => isText(o) || isCount(o))
|
|
2634
|
+
};
|
|
2635
|
+
var CHECK_VALUES = ["expected", "received"];
|
|
2636
|
+
var INSTRUCTION_KINDS = {
|
|
2637
|
+
goBack: [],
|
|
2638
|
+
goForward: [],
|
|
2639
|
+
reload: [],
|
|
2640
|
+
gotoUnknown: [],
|
|
2641
|
+
setContent: [],
|
|
2642
|
+
keyboardType: [],
|
|
2643
|
+
mouseAt: ["x", "y", "call"],
|
|
2644
|
+
mouseOrKeyboard: ["call"],
|
|
2645
|
+
failedCall: ["call"],
|
|
2646
|
+
locatorCall: ["locator", "call"],
|
|
2647
|
+
fillUnknown: ["locator"],
|
|
2648
|
+
fillPartial: ["locator"],
|
|
2649
|
+
pressUnknown: ["locator"],
|
|
2650
|
+
selectOptionUnknown: ["locator"],
|
|
2651
|
+
setInputFiles: ["locator"],
|
|
2652
|
+
dragTo: ["locator"],
|
|
2653
|
+
drop: ["locator"],
|
|
2654
|
+
focus: ["locator"],
|
|
2655
|
+
blur: ["locator"],
|
|
2656
|
+
dispatchEvent: ["locator"],
|
|
2657
|
+
scrollIntoViewIfNeeded: ["locator"],
|
|
2658
|
+
selectOptions: ["locator", "options"],
|
|
2659
|
+
expectPoll: ["matcher", ...CHECK_VALUES],
|
|
2660
|
+
expectValue: ["what", ...CHECK_VALUES],
|
|
2661
|
+
expectLocator: ["locator", "matcher", ...CHECK_VALUES],
|
|
2662
|
+
expectURL: CHECK_VALUES,
|
|
2663
|
+
leadingSteps: ["count", "limit"]
|
|
2664
|
+
};
|
|
2665
|
+
function readInstruction(v) {
|
|
2666
|
+
if (!isObject(v) || typeof v.kind !== "string" || !Object.prototype.hasOwnProperty.call(INSTRUCTION_KINDS, v.kind))
|
|
2667
|
+
return null;
|
|
2668
|
+
const kind = v.kind;
|
|
2669
|
+
const out = { kind };
|
|
2670
|
+
for (const field of INSTRUCTION_KINDS[kind]) {
|
|
2671
|
+
if (!INSTRUCTION_FIELDS[field](v[field])) return null;
|
|
2672
|
+
out[field] = v[field] ?? null;
|
|
2673
|
+
}
|
|
2674
|
+
return out;
|
|
2675
|
+
}
|
|
2500
2676
|
function checkViewports(c, v, stepCount) {
|
|
2501
2677
|
if (v == null) return [];
|
|
2502
2678
|
if (!Array.isArray(v)) {
|
|
@@ -2717,9 +2893,9 @@ Code:
|
|
|
2717
2893
|
--tag <tag> Add a tag; repeat for more
|
|
2718
2894
|
|
|
2719
2895
|
Project (optional: your function catalog, and the locators your tests already use):
|
|
2720
|
-
--project <name|id> Project (env PIWI_PROJECT_NAME)
|
|
2721
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
2722
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
2896
|
+
--project <name|id> Project (env PIWI_PROJECT_NAME, .env)
|
|
2897
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
2898
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
2723
2899
|
--offline Do not contact the dashboard
|
|
2724
2900
|
|
|
2725
2901
|
Exit codes: 0 ok, 2 the steps could not be read or the spec could not be written.
|
|
@@ -2759,12 +2935,22 @@ function findFile(argv) {
|
|
|
2759
2935
|
}
|
|
2760
2936
|
return void 0;
|
|
2761
2937
|
}
|
|
2762
|
-
function parseCodegenArgs(argv, env) {
|
|
2938
|
+
function parseCodegenArgs(argv, env, dirs = []) {
|
|
2763
2939
|
const file = findFile(argv);
|
|
2764
2940
|
if (!file) throw new Error("No steps file \u2014 usage: piwi codegen <steps.json>");
|
|
2765
2941
|
for (const flag of VALUE_FLAGS2) {
|
|
2766
2942
|
if (argv.includes(flag) && readOption3(argv, flag) === void 0) throw new Error(`${flag} expects a value`);
|
|
2767
2943
|
}
|
|
2944
|
+
const connection = resolveCliConnection(
|
|
2945
|
+
{
|
|
2946
|
+
serverUrl: readOption3(argv, "--server-url"),
|
|
2947
|
+
apiKey: readOption3(argv, "--api-key"),
|
|
2948
|
+
project: readOption3(argv, "--project")
|
|
2949
|
+
},
|
|
2950
|
+
env,
|
|
2951
|
+
dirs,
|
|
2952
|
+
"piwi codegen"
|
|
2953
|
+
);
|
|
2768
2954
|
return {
|
|
2769
2955
|
file,
|
|
2770
2956
|
out: readOption3(argv, "--out") ?? null,
|
|
@@ -2778,9 +2964,9 @@ function parseCodegenArgs(argv, env) {
|
|
|
2778
2964
|
fail: argv.includes("--fail") || argv.includes("--fail-reason"),
|
|
2779
2965
|
failReason: readOption3(argv, "--fail-reason") ?? null,
|
|
2780
2966
|
tags: readAll2(argv, "--tag"),
|
|
2781
|
-
project: readOption3(argv, "--project") ?? env.PIWI_PROJECT_NAME ?? "",
|
|
2782
|
-
serverUrl:
|
|
2783
|
-
apiKey:
|
|
2967
|
+
project: connection?.project ?? readOption3(argv, "--project") ?? env.PIWI_PROJECT_NAME ?? "",
|
|
2968
|
+
serverUrl: connection?.serverUrl ?? "",
|
|
2969
|
+
apiKey: connection?.apiKey ?? null,
|
|
2784
2970
|
offline: argv.includes("--offline")
|
|
2785
2971
|
};
|
|
2786
2972
|
}
|
|
@@ -2839,14 +3025,15 @@ async function loadProjectContext(args) {
|
|
|
2839
3025
|
);
|
|
2840
3026
|
return { catalog, suiteLocators };
|
|
2841
3027
|
}
|
|
2842
|
-
async function runCodegen(argv, env = process.env) {
|
|
3028
|
+
async function runCodegen(argv, env = process.env, cwd = process.cwd()) {
|
|
2843
3029
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
2844
3030
|
console.log(USAGE3);
|
|
2845
3031
|
return EXIT_OK3;
|
|
2846
3032
|
}
|
|
2847
3033
|
let args;
|
|
2848
3034
|
try {
|
|
2849
|
-
|
|
3035
|
+
const given = readOption3(argv, "--server-url") && (readOption3(argv, "--project") || env.PIWI_PROJECT_NAME);
|
|
3036
|
+
args = parseCodegenArgs(argv, env, given ? [] : connectionFolders(cwd));
|
|
2850
3037
|
} catch (e) {
|
|
2851
3038
|
console.error(`piwi codegen: ${e.message}
|
|
2852
3039
|
|
|
@@ -2858,7 +3045,7 @@ ${USAGE3}`);
|
|
|
2858
3045
|
if (bugId) {
|
|
2859
3046
|
if (!args.serverUrl) {
|
|
2860
3047
|
console.error(
|
|
2861
|
-
"piwi codegen: bug:<id> reads the report from the dashboard \u2014 pass --server-url or set PIWI_DASHBOARD_URL"
|
|
3048
|
+
"piwi codegen: bug:<id> reads the report from the dashboard \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)"
|
|
2862
3049
|
);
|
|
2863
3050
|
return EXIT_ERROR3;
|
|
2864
3051
|
}
|
|
@@ -2871,8 +3058,8 @@ ${USAGE3}`);
|
|
|
2871
3058
|
}
|
|
2872
3059
|
} else {
|
|
2873
3060
|
try {
|
|
2874
|
-
if (
|
|
2875
|
-
text =
|
|
3061
|
+
if (fs8.statSync(args.file).size > MAX_STEPS_FILE_BYTES) throw new Error("is too large to be a steps file");
|
|
3062
|
+
text = fs8.readFileSync(args.file, "utf-8");
|
|
2876
3063
|
} catch (e) {
|
|
2877
3064
|
const reason = e.code === "ENOENT" ? "does not exist" : e.message;
|
|
2878
3065
|
console.error(`piwi codegen: ${args.file} ${reason}`);
|
|
@@ -2904,8 +3091,8 @@ ${USAGE3}`);
|
|
|
2904
3091
|
return EXIT_OK3;
|
|
2905
3092
|
}
|
|
2906
3093
|
try {
|
|
2907
|
-
|
|
2908
|
-
|
|
3094
|
+
fs8.mkdirSync(path11.dirname(args.out), { recursive: true });
|
|
3095
|
+
fs8.writeFileSync(args.out, result.code, { flag: args.force ? "w" : "wx" });
|
|
2909
3096
|
} catch (e) {
|
|
2910
3097
|
const exists = e.code === "EEXIST";
|
|
2911
3098
|
console.error(`piwi codegen: ${exists ? `${args.out} exists \u2014 pass --force to replace it` : e.message}`);
|
|
@@ -2919,7 +3106,7 @@ ${USAGE3}`);
|
|
|
2919
3106
|
}
|
|
2920
3107
|
|
|
2921
3108
|
// src/cli/coverage.ts
|
|
2922
|
-
var
|
|
3109
|
+
var path16 = __toESM(require("path"));
|
|
2923
3110
|
|
|
2924
3111
|
// ../core/src/codeowners.ts
|
|
2925
3112
|
function patternParts(pattern) {
|
|
@@ -3043,7 +3230,7 @@ function errorMessage(error) {
|
|
|
3043
3230
|
}
|
|
3044
3231
|
|
|
3045
3232
|
// src/internal/support/test-paths.ts
|
|
3046
|
-
var
|
|
3233
|
+
var path12 = __toESM(require("path"));
|
|
3047
3234
|
|
|
3048
3235
|
// src/internal/collect/metadata-collector.ts
|
|
3049
3236
|
var DETACHED_HEAD = "HEAD";
|
|
@@ -3076,23 +3263,23 @@ function normalizeRef(ref) {
|
|
|
3076
3263
|
|
|
3077
3264
|
// src/internal/coverage-upload/git-inventory.ts
|
|
3078
3265
|
var import_node_child_process5 = require("child_process");
|
|
3079
|
-
var
|
|
3080
|
-
var
|
|
3266
|
+
var fs10 = __toESM(require("fs"));
|
|
3267
|
+
var path14 = __toESM(require("path"));
|
|
3081
3268
|
|
|
3082
3269
|
// src/internal/collect/blob-ids.ts
|
|
3083
3270
|
var import_node_child_process4 = require("child_process");
|
|
3084
3271
|
var crypto2 = __toESM(require("crypto"));
|
|
3085
|
-
var
|
|
3086
|
-
var
|
|
3272
|
+
var fs9 = __toESM(require("fs"));
|
|
3273
|
+
var path13 = __toESM(require("path"));
|
|
3087
3274
|
function gitHashObjects(root, paths) {
|
|
3088
|
-
return new Promise((
|
|
3275
|
+
return new Promise((resolve11) => {
|
|
3089
3276
|
const child = (0, import_node_child_process4.execFile)(
|
|
3090
3277
|
"git",
|
|
3091
3278
|
["hash-object", "--stdin-paths"],
|
|
3092
3279
|
{ cwd: root, encoding: "utf8", timeout: 3e4, windowsHide: true },
|
|
3093
3280
|
(error, stdout) => {
|
|
3094
3281
|
const ids = error ? [] : stdout.split("\n").filter(Boolean);
|
|
3095
|
-
|
|
3282
|
+
resolve11(ids.length === paths.length ? ids : null);
|
|
3096
3283
|
}
|
|
3097
3284
|
);
|
|
3098
3285
|
child.stdin?.on("error", () => {
|
|
@@ -3106,12 +3293,12 @@ function gitHashObjects(root, paths) {
|
|
|
3106
3293
|
var MAX_GIT_OUTPUT = 512 * 1024 * 1024;
|
|
3107
3294
|
var GIT_BATCH = 500;
|
|
3108
3295
|
function git(cwd, args) {
|
|
3109
|
-
return new Promise((
|
|
3296
|
+
return new Promise((resolve11, reject) => {
|
|
3110
3297
|
(0, import_node_child_process5.execFile)(
|
|
3111
3298
|
"git",
|
|
3112
3299
|
args,
|
|
3113
3300
|
{ cwd, encoding: "utf8", maxBuffer: MAX_GIT_OUTPUT, timeout: 12e4, windowsHide: true },
|
|
3114
|
-
(error, stdout) => error ? reject(error) :
|
|
3301
|
+
(error, stdout) => error ? reject(error) : resolve11(stdout)
|
|
3115
3302
|
);
|
|
3116
3303
|
});
|
|
3117
3304
|
}
|
|
@@ -3150,7 +3337,7 @@ async function readInventory(root, warn = () => {
|
|
|
3150
3337
|
const changed = [];
|
|
3151
3338
|
for (const file of (await git(root, ["diff", "--name-only", "-z", "HEAD"]).catch(() => "")).split("\0")) {
|
|
3152
3339
|
if (!file || !inventory.has(file)) continue;
|
|
3153
|
-
if (
|
|
3340
|
+
if (fs10.existsSync(path14.join(root, file))) changed.push(file);
|
|
3154
3341
|
else inventory.delete(file);
|
|
3155
3342
|
}
|
|
3156
3343
|
const ids = await hashPaths(root, changed);
|
|
@@ -3169,8 +3356,8 @@ async function readInventory(root, warn = () => {
|
|
|
3169
3356
|
}
|
|
3170
3357
|
|
|
3171
3358
|
// src/internal/coverage-upload/report-files.ts
|
|
3172
|
-
var
|
|
3173
|
-
var
|
|
3359
|
+
var fs11 = __toESM(require("fs"));
|
|
3360
|
+
var path15 = __toESM(require("path"));
|
|
3174
3361
|
|
|
3175
3362
|
// ../core/src/coverage.ts
|
|
3176
3363
|
var MAX_LINE = 1e6;
|
|
@@ -3682,14 +3869,14 @@ function globSegmentRegExp(segment) {
|
|
|
3682
3869
|
function expandPattern(pattern, cwd) {
|
|
3683
3870
|
const normalized = pattern.replace(/\\/g, "/");
|
|
3684
3871
|
if (!/[*?]/.test(normalized)) {
|
|
3685
|
-
const file =
|
|
3686
|
-
return
|
|
3872
|
+
const file = path15.resolve(cwd, normalized);
|
|
3873
|
+
return fs11.existsSync(file) && fs11.statSync(file).isFile() ? [file] : [];
|
|
3687
3874
|
}
|
|
3688
|
-
const absolute =
|
|
3875
|
+
const absolute = path15.isAbsolute(normalized) || /^[A-Za-z]:\//.test(normalized);
|
|
3689
3876
|
const segments = normalized.split("/");
|
|
3690
3877
|
const firstWild = segments.findIndex((s) => /[*?]/.test(s));
|
|
3691
3878
|
const baseParts = segments.slice(0, firstWild);
|
|
3692
|
-
const base = absolute ? baseParts.join("/") || "/" :
|
|
3879
|
+
const base = absolute ? baseParts.join("/") || "/" : path15.resolve(cwd, ...baseParts);
|
|
3693
3880
|
const rest = segments.slice(firstWild).filter((s) => s !== "");
|
|
3694
3881
|
const out = [];
|
|
3695
3882
|
const walk = (dir, at) => {
|
|
@@ -3697,14 +3884,14 @@ function expandPattern(pattern, cwd) {
|
|
|
3697
3884
|
const segment = rest[at];
|
|
3698
3885
|
let entries;
|
|
3699
3886
|
try {
|
|
3700
|
-
entries =
|
|
3887
|
+
entries = fs11.readdirSync(dir, { withFileTypes: true });
|
|
3701
3888
|
} catch {
|
|
3702
3889
|
return;
|
|
3703
3890
|
}
|
|
3704
3891
|
if (segment === "**") {
|
|
3705
3892
|
walk(dir, at + 1);
|
|
3706
3893
|
for (const entry of entries) {
|
|
3707
|
-
if (entry.isDirectory() && !SKIPPED_DIRS.has(entry.name)) walk(
|
|
3894
|
+
if (entry.isDirectory() && !SKIPPED_DIRS.has(entry.name)) walk(path15.join(dir, entry.name), at);
|
|
3708
3895
|
}
|
|
3709
3896
|
return;
|
|
3710
3897
|
}
|
|
@@ -3712,7 +3899,7 @@ function expandPattern(pattern, cwd) {
|
|
|
3712
3899
|
const last = at === rest.length - 1;
|
|
3713
3900
|
for (const entry of entries) {
|
|
3714
3901
|
if (!match.test(entry.name)) continue;
|
|
3715
|
-
const full =
|
|
3902
|
+
const full = path15.join(dir, entry.name);
|
|
3716
3903
|
if (last && entry.isFile()) out.push(full);
|
|
3717
3904
|
else if (!last && entry.isDirectory() && (!SKIPPED_DIRS.has(entry.name) || segment === entry.name)) {
|
|
3718
3905
|
walk(full, at + 1);
|
|
@@ -3725,7 +3912,7 @@ function expandPattern(pattern, cwd) {
|
|
|
3725
3912
|
async function readReport(file, format) {
|
|
3726
3913
|
let reader = format ? new CoverageReportReader(format) : null;
|
|
3727
3914
|
let head = "";
|
|
3728
|
-
for await (const chunk of
|
|
3915
|
+
for await (const chunk of fs11.createReadStream(file, { encoding: "utf8", highWaterMark: 1024 * 1024 })) {
|
|
3729
3916
|
if (!reader) {
|
|
3730
3917
|
head += chunk;
|
|
3731
3918
|
if (head.length < 4096) continue;
|
|
@@ -3747,18 +3934,18 @@ async function readReport(file, format) {
|
|
|
3747
3934
|
return reader.end();
|
|
3748
3935
|
}
|
|
3749
3936
|
function reportBases(report, reportFile, options) {
|
|
3750
|
-
if (options.root) return [
|
|
3751
|
-
const dir =
|
|
3937
|
+
if (options.root) return [path15.resolve(options.cwd, options.root)];
|
|
3938
|
+
const dir = path15.dirname(path15.resolve(reportFile));
|
|
3752
3939
|
const sources = report.sources.map(
|
|
3753
|
-
(source) =>
|
|
3940
|
+
(source) => path15.isAbsolute(source) || /^[A-Za-z]:[\\/]/.test(source) ? source : path15.resolve(dir, source)
|
|
3754
3941
|
);
|
|
3755
|
-
return [...sources, options.cwd, dir,
|
|
3942
|
+
return [...sources, options.cwd, dir, path15.dirname(dir), options.repoRoot];
|
|
3756
3943
|
}
|
|
3757
3944
|
function resolveReport(report, reportFile, options, into) {
|
|
3758
3945
|
const bases = reportBases(report, reportFile, options);
|
|
3759
3946
|
for (const file of report.files) {
|
|
3760
3947
|
const candidates = reportPathCandidates(file.path, { repoRoot: options.repoRoot, bases });
|
|
3761
|
-
const match = candidates.find((candidate) => options.inventory.has(candidate)) ?? candidates.find((candidate) =>
|
|
3948
|
+
const match = candidates.find((candidate) => options.inventory.has(candidate)) ?? candidates.find((candidate) => fs11.existsSync(path15.join(options.repoRoot, candidate)));
|
|
3762
3949
|
if (!match) {
|
|
3763
3950
|
into.unmatched.push(file.path);
|
|
3764
3951
|
continue;
|
|
@@ -3798,9 +3985,9 @@ Suite:
|
|
|
3798
3985
|
--kind <kind> unit (the default) or e2e
|
|
3799
3986
|
|
|
3800
3987
|
Connection:
|
|
3801
|
-
--project <name|id> Project (env PIWI_PROJECT_NAME)
|
|
3802
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
3803
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
3988
|
+
--project <name|id> Project (env PIWI_PROJECT_NAME, .env)
|
|
3989
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
3990
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
3804
3991
|
--branch <name> The branch (env PIWI_BRANCH, the CI's, else git's)
|
|
3805
3992
|
|
|
3806
3993
|
Other:
|
|
@@ -3820,7 +4007,7 @@ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
3820
4007
|
"--api-key",
|
|
3821
4008
|
"--branch"
|
|
3822
4009
|
]);
|
|
3823
|
-
function parseCoverageUploadArgs(argv, env) {
|
|
4010
|
+
function parseCoverageUploadArgs(argv, env, dirs = []) {
|
|
3824
4011
|
const values = /* @__PURE__ */ new Map();
|
|
3825
4012
|
const patterns2 = [];
|
|
3826
4013
|
const flags = /* @__PURE__ */ new Set();
|
|
@@ -3851,11 +4038,19 @@ function parseCoverageUploadArgs(argv, env) {
|
|
|
3851
4038
|
if (format !== void 0 && format !== "cobertura" && format !== "lcov" && format !== "piwi") {
|
|
3852
4039
|
throw new Error("--format is cobertura, lcov or piwi");
|
|
3853
4040
|
}
|
|
3854
|
-
const
|
|
4041
|
+
const connection = resolveCliConnection(
|
|
4042
|
+
{ serverUrl: values.get("--server-url"), apiKey: values.get("--api-key"), project: values.get("--project") },
|
|
4043
|
+
env,
|
|
4044
|
+
dirs,
|
|
4045
|
+
"piwi coverage upload"
|
|
4046
|
+
);
|
|
4047
|
+
const serverUrl = connection?.serverUrl ?? "";
|
|
3855
4048
|
const dryRun = flags.has("--dry-run");
|
|
3856
|
-
if (!serverUrl && !dryRun)
|
|
3857
|
-
|
|
3858
|
-
|
|
4049
|
+
if (!serverUrl && !dryRun)
|
|
4050
|
+
throw new Error("No dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
4051
|
+
const project = connection?.project ?? values.get("--project") ?? env.PIWI_PROJECT_NAME ?? "";
|
|
4052
|
+
if (!project && !dryRun)
|
|
4053
|
+
throw new Error("No project \u2014 pass --project or set PIWI_PROJECT_NAME (environment or .env)");
|
|
3859
4054
|
return {
|
|
3860
4055
|
patterns: patterns2,
|
|
3861
4056
|
suite,
|
|
@@ -3864,7 +4059,7 @@ function parseCoverageUploadArgs(argv, env) {
|
|
|
3864
4059
|
...values.get("--root") ? { root: values.get("--root") } : {},
|
|
3865
4060
|
project,
|
|
3866
4061
|
serverUrl,
|
|
3867
|
-
apiKey:
|
|
4062
|
+
apiKey: connection?.apiKey ?? null,
|
|
3868
4063
|
...values.get("--branch") ? { branch: values.get("--branch") } : {},
|
|
3869
4064
|
dryRun,
|
|
3870
4065
|
json: flags.has("--json")
|
|
@@ -3983,7 +4178,7 @@ async function uploadCoverage(args, env, cwd, warn = warnOnStderr) {
|
|
|
3983
4178
|
commit: checkout.commit,
|
|
3984
4179
|
branch,
|
|
3985
4180
|
suite: args.suite,
|
|
3986
|
-
reports: reports.map((report) =>
|
|
4181
|
+
reports: reports.map((report) => path16.relative(cwd, report) || report),
|
|
3987
4182
|
files: files.length,
|
|
3988
4183
|
unmatched: resolved.unmatched.length,
|
|
3989
4184
|
outOfScope: resolved.outOfScope,
|
|
@@ -4030,7 +4225,7 @@ ${USAGE4}`);
|
|
|
4030
4225
|
}
|
|
4031
4226
|
let args;
|
|
4032
4227
|
try {
|
|
4033
|
-
args = parseCoverageUploadArgs(rest, env);
|
|
4228
|
+
args = parseCoverageUploadArgs(rest, env, [cwd]);
|
|
4034
4229
|
} catch (error) {
|
|
4035
4230
|
console.error(`piwi coverage upload: ${errorMessage(error)}`);
|
|
4036
4231
|
return EXIT_ERROR4;
|
|
@@ -4053,7 +4248,7 @@ ${USAGE4}`);
|
|
|
4053
4248
|
|
|
4054
4249
|
// src/cli/flake.ts
|
|
4055
4250
|
var import_node_child_process6 = require("child_process");
|
|
4056
|
-
var
|
|
4251
|
+
var fs12 = __toESM(require("fs"));
|
|
4057
4252
|
var os2 = __toESM(require("os"));
|
|
4058
4253
|
var path17 = __toESM(require("path"));
|
|
4059
4254
|
|
|
@@ -4256,19 +4451,6 @@ function withinBudget(startedAt, now, budgetMs) {
|
|
|
4256
4451
|
return now - startedAt < budgetMs;
|
|
4257
4452
|
}
|
|
4258
4453
|
|
|
4259
|
-
// src/internal/support/connection.ts
|
|
4260
|
-
var path16 = __toESM(require("path"));
|
|
4261
|
-
function resolveCliConnection(flags, env, dirs) {
|
|
4262
|
-
let dotEnv = {};
|
|
4263
|
-
for (const dir of dirs) dotEnv = { ...readDotEnv(path16.join(dir, ".env")), ...dotEnv };
|
|
4264
|
-
return resolvePiwiConnection({
|
|
4265
|
-
flags,
|
|
4266
|
-
env,
|
|
4267
|
-
dotEnv,
|
|
4268
|
-
desktop: readDesktopConfig(env[PIWI_DESKTOP_CONFIG_ENV] || defaultDesktopConfigPath())
|
|
4269
|
-
});
|
|
4270
|
-
}
|
|
4271
|
-
|
|
4272
4454
|
// src/cli/flake.ts
|
|
4273
4455
|
var USAGE5 = `
|
|
4274
4456
|
piwi flake \u2014 make a flaky test fail on demand, then prove the fix
|
|
@@ -4311,7 +4493,7 @@ Options:
|
|
|
4311
4493
|
--json Print the results as JSON instead of text
|
|
4312
4494
|
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
4313
4495
|
--api-key <key> Reporter API key (env PIWI_API_KEY, .env)
|
|
4314
|
-
--project <name|id> The Piwi project, for a file:line test (env PIWI_PROJECT_NAME)
|
|
4496
|
+
--project <name|id> The Piwi project, for a file:line test (env PIWI_PROJECT_NAME, .env)
|
|
4315
4497
|
|
|
4316
4498
|
Exit codes: 0 an arm reproduced the failure (verify: the fix held),
|
|
4317
4499
|
1 nothing reproduced (verify: it still fails, or too few runs),
|
|
@@ -4459,8 +4641,8 @@ function playwrightRunner(dir, log) {
|
|
|
4459
4641
|
now: () => Date.now(),
|
|
4460
4642
|
async runBatch(plan, arm, repeat, batch) {
|
|
4461
4643
|
const planFile = path17.join(dir, `${arm.id}.plan.json`);
|
|
4462
|
-
if (!
|
|
4463
|
-
|
|
4644
|
+
if (!fs12.existsSync(planFile)) {
|
|
4645
|
+
fs12.writeFileSync(
|
|
4464
4646
|
planFile,
|
|
4465
4647
|
JSON.stringify({
|
|
4466
4648
|
version: FLAKE_PLAN_VERSION,
|
|
@@ -4472,14 +4654,14 @@ function playwrightRunner(dir, log) {
|
|
|
4472
4654
|
);
|
|
4473
4655
|
}
|
|
4474
4656
|
const resultsFile = path17.join(dir, `${arm.id}.${batch}.jsonl`);
|
|
4475
|
-
|
|
4657
|
+
fs12.writeFileSync(resultsFile, "");
|
|
4476
4658
|
const env = {
|
|
4477
4659
|
...process.env,
|
|
4478
4660
|
[PIWI_FLAKE_ENV.plan]: planFile,
|
|
4479
4661
|
[PIWI_FLAKE_ENV.results]: resultsFile
|
|
4480
4662
|
};
|
|
4481
4663
|
delete env[PIWI_PROBE_ENV.flag];
|
|
4482
|
-
|
|
4664
|
+
fs12.appendFileSync(log, `
|
|
4483
4665
|
\u2500\u2500 ${arm.id} batch ${batch} \u2500\u2500
|
|
4484
4666
|
`);
|
|
4485
4667
|
await spawnPlaywright(playwrightArgs(plan.test, arm, repeat), env, {
|
|
@@ -4492,7 +4674,7 @@ function playwrightRunner(dir, log) {
|
|
|
4492
4674
|
}
|
|
4493
4675
|
function readResults(file) {
|
|
4494
4676
|
const lines = [];
|
|
4495
|
-
for (const line of
|
|
4677
|
+
for (const line of fs12.readFileSync(file, "utf8").split("\n")) {
|
|
4496
4678
|
if (!line.trim()) continue;
|
|
4497
4679
|
try {
|
|
4498
4680
|
lines.push(JSON.parse(line));
|
|
@@ -4656,7 +4838,7 @@ function printHeader(plan, commit, estimate, budgetMs) {
|
|
|
4656
4838
|
async function loadPlan(args, commit) {
|
|
4657
4839
|
if (args.planFile) {
|
|
4658
4840
|
try {
|
|
4659
|
-
const raw = JSON.parse(
|
|
4841
|
+
const raw = JSON.parse(fs12.readFileSync(args.planFile, "utf8"));
|
|
4660
4842
|
const plan = "plan" in raw && raw.plan ? raw.plan : raw;
|
|
4661
4843
|
if (plan.version !== 1 || !plan.control || !Array.isArray(plan.arms)) throw new Error("not a flake-lab plan");
|
|
4662
4844
|
if (args.verify !== (plan.kind === "verify")) {
|
|
@@ -4671,7 +4853,8 @@ async function loadPlan(args, commit) {
|
|
|
4671
4853
|
const connection = resolveCliConnection(
|
|
4672
4854
|
{ serverUrl: args.serverUrl, apiKey: args.apiKey, project: args.project },
|
|
4673
4855
|
process.env,
|
|
4674
|
-
[process.cwd()]
|
|
4856
|
+
[process.cwd()],
|
|
4857
|
+
args.verify ? "piwi flake verify" : "piwi flake"
|
|
4675
4858
|
);
|
|
4676
4859
|
if (!connection) {
|
|
4677
4860
|
throw new FlakeError(
|
|
@@ -4750,7 +4933,7 @@ async function runFlake(argv, runner) {
|
|
|
4750
4933
|
if (!args.json) {
|
|
4751
4934
|
printHeader(plan, commit, estimate, args.budgetMs);
|
|
4752
4935
|
}
|
|
4753
|
-
const dir =
|
|
4936
|
+
const dir = fs12.mkdtempSync(path17.join(os2.tmpdir(), "piwi-flake-"));
|
|
4754
4937
|
const log = path17.join(dir, "playwright.log");
|
|
4755
4938
|
let session;
|
|
4756
4939
|
try {
|
|
@@ -4759,8 +4942,8 @@ async function runFlake(argv, runner) {
|
|
|
4759
4942
|
});
|
|
4760
4943
|
} catch (error) {
|
|
4761
4944
|
console.error(`piwi flake: ${error.message}`);
|
|
4762
|
-
if (
|
|
4763
|
-
const tail =
|
|
4945
|
+
if (fs12.existsSync(log)) {
|
|
4946
|
+
const tail = fs12.readFileSync(log, "utf8").trimEnd().split("\n").slice(-LOG_TAIL_LINES).join("\n");
|
|
4764
4947
|
console.error(`
|
|
4765
4948
|
Playwright's output (${log}):
|
|
4766
4949
|
${tail}`);
|
|
@@ -4833,7 +5016,7 @@ ${tail}`);
|
|
|
4833
5016
|
verifyCommand: plan.kind === "reproduce" && verdict === "reproduced" ? verifyCommand : null,
|
|
4834
5017
|
exitCode: code
|
|
4835
5018
|
};
|
|
4836
|
-
|
|
5019
|
+
fs12.rmSync(dir, { recursive: true, force: true });
|
|
4837
5020
|
if (args.json) {
|
|
4838
5021
|
console.log(JSON.stringify(report, null, 2));
|
|
4839
5022
|
return code;
|
|
@@ -4848,7 +5031,7 @@ ${tail}`);
|
|
|
4848
5031
|
}
|
|
4849
5032
|
|
|
4850
5033
|
// src/cli/gate.ts
|
|
4851
|
-
var
|
|
5034
|
+
var fs13 = __toESM(require("fs"));
|
|
4852
5035
|
|
|
4853
5036
|
// ../core/src/gate.ts
|
|
4854
5037
|
function formatGateResult(result) {
|
|
@@ -4894,8 +5077,8 @@ Where the run comes from (first match wins):
|
|
|
4894
5077
|
(default) ./piwi-run.json, if it exists
|
|
4895
5078
|
|
|
4896
5079
|
Connection:
|
|
4897
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
4898
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
5080
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
5081
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
4899
5082
|
|
|
4900
5083
|
Policy (at least one rule or warn-only flag is required):
|
|
4901
5084
|
--require-tag <tags> Comma-separated; every test carrying the tag must pass
|
|
@@ -4944,16 +5127,22 @@ function readCount(argv, name) {
|
|
|
4944
5127
|
}
|
|
4945
5128
|
function readRunIdFromFile(path25) {
|
|
4946
5129
|
try {
|
|
4947
|
-
const parsed = JSON.parse(
|
|
5130
|
+
const parsed = JSON.parse(fs13.readFileSync(path25, "utf-8"));
|
|
4948
5131
|
const runId = Number(parsed.runId);
|
|
4949
5132
|
return Number.isFinite(runId) && runId > 0 ? runId : null;
|
|
4950
5133
|
} catch {
|
|
4951
5134
|
return null;
|
|
4952
5135
|
}
|
|
4953
5136
|
}
|
|
4954
|
-
function parseGateArgs(argv, env) {
|
|
4955
|
-
const
|
|
4956
|
-
|
|
5137
|
+
function parseGateArgs(argv, env, dirs = []) {
|
|
5138
|
+
const connection = resolveCliConnection(
|
|
5139
|
+
{ serverUrl: readOption4(argv, "--server-url"), apiKey: readOption4(argv, "--api-key") },
|
|
5140
|
+
env,
|
|
5141
|
+
dirs,
|
|
5142
|
+
"piwi gate"
|
|
5143
|
+
);
|
|
5144
|
+
if (!connection)
|
|
5145
|
+
throw new Error("No dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
4957
5146
|
let runId = Number(readOption4(argv, "--run-id") ?? NaN);
|
|
4958
5147
|
if (!Number.isFinite(runId) || runId <= 0) {
|
|
4959
5148
|
const candidate = readOption4(argv, "--from-file") ?? env.PIWI_OUTPUT_FILE ?? "piwi-run.json";
|
|
@@ -4978,8 +5167,8 @@ function parseGateArgs(argv, env) {
|
|
|
4978
5167
|
maxNewLeaks: readCount(argv, "--max-new-leaks")
|
|
4979
5168
|
};
|
|
4980
5169
|
return {
|
|
4981
|
-
serverUrl,
|
|
4982
|
-
apiKey:
|
|
5170
|
+
serverUrl: connection.serverUrl,
|
|
5171
|
+
apiKey: connection.apiKey,
|
|
4983
5172
|
runId,
|
|
4984
5173
|
policy,
|
|
4985
5174
|
maxUncoveredChanges: readCount(argv, "--max-uncovered-changes"),
|
|
@@ -5004,14 +5193,14 @@ async function requestGate(args) {
|
|
|
5004
5193
|
}
|
|
5005
5194
|
return await res.json();
|
|
5006
5195
|
}
|
|
5007
|
-
async function runGate(argv, env = process.env) {
|
|
5196
|
+
async function runGate(argv, env = process.env, cwd = process.cwd()) {
|
|
5008
5197
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
5009
5198
|
console.log(USAGE6);
|
|
5010
5199
|
return EXIT_OK5;
|
|
5011
5200
|
}
|
|
5012
5201
|
let args;
|
|
5013
5202
|
try {
|
|
5014
|
-
args = parseGateArgs(argv, env);
|
|
5203
|
+
args = parseGateArgs(argv, env, [cwd]);
|
|
5015
5204
|
} catch (e) {
|
|
5016
5205
|
console.error(`piwi gate: ${e.message}
|
|
5017
5206
|
`);
|
|
@@ -5035,7 +5224,7 @@ async function runGate(argv, env = process.env) {
|
|
|
5035
5224
|
}
|
|
5036
5225
|
|
|
5037
5226
|
// src/cli/init.ts
|
|
5038
|
-
var
|
|
5227
|
+
var fs16 = __toESM(require("fs"));
|
|
5039
5228
|
var path20 = __toESM(require("path"));
|
|
5040
5229
|
|
|
5041
5230
|
// src/cli/report.ts
|
|
@@ -5053,9 +5242,9 @@ function formatStep(result) {
|
|
|
5053
5242
|
}
|
|
5054
5243
|
|
|
5055
5244
|
// src/cli/detect.ts
|
|
5056
|
-
var
|
|
5245
|
+
var fs14 = __toESM(require("fs"));
|
|
5057
5246
|
var path18 = __toESM(require("path"));
|
|
5058
|
-
var
|
|
5247
|
+
var CONFIG_NAMES2 = [
|
|
5059
5248
|
"playwright.config.ts",
|
|
5060
5249
|
"playwright.config.mts",
|
|
5061
5250
|
"playwright.config.cts",
|
|
@@ -5074,7 +5263,7 @@ var REPORTER_PACKAGE = "@piwitests/reporter";
|
|
|
5074
5263
|
function readPackageJson(root) {
|
|
5075
5264
|
const pkgPath = path18.join(root, "package.json");
|
|
5076
5265
|
try {
|
|
5077
|
-
return { pkg: JSON.parse(
|
|
5266
|
+
return { pkg: JSON.parse(fs14.readFileSync(pkgPath, "utf-8")), pkgPath };
|
|
5078
5267
|
} catch {
|
|
5079
5268
|
return { pkg: null, pkgPath: null };
|
|
5080
5269
|
}
|
|
@@ -5083,14 +5272,14 @@ function detectPackageManager(root, pkg) {
|
|
|
5083
5272
|
const declared = pkg?.packageManager?.split("@")[0];
|
|
5084
5273
|
if (declared === "pnpm" || declared === "yarn" || declared === "bun" || declared === "npm") return declared;
|
|
5085
5274
|
for (const [file, manager] of LOCKFILES) {
|
|
5086
|
-
if (
|
|
5275
|
+
if (fs14.existsSync(path18.join(root, file))) return manager;
|
|
5087
5276
|
}
|
|
5088
5277
|
return "npm";
|
|
5089
5278
|
}
|
|
5090
5279
|
function findConfig(root) {
|
|
5091
|
-
for (const name of
|
|
5280
|
+
for (const name of CONFIG_NAMES2) {
|
|
5092
5281
|
const candidate = path18.join(root, name);
|
|
5093
|
-
if (
|
|
5282
|
+
if (fs14.existsSync(candidate)) return candidate;
|
|
5094
5283
|
}
|
|
5095
5284
|
return null;
|
|
5096
5285
|
}
|
|
@@ -5251,7 +5440,7 @@ function ensureGitignoreEntry(existing, entry = ".env") {
|
|
|
5251
5440
|
}
|
|
5252
5441
|
|
|
5253
5442
|
// src/cli/skills.ts
|
|
5254
|
-
var
|
|
5443
|
+
var fs15 = __toESM(require("fs"));
|
|
5255
5444
|
var path19 = __toESM(require("path"));
|
|
5256
5445
|
|
|
5257
5446
|
// ../core/src/skill-stamp.ts
|
|
@@ -5319,7 +5508,7 @@ var DEFAULT_SKILLS_DIR = path19.join(".claude", "skills");
|
|
|
5319
5508
|
function findTemplatesDir(fromDir) {
|
|
5320
5509
|
let dir = fromDir;
|
|
5321
5510
|
for (let i = 0; i < 6; i++) {
|
|
5322
|
-
if (
|
|
5511
|
+
if (fs15.existsSync(path19.join(dir, "templates", "skills"))) return path19.join(dir, "templates");
|
|
5323
5512
|
const parent = path19.dirname(dir);
|
|
5324
5513
|
if (parent === dir) break;
|
|
5325
5514
|
dir = parent;
|
|
@@ -5338,7 +5527,7 @@ function readFrontMatter(markdown) {
|
|
|
5338
5527
|
}
|
|
5339
5528
|
function readTemplatesVersion(templatesDir) {
|
|
5340
5529
|
try {
|
|
5341
|
-
const pkg = JSON.parse(
|
|
5530
|
+
const pkg = JSON.parse(fs15.readFileSync(path19.join(templatesDir, "..", "package.json"), "utf-8"));
|
|
5342
5531
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
5343
5532
|
} catch {
|
|
5344
5533
|
return "0.0.0";
|
|
@@ -5351,8 +5540,8 @@ function listSkills(templatesDir, slugs = ALL_SKILLS) {
|
|
|
5351
5540
|
const infos = [];
|
|
5352
5541
|
for (const slug of slugs) {
|
|
5353
5542
|
const file = templatePath(templatesDir, slug);
|
|
5354
|
-
if (!
|
|
5355
|
-
const front = readFrontMatter(
|
|
5543
|
+
if (!fs15.existsSync(file)) continue;
|
|
5544
|
+
const front = readFrontMatter(fs15.readFileSync(file, "utf-8"));
|
|
5356
5545
|
infos.push({ slug, name: front.name ?? slug, description: front.description ?? "" });
|
|
5357
5546
|
}
|
|
5358
5547
|
return infos;
|
|
@@ -5364,16 +5553,16 @@ function installSkills(opts) {
|
|
|
5364
5553
|
const source = templatePath(opts.templatesDir, slug);
|
|
5365
5554
|
const relDest = path19.join(opts.skillsDir, slug, "SKILL.md");
|
|
5366
5555
|
const dest = path19.join(opts.root, relDest);
|
|
5367
|
-
if (!
|
|
5556
|
+
if (!fs15.existsSync(source)) {
|
|
5368
5557
|
results.push({ step, status: "error", detail: `no template found for "${slug}"` });
|
|
5369
5558
|
continue;
|
|
5370
5559
|
}
|
|
5371
5560
|
const version = opts.version ?? readTemplatesVersion(opts.templatesDir);
|
|
5372
|
-
const contents = stampSkill(
|
|
5373
|
-
const exists =
|
|
5561
|
+
const contents = stampSkill(fs15.readFileSync(source, "utf-8"), version);
|
|
5562
|
+
const exists = fs15.existsSync(dest);
|
|
5374
5563
|
let detail = "installed skill";
|
|
5375
5564
|
if (exists) {
|
|
5376
|
-
const installed =
|
|
5565
|
+
const installed = fs15.readFileSync(dest, "utf-8");
|
|
5377
5566
|
const state = classifyInstalledSkill(installed, contents);
|
|
5378
5567
|
const from = readSkillStamp(installed).version;
|
|
5379
5568
|
if (state === "current") {
|
|
@@ -5395,8 +5584,8 @@ function installSkills(opts) {
|
|
|
5395
5584
|
}
|
|
5396
5585
|
}
|
|
5397
5586
|
if (!opts.dryRun) {
|
|
5398
|
-
|
|
5399
|
-
|
|
5587
|
+
fs15.mkdirSync(path19.dirname(dest), { recursive: true });
|
|
5588
|
+
fs15.writeFileSync(dest, contents);
|
|
5400
5589
|
}
|
|
5401
5590
|
results.push({ step, file: relDest, status: exists ? "updated" : "created", detail });
|
|
5402
5591
|
}
|
|
@@ -5490,7 +5679,7 @@ function runSkills(argv, templatesDir, cwd = process.cwd()) {
|
|
|
5490
5679
|
|
|
5491
5680
|
// src/cli/init.ts
|
|
5492
5681
|
var DEFAULT_SERVER_URL = "http://localhost:3000";
|
|
5493
|
-
var
|
|
5682
|
+
var DESKTOP_APP_MODES2 = ["prefer", "fallback", "off"];
|
|
5494
5683
|
var USAGE8 = `
|
|
5495
5684
|
piwi init \u2014 wire a Playwright project up to a Piwi Dashboard
|
|
5496
5685
|
|
|
@@ -5566,7 +5755,7 @@ function parseInitArgs(argv, env, cwd) {
|
|
|
5566
5755
|
}
|
|
5567
5756
|
function readFileOr(file, fallback) {
|
|
5568
5757
|
try {
|
|
5569
|
-
return
|
|
5758
|
+
return fs16.readFileSync(file, "utf-8");
|
|
5570
5759
|
} catch {
|
|
5571
5760
|
return fallback;
|
|
5572
5761
|
}
|
|
@@ -5620,7 +5809,7 @@ function stepConfig(project, opts) {
|
|
|
5620
5809
|
projectName: opts.projectName,
|
|
5621
5810
|
desktopApp
|
|
5622
5811
|
});
|
|
5623
|
-
if (edit.status === "updated" && !opts.dryRun)
|
|
5812
|
+
if (edit.status === "updated" && !opts.dryRun) fs16.writeFileSync(project.configPath, edit.text);
|
|
5624
5813
|
if (edit.status === "already" && desktopApp && !new RegExp(`\\bdesktopApp\\s*:\\s*['"\`]${desktopApp}['"\`]`).test(source)) {
|
|
5625
5814
|
return {
|
|
5626
5815
|
step,
|
|
@@ -5635,7 +5824,7 @@ function stepFixtures(project, opts) {
|
|
|
5635
5824
|
const step = "fixtures";
|
|
5636
5825
|
const rel = path20.join("tests", project.configLang === "js" ? "fixtures.js" : "fixtures.ts");
|
|
5637
5826
|
const dest = path20.join(project.root, rel);
|
|
5638
|
-
if (
|
|
5827
|
+
if (fs16.existsSync(dest)) {
|
|
5639
5828
|
const current = readFileOr(dest, "");
|
|
5640
5829
|
if (/piwiFixtures/.test(current))
|
|
5641
5830
|
return { step, file: rel, status: "already", detail: "capture fixtures already set up" };
|
|
@@ -5647,8 +5836,8 @@ function stepFixtures(project, opts) {
|
|
|
5647
5836
|
};
|
|
5648
5837
|
}
|
|
5649
5838
|
if (!opts.dryRun) {
|
|
5650
|
-
|
|
5651
|
-
|
|
5839
|
+
fs16.mkdirSync(path20.dirname(dest), { recursive: true });
|
|
5840
|
+
fs16.writeFileSync(dest, fixturesContents());
|
|
5652
5841
|
}
|
|
5653
5842
|
return {
|
|
5654
5843
|
step,
|
|
@@ -5664,7 +5853,7 @@ function stepEnv(project, opts) {
|
|
|
5664
5853
|
["PIWI_DASHBOARD_URL", opts.serverUrl],
|
|
5665
5854
|
["PIWI_API_KEY", ""]
|
|
5666
5855
|
]);
|
|
5667
|
-
if (example.added.length && !opts.dryRun)
|
|
5856
|
+
if (example.added.length && !opts.dryRun) fs16.writeFileSync(examplePath, example.text);
|
|
5668
5857
|
results.push({
|
|
5669
5858
|
step: "env",
|
|
5670
5859
|
file: ".env.example",
|
|
@@ -5676,7 +5865,7 @@ function stepEnv(project, opts) {
|
|
|
5676
5865
|
const entries = [["PIWI_API_KEY", opts.apiKey]];
|
|
5677
5866
|
if (opts.serverUrl) entries.unshift(["PIWI_DASHBOARD_URL", opts.serverUrl]);
|
|
5678
5867
|
const env = upsertEnvKeys(readFileOr(envPath, ""), entries);
|
|
5679
|
-
if (env.added.length && !opts.dryRun)
|
|
5868
|
+
if (env.added.length && !opts.dryRun) fs16.writeFileSync(envPath, env.text);
|
|
5680
5869
|
results.push({
|
|
5681
5870
|
step: "env",
|
|
5682
5871
|
file: ".env",
|
|
@@ -5685,7 +5874,7 @@ function stepEnv(project, opts) {
|
|
|
5685
5874
|
});
|
|
5686
5875
|
const gitignorePath = path20.join(project.root, ".gitignore");
|
|
5687
5876
|
const gitignore = ensureGitignoreEntry(readFileOr(gitignorePath, ""));
|
|
5688
|
-
if (gitignore.added && !opts.dryRun)
|
|
5877
|
+
if (gitignore.added && !opts.dryRun) fs16.writeFileSync(gitignorePath, gitignore.text);
|
|
5689
5878
|
results.push({
|
|
5690
5879
|
step: "gitignore",
|
|
5691
5880
|
file: ".gitignore",
|
|
@@ -5728,8 +5917,8 @@ async function runInit(argv, env = process.env, cwd = process.cwd()) {
|
|
|
5728
5917
|
return 0;
|
|
5729
5918
|
}
|
|
5730
5919
|
const opts = parseInitArgs(argv, env, cwd);
|
|
5731
|
-
if (opts.desktopApp !== null && !
|
|
5732
|
-
console.error(`--desktop-app must be one of ${
|
|
5920
|
+
if (opts.desktopApp !== null && !DESKTOP_APP_MODES2.includes(opts.desktopApp)) {
|
|
5921
|
+
console.error(`--desktop-app must be one of ${DESKTOP_APP_MODES2.join(", ")} (got "${opts.desktopApp}")`);
|
|
5733
5922
|
return 1;
|
|
5734
5923
|
}
|
|
5735
5924
|
const project = detectProject(opts.root);
|
|
@@ -5786,7 +5975,7 @@ Piwi setup${opts.dryRun ? " (dry run \u2014 nothing written)" : ""} for "${opts.
|
|
|
5786
5975
|
}
|
|
5787
5976
|
|
|
5788
5977
|
// src/cli/select.ts
|
|
5789
|
-
var
|
|
5978
|
+
var fs18 = __toESM(require("fs"));
|
|
5790
5979
|
var path22 = __toESM(require("path"));
|
|
5791
5980
|
var import_node_child_process7 = require("child_process");
|
|
5792
5981
|
|
|
@@ -6308,7 +6497,7 @@ function attributeMatches(value, exact, actual) {
|
|
|
6308
6497
|
|
|
6309
6498
|
// ../core/src/locator-break.ts
|
|
6310
6499
|
var TEXT_KINDS = /* @__PURE__ */ new Set(["text", "translation", "literal"]);
|
|
6311
|
-
function
|
|
6500
|
+
function isText2(anchor) {
|
|
6312
6501
|
return TEXT_KINDS.has(anchor.kind);
|
|
6313
6502
|
}
|
|
6314
6503
|
function hasAttribute(anchor, names) {
|
|
@@ -6407,7 +6596,7 @@ function directSlots(call, testIds) {
|
|
|
6407
6596
|
const hasTextSlot = (index) => {
|
|
6408
6597
|
const hasText = optionsOf(call.args[index]).get("hasText");
|
|
6409
6598
|
if (!hasText) return null;
|
|
6410
|
-
return stringSlot(hasText, false, "text",
|
|
6599
|
+
return stringSlot(hasText, false, "text", isText2, (next) => withOption(call, index, "hasText", next));
|
|
6411
6600
|
};
|
|
6412
6601
|
const textOr = (attribute) => (a) => a.kind === "translation" || a.kind === "literal" || hasAttribute(a, [attribute]);
|
|
6413
6602
|
switch (call.method) {
|
|
@@ -6420,16 +6609,16 @@ function directSlots(call, testIds) {
|
|
|
6420
6609
|
opts.get("name"),
|
|
6421
6610
|
exact,
|
|
6422
6611
|
"name",
|
|
6423
|
-
(a) =>
|
|
6612
|
+
(a) => isText2(a) || hasAttribute(a, ["aria-label", "title", "alt", "value"]),
|
|
6424
6613
|
(next) => withOption(call, 1, "name", next)
|
|
6425
6614
|
)
|
|
6426
6615
|
);
|
|
6427
6616
|
break;
|
|
6428
6617
|
case "getByText":
|
|
6429
|
-
slots.push(argSlot("text",
|
|
6618
|
+
slots.push(argSlot("text", isText2));
|
|
6430
6619
|
break;
|
|
6431
6620
|
case "getByLabel":
|
|
6432
|
-
slots.push(argSlot("text", (a) =>
|
|
6621
|
+
slots.push(argSlot("text", (a) => isText2(a) || hasAttribute(a, ["aria-label"])));
|
|
6433
6622
|
break;
|
|
6434
6623
|
case "getByPlaceholder":
|
|
6435
6624
|
slots.push(argSlot("text", textOr("placeholder")));
|
|
@@ -6632,7 +6821,7 @@ function diffImpactInput(files) {
|
|
|
6632
6821
|
}
|
|
6633
6822
|
|
|
6634
6823
|
// src/cli/add-reporter.ts
|
|
6635
|
-
var
|
|
6824
|
+
var fs17 = __toESM(require("fs"));
|
|
6636
6825
|
var path21 = __toESM(require("path"));
|
|
6637
6826
|
var import_node_module2 = require("module");
|
|
6638
6827
|
var REPORTER_PACKAGE2 = "@piwitests/reporter";
|
|
@@ -6676,7 +6865,7 @@ function resolveConfigPath(cwd, playwrightArgs2) {
|
|
|
6676
6865
|
function readConfigSource(configPath) {
|
|
6677
6866
|
if (!configPath) return null;
|
|
6678
6867
|
try {
|
|
6679
|
-
return
|
|
6868
|
+
return fs17.readFileSync(configPath, "utf-8");
|
|
6680
6869
|
} catch {
|
|
6681
6870
|
return null;
|
|
6682
6871
|
}
|
|
@@ -6711,9 +6900,9 @@ Usage:
|
|
|
6711
6900
|
npx @piwitests/reporter run impact --base <ref> run the tests your diff impacts
|
|
6712
6901
|
|
|
6713
6902
|
Connection:
|
|
6714
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
6715
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
6716
|
-
--project <name|id> Project (env PIWI_PROJECT_NAME)
|
|
6903
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
6904
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
6905
|
+
--project <name|id> Project (env PIWI_PROJECT_NAME, .env)
|
|
6717
6906
|
|
|
6718
6907
|
Selection:
|
|
6719
6908
|
--format <fmt> args (file:line, default) | grep | files | json
|
|
@@ -6770,12 +6959,22 @@ function parseDuration2(raw) {
|
|
|
6770
6959
|
const ms = Math.round(value * factor);
|
|
6771
6960
|
return ms > 0 ? ms : null;
|
|
6772
6961
|
}
|
|
6773
|
-
function parseSelectArgs(argv, env) {
|
|
6962
|
+
function parseSelectArgs(argv, env, dirs = [], command = "piwi select") {
|
|
6774
6963
|
const dashIndex = argv.indexOf("--");
|
|
6775
6964
|
const own = dashIndex === -1 ? argv : argv.slice(0, dashIndex);
|
|
6776
6965
|
const extra = dashIndex === -1 ? [] : argv.slice(dashIndex + 1);
|
|
6777
|
-
const
|
|
6778
|
-
|
|
6966
|
+
const connection = resolveCliConnection(
|
|
6967
|
+
{
|
|
6968
|
+
serverUrl: readOption7(own, "--server-url"),
|
|
6969
|
+
apiKey: readOption7(own, "--api-key"),
|
|
6970
|
+
project: readOption7(own, "--project")
|
|
6971
|
+
},
|
|
6972
|
+
env,
|
|
6973
|
+
dirs,
|
|
6974
|
+
command
|
|
6975
|
+
);
|
|
6976
|
+
if (!connection)
|
|
6977
|
+
throw new Error("No dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
6779
6978
|
const key = findKey(own);
|
|
6780
6979
|
if (!key) throw new Error("No selection key \u2014 usage: piwi select <key>");
|
|
6781
6980
|
const budgetRaw = readOption7(own, "--budget");
|
|
@@ -6789,9 +6988,9 @@ function parseSelectArgs(argv, env) {
|
|
|
6789
6988
|
throw new Error(`--shard expects an "i/n" spec like 2/4, got "${shard}"`);
|
|
6790
6989
|
}
|
|
6791
6990
|
return {
|
|
6792
|
-
serverUrl,
|
|
6793
|
-
apiKey:
|
|
6794
|
-
project:
|
|
6991
|
+
serverUrl: connection.serverUrl,
|
|
6992
|
+
apiKey: connection.apiKey,
|
|
6993
|
+
project: connection.project,
|
|
6795
6994
|
key,
|
|
6796
6995
|
format: readOption7(own, "--format") ?? "args",
|
|
6797
6996
|
budgetMs,
|
|
@@ -6810,7 +7009,7 @@ function cacheKey(args) {
|
|
|
6810
7009
|
}
|
|
6811
7010
|
function readCache(args) {
|
|
6812
7011
|
try {
|
|
6813
|
-
const store = JSON.parse(
|
|
7012
|
+
const store = JSON.parse(fs18.readFileSync(CACHE_FILE, "utf-8"));
|
|
6814
7013
|
return store[cacheKey(args)] ?? null;
|
|
6815
7014
|
} catch {
|
|
6816
7015
|
return null;
|
|
@@ -6820,12 +7019,12 @@ function writeCache(args, resolution) {
|
|
|
6820
7019
|
try {
|
|
6821
7020
|
let store = {};
|
|
6822
7021
|
try {
|
|
6823
|
-
store = JSON.parse(
|
|
7022
|
+
store = JSON.parse(fs18.readFileSync(CACHE_FILE, "utf-8"));
|
|
6824
7023
|
} catch {
|
|
6825
7024
|
}
|
|
6826
7025
|
store[cacheKey(args)] = resolution;
|
|
6827
|
-
|
|
6828
|
-
|
|
7026
|
+
fs18.mkdirSync(path22.dirname(CACHE_FILE), { recursive: true });
|
|
7027
|
+
fs18.writeFileSync(CACHE_FILE, JSON.stringify(store, null, 2));
|
|
6829
7028
|
} catch {
|
|
6830
7029
|
}
|
|
6831
7030
|
}
|
|
@@ -6917,7 +7116,7 @@ async function runSelect(argv, env = process.env) {
|
|
|
6917
7116
|
}
|
|
6918
7117
|
let args;
|
|
6919
7118
|
try {
|
|
6920
|
-
args = parseSelectArgs(argv, env);
|
|
7119
|
+
args = parseSelectArgs(argv, env, [process.cwd()]);
|
|
6921
7120
|
} catch (e) {
|
|
6922
7121
|
console.error(`piwi select: ${e.message}
|
|
6923
7122
|
`);
|
|
@@ -7008,7 +7207,7 @@ async function runRun(argv, env = process.env) {
|
|
|
7008
7207
|
}
|
|
7009
7208
|
let args;
|
|
7010
7209
|
try {
|
|
7011
|
-
args = parseSelectArgs(argv, env);
|
|
7210
|
+
args = parseSelectArgs(argv, env, [process.cwd()], "piwi run");
|
|
7012
7211
|
} catch (e) {
|
|
7013
7212
|
console.error(`piwi run: ${e.message}
|
|
7014
7213
|
`);
|
|
@@ -7058,7 +7257,7 @@ async function runRun(argv, env = process.env) {
|
|
|
7058
7257
|
}
|
|
7059
7258
|
|
|
7060
7259
|
// src/cli/preflight.ts
|
|
7061
|
-
var
|
|
7260
|
+
var fs19 = __toESM(require("fs"));
|
|
7062
7261
|
var path23 = __toESM(require("path"));
|
|
7063
7262
|
var import_node_child_process8 = require("child_process");
|
|
7064
7263
|
|
|
@@ -7124,8 +7323,8 @@ Actions:
|
|
|
7124
7323
|
|
|
7125
7324
|
Connection:
|
|
7126
7325
|
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
7127
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
7128
|
-
--project <name|id> Project (env PIWI_PROJECT_NAME)
|
|
7326
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
7327
|
+
--project <name|id> Project (env PIWI_PROJECT_NAME, .env)
|
|
7129
7328
|
|
|
7130
7329
|
Output:
|
|
7131
7330
|
--json Print the breaks, the edits and the impact as JSON
|
|
@@ -7182,16 +7381,6 @@ function parsePreflightArgs(argv) {
|
|
|
7182
7381
|
extra: dashIndex === -1 ? [] : argv.slice(dashIndex + 1)
|
|
7183
7382
|
};
|
|
7184
7383
|
}
|
|
7185
|
-
var CONFIG_NAMES2 = ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs", "playwright.config.cjs"];
|
|
7186
|
-
function findPlaywrightRoot(from) {
|
|
7187
|
-
let dir = path23.resolve(from);
|
|
7188
|
-
for (; ; ) {
|
|
7189
|
-
if (CONFIG_NAMES2.some((name) => fs18.existsSync(path23.join(dir, name)))) return dir;
|
|
7190
|
-
const parent = path23.dirname(dir);
|
|
7191
|
-
if (parent === dir) return null;
|
|
7192
|
-
dir = parent;
|
|
7193
|
-
}
|
|
7194
|
-
}
|
|
7195
7384
|
function git3(cwd, args) {
|
|
7196
7385
|
return (0, import_node_child_process8.execFileSync)("git", args, {
|
|
7197
7386
|
cwd,
|
|
@@ -7209,7 +7398,12 @@ function tryGit(cwd, args) {
|
|
|
7209
7398
|
}
|
|
7210
7399
|
var TEST_FILE = /(?:^|\/)[^/]+\.(?:spec|test)\.[cm]?[jt]sx?$/;
|
|
7211
7400
|
function resolveConnection(args, env, dirs) {
|
|
7212
|
-
return resolveCliConnection(
|
|
7401
|
+
return resolveCliConnection(
|
|
7402
|
+
{ serverUrl: args.serverUrl, apiKey: args.apiKey, project: args.project },
|
|
7403
|
+
env,
|
|
7404
|
+
dirs,
|
|
7405
|
+
"piwi preflight"
|
|
7406
|
+
);
|
|
7213
7407
|
}
|
|
7214
7408
|
function translationLookup(repoRoot, base, locale) {
|
|
7215
7409
|
const files = (tryGit(repoRoot, ["ls-files"]) ?? "").split("\n").filter((f) => f && isTranslationFile(f)).sort((a, b) => Number(prefersLocale(b, locale)) - Number(prefersLocale(a, locale)) || a.localeCompare(b));
|
|
@@ -7236,7 +7430,7 @@ function prefersLocale(file, locale) {
|
|
|
7236
7430
|
}
|
|
7237
7431
|
function readText(file) {
|
|
7238
7432
|
try {
|
|
7239
|
-
return
|
|
7433
|
+
return fs19.readFileSync(file, "utf-8");
|
|
7240
7434
|
} catch {
|
|
7241
7435
|
return void 0;
|
|
7242
7436
|
}
|
|
@@ -7247,7 +7441,7 @@ function cacheKey2(connection, branch) {
|
|
|
7247
7441
|
}
|
|
7248
7442
|
function readCache2(root, key) {
|
|
7249
7443
|
try {
|
|
7250
|
-
const store = JSON.parse(
|
|
7444
|
+
const store = JSON.parse(fs19.readFileSync(path23.join(root, CACHE_FILE2), "utf-8"));
|
|
7251
7445
|
return store[key] ?? null;
|
|
7252
7446
|
} catch {
|
|
7253
7447
|
return null;
|
|
@@ -7258,12 +7452,12 @@ function writeCache2(root, key, index) {
|
|
|
7258
7452
|
try {
|
|
7259
7453
|
let store = {};
|
|
7260
7454
|
try {
|
|
7261
|
-
store = JSON.parse(
|
|
7455
|
+
store = JSON.parse(fs19.readFileSync(file, "utf-8"));
|
|
7262
7456
|
} catch {
|
|
7263
7457
|
}
|
|
7264
7458
|
store[key] = { savedAt: (/* @__PURE__ */ new Date()).toISOString(), index };
|
|
7265
|
-
|
|
7266
|
-
|
|
7459
|
+
fs19.mkdirSync(path23.dirname(file), { recursive: true });
|
|
7460
|
+
fs19.writeFileSync(file, JSON.stringify(store));
|
|
7267
7461
|
} catch {
|
|
7268
7462
|
}
|
|
7269
7463
|
}
|
|
@@ -7322,7 +7516,7 @@ function applyEdits(breaks, replacementsOf) {
|
|
|
7322
7516
|
}
|
|
7323
7517
|
const edited = [];
|
|
7324
7518
|
for (const [file, edits] of byFile) {
|
|
7325
|
-
const text =
|
|
7519
|
+
const text = fs19.readFileSync(file, "utf-8");
|
|
7326
7520
|
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
7327
7521
|
const lines = text.split(/\r?\n/);
|
|
7328
7522
|
for (const e of edits) {
|
|
@@ -7332,7 +7526,7 @@ function applyEdits(breaks, replacementsOf) {
|
|
|
7332
7526
|
}
|
|
7333
7527
|
const next = lines.join(eol);
|
|
7334
7528
|
if (next !== text) {
|
|
7335
|
-
|
|
7529
|
+
fs19.writeFileSync(file, next);
|
|
7336
7530
|
edited.push(file);
|
|
7337
7531
|
}
|
|
7338
7532
|
}
|
|
@@ -7443,7 +7637,7 @@ async function runPreflight(argv, env = process.env, cwd = process.cwd()) {
|
|
|
7443
7637
|
console.error("piwi preflight: not inside a git repository");
|
|
7444
7638
|
return EXIT_ERROR8;
|
|
7445
7639
|
}
|
|
7446
|
-
const testRoot = path23.resolve(cwd, args.testRoot ?? findPlaywrightRoot(cwd) ?? cwd);
|
|
7640
|
+
const testRoot = path23.resolve(cwd, args.testRoot ?? findPlaywrightRoot(cwd, repoRoot) ?? cwd);
|
|
7447
7641
|
let files;
|
|
7448
7642
|
try {
|
|
7449
7643
|
files = parseUnifiedDiff(
|
|
@@ -7569,7 +7763,7 @@ async function runPreflight(argv, env = process.env, cwd = process.cwd()) {
|
|
|
7569
7763
|
}
|
|
7570
7764
|
|
|
7571
7765
|
// src/cli/probe.ts
|
|
7572
|
-
var
|
|
7766
|
+
var fs20 = __toESM(require("fs"));
|
|
7573
7767
|
var os3 = __toESM(require("os"));
|
|
7574
7768
|
var path24 = __toESM(require("path"));
|
|
7575
7769
|
var EXIT_OK8 = 0;
|
|
@@ -7581,9 +7775,9 @@ Usage:
|
|
|
7581
7775
|
npx @piwitests/reporter probe [options] [-- <playwright args>]
|
|
7582
7776
|
|
|
7583
7777
|
Options:
|
|
7584
|
-
--server-url <url> Dashboard URL (or PIWI_DASHBOARD_URL)
|
|
7585
|
-
--api-key <key> Reporter API key (or PIWI_API_KEY)
|
|
7586
|
-
--project <name|id> Project name or id (or PIWI_PROJECT_NAME)
|
|
7778
|
+
--server-url <url> Dashboard URL (or PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
7779
|
+
--api-key <key> Reporter API key (or PIWI_API_KEY, .env)
|
|
7780
|
+
--project <name|id> Project name or id (or PIWI_PROJECT_NAME, .env)
|
|
7587
7781
|
--budget <n> Max pairs to probe this run (default the server's)
|
|
7588
7782
|
|
|
7589
7783
|
Everything after \`--\` is passed to \`playwright test\`.
|
|
@@ -7604,13 +7798,21 @@ async function runProbe(argv) {
|
|
|
7604
7798
|
return EXIT_OK8;
|
|
7605
7799
|
}
|
|
7606
7800
|
const env = process.env;
|
|
7607
|
-
const
|
|
7608
|
-
|
|
7609
|
-
|
|
7801
|
+
const connection = resolveCliConnection(
|
|
7802
|
+
{
|
|
7803
|
+
serverUrl: readOption9(own, "--server-url"),
|
|
7804
|
+
apiKey: readOption9(own, "--api-key"),
|
|
7805
|
+
project: readOption9(own, "--project")
|
|
7806
|
+
},
|
|
7807
|
+
env,
|
|
7808
|
+
[process.cwd()],
|
|
7809
|
+
"piwi probe"
|
|
7810
|
+
);
|
|
7811
|
+
if (!connection) {
|
|
7812
|
+
console.error("piwi probe: no dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
7610
7813
|
return EXIT_ERROR9;
|
|
7611
7814
|
}
|
|
7612
|
-
const
|
|
7613
|
-
const project = readOption9(own, "--project") ?? env.PIWI_PROJECT_NAME ?? "";
|
|
7815
|
+
const { serverUrl, apiKey, project } = connection;
|
|
7614
7816
|
const budget = readOption9(own, "--budget");
|
|
7615
7817
|
let projectId;
|
|
7616
7818
|
try {
|
|
@@ -7635,12 +7837,12 @@ async function runProbe(argv) {
|
|
|
7635
7837
|
return EXIT_OK8;
|
|
7636
7838
|
}
|
|
7637
7839
|
console.error(`piwi probe: ${plan.items.length} pair(s) to probe (budget ${plan.budget}).`);
|
|
7638
|
-
const dir =
|
|
7840
|
+
const dir = fs20.mkdtempSync(path24.join(os3.tmpdir(), "piwi-probe-"));
|
|
7639
7841
|
const planFile = path24.join(dir, "plan.json");
|
|
7640
7842
|
const resultsFile = path24.join(dir, "results.jsonl");
|
|
7641
7843
|
const runOutputFile = path24.join(dir, "run.json");
|
|
7642
|
-
|
|
7643
|
-
|
|
7844
|
+
fs20.writeFileSync(planFile, JSON.stringify(plan));
|
|
7845
|
+
fs20.writeFileSync(resultsFile, "");
|
|
7644
7846
|
const grep = plan.items.map((i) => i.testTitle).filter(Boolean).map(escapeRegex2).join("|");
|
|
7645
7847
|
const pwArgs = [...playwrightArgs2];
|
|
7646
7848
|
if (grep && !playwrightArgs2.includes("--grep") && !playwrightArgs2.includes("-g")) pwArgs.push("--grep", grep);
|
|
@@ -7654,7 +7856,7 @@ async function runProbe(argv) {
|
|
|
7654
7856
|
[PIWI_ENV_KEYS.outputFile]: runOutputFile
|
|
7655
7857
|
};
|
|
7656
7858
|
await spawnPlaywright(pwArgs, childEnv, { command: "piwi probe" });
|
|
7657
|
-
const lines =
|
|
7859
|
+
const lines = fs20.readFileSync(resultsFile, "utf8").split("\n").filter(Boolean);
|
|
7658
7860
|
const results = [];
|
|
7659
7861
|
for (const line of lines) {
|
|
7660
7862
|
try {
|
|
@@ -7668,7 +7870,7 @@ async function runProbe(argv) {
|
|
|
7668
7870
|
}
|
|
7669
7871
|
let runId = null;
|
|
7670
7872
|
try {
|
|
7671
|
-
const out = JSON.parse(
|
|
7873
|
+
const out = JSON.parse(fs20.readFileSync(runOutputFile, "utf8"));
|
|
7672
7874
|
if (typeof out.runId === "number") runId = out.runId;
|
|
7673
7875
|
} catch {
|
|
7674
7876
|
}
|
|
@@ -7693,7 +7895,7 @@ async function runProbe(argv) {
|
|
|
7693
7895
|
}
|
|
7694
7896
|
|
|
7695
7897
|
// src/cli/quality-report.ts
|
|
7696
|
-
var
|
|
7898
|
+
var fs21 = __toESM(require("fs"));
|
|
7697
7899
|
var EXIT_OK9 = 0;
|
|
7698
7900
|
var EXIT_VERDICT = 1;
|
|
7699
7901
|
var EXIT_ERROR10 = 2;
|
|
@@ -7706,8 +7908,8 @@ Usage:
|
|
|
7706
7908
|
npx @piwitests/reporter report [options]
|
|
7707
7909
|
|
|
7708
7910
|
Connection:
|
|
7709
|
-
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL)
|
|
7710
|
-
--api-key <key> API key (env PIWI_API_KEY)
|
|
7911
|
+
--server-url <url> Dashboard URL (env PIWI_DASHBOARD_URL, .env, the desktop app)
|
|
7912
|
+
--api-key <key> API key (env PIWI_API_KEY, .env)
|
|
7711
7913
|
|
|
7712
7914
|
What to report:
|
|
7713
7915
|
--project <names|ids> Comma-separated projects (default: every project you can see)
|
|
@@ -7739,9 +7941,15 @@ function normalizePeriod(raw) {
|
|
|
7739
7941
|
const days = raw.trim().match(/^(\d+)d$/);
|
|
7740
7942
|
return days ? `last-${days[1]}d` : raw.trim();
|
|
7741
7943
|
}
|
|
7742
|
-
function parseReportArgs(argv, env) {
|
|
7743
|
-
const
|
|
7744
|
-
|
|
7944
|
+
function parseReportArgs(argv, env, dirs = []) {
|
|
7945
|
+
const connection = resolveCliConnection(
|
|
7946
|
+
{ serverUrl: readOption10(argv, "--server-url"), apiKey: readOption10(argv, "--api-key") },
|
|
7947
|
+
env,
|
|
7948
|
+
dirs,
|
|
7949
|
+
"piwi report"
|
|
7950
|
+
);
|
|
7951
|
+
if (!connection)
|
|
7952
|
+
throw new Error("No dashboard URL \u2014 pass --server-url or set PIWI_DASHBOARD_URL (environment or .env)");
|
|
7745
7953
|
const format = (readOption10(argv, "--format") ?? "md").toLowerCase();
|
|
7746
7954
|
if (!FORMATS.includes(format)) {
|
|
7747
7955
|
throw new Error(`--format must be one of ${FORMATS.join(", ")}, got "${format}"`);
|
|
@@ -7761,8 +7969,8 @@ function parseReportArgs(argv, env) {
|
|
|
7761
7969
|
const output = readOption10(argv, "--output") ?? null;
|
|
7762
7970
|
if (format === "pdf" && !output) throw new Error("A PDF needs --output <file>");
|
|
7763
7971
|
return {
|
|
7764
|
-
serverUrl,
|
|
7765
|
-
apiKey:
|
|
7972
|
+
serverUrl: connection.serverUrl,
|
|
7973
|
+
apiKey: connection.apiKey,
|
|
7766
7974
|
projects: (readOption10(argv, "--project") ?? "").split(",").map((p) => p.trim()).filter(Boolean),
|
|
7767
7975
|
period: normalizePeriod(readOption10(argv, "--period") ?? "30d"),
|
|
7768
7976
|
compare: readOption10(argv, "--compare") ?? null,
|
|
@@ -7806,14 +8014,14 @@ async function runQualityReport(argv) {
|
|
|
7806
8014
|
return EXIT_OK9;
|
|
7807
8015
|
}
|
|
7808
8016
|
try {
|
|
7809
|
-
const args = parseReportArgs(argv, process.env);
|
|
8017
|
+
const args = parseReportArgs(argv, process.env, [process.cwd()]);
|
|
7810
8018
|
const projectIds = [];
|
|
7811
8019
|
for (const project of args.projects) {
|
|
7812
8020
|
projectIds.push(await resolveProjectId({ serverUrl: args.serverUrl, apiKey: args.apiKey, project, key: "" }));
|
|
7813
8021
|
}
|
|
7814
8022
|
const res = await fetchReport(args, reportQuery(args, projectIds, args.format));
|
|
7815
8023
|
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
7816
|
-
if (args.output)
|
|
8024
|
+
if (args.output) fs21.writeFileSync(args.output, bytes);
|
|
7817
8025
|
else process.stdout.write(bytes);
|
|
7818
8026
|
if (!args.failOn) return EXIT_OK9;
|
|
7819
8027
|
const bundle = args.format === "json" ? JSON.parse(new TextDecoder().decode(bytes)) : await (await fetchReport(args, reportQuery(args, projectIds, "json"))).json();
|