@plune-ai/cli 0.13.0 → 0.14.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.cjs +275 -59
- package/dist/reporter-core.cjs +207 -24
- package/dist/reporter-core.d.cts +98 -11
- package/dist/reporter-core.d.ts +98 -11
- package/dist/reporter-core.js +202 -23
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -512,7 +512,7 @@ var init_retry = __esm({
|
|
|
512
512
|
"use strict";
|
|
513
513
|
init_cjs_shims();
|
|
514
514
|
init_errors2();
|
|
515
|
-
defaultSleep = (ms) => new Promise((
|
|
515
|
+
defaultSleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
516
516
|
}
|
|
517
517
|
});
|
|
518
518
|
|
|
@@ -2186,6 +2186,35 @@ var init_credentials = __esm({
|
|
|
2186
2186
|
});
|
|
2187
2187
|
|
|
2188
2188
|
// src/reporter-core/client.ts
|
|
2189
|
+
function nodeTransport(url, init, idleMs = IDLE_MS) {
|
|
2190
|
+
return new Promise((resolve8, reject) => {
|
|
2191
|
+
const target = new URL(url);
|
|
2192
|
+
const send = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
|
|
2193
|
+
const req = send(target, { method: init.method, headers: init.headers }, (res) => {
|
|
2194
|
+
const chunks = [];
|
|
2195
|
+
res.on("data", (chunk2) => chunks.push(chunk2));
|
|
2196
|
+
res.on("error", reject);
|
|
2197
|
+
res.on("end", () => {
|
|
2198
|
+
const status = res.statusCode ?? 0;
|
|
2199
|
+
const text2 = Buffer.concat(chunks).toString("utf8");
|
|
2200
|
+
resolve8({
|
|
2201
|
+
ok: status >= 200 && status <= 299,
|
|
2202
|
+
status,
|
|
2203
|
+
headers: {
|
|
2204
|
+
get: (name) => {
|
|
2205
|
+
const value2 = res.headers[name.toLowerCase()];
|
|
2206
|
+
return value2 === void 0 ? null : Array.isArray(value2) ? value2.join(", ") : value2;
|
|
2207
|
+
}
|
|
2208
|
+
},
|
|
2209
|
+
json: async () => JSON.parse(text2)
|
|
2210
|
+
});
|
|
2211
|
+
});
|
|
2212
|
+
});
|
|
2213
|
+
req.setTimeout(idleMs, () => req.destroy(new Error(`no answer from ${target.origin} in ${idleMs} ms`)));
|
|
2214
|
+
req.on("error", reject);
|
|
2215
|
+
req.end(init.body);
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2189
2218
|
async function detailOf(res) {
|
|
2190
2219
|
try {
|
|
2191
2220
|
const body = await res.json();
|
|
@@ -2208,7 +2237,7 @@ function classify3(status) {
|
|
|
2208
2237
|
return "other";
|
|
2209
2238
|
}
|
|
2210
2239
|
function createClient(opts) {
|
|
2211
|
-
const doFetch = opts.fetchImpl ??
|
|
2240
|
+
const doFetch = opts.fetchImpl ?? nodeTransport;
|
|
2212
2241
|
const wait = opts.wait ?? sleep;
|
|
2213
2242
|
const base = opts.apiUrl.replace(/\/+$/, "");
|
|
2214
2243
|
const scrub = (text2) => opts.token === "" ? text2 : text2.split(opts.token).join("***");
|
|
@@ -2253,12 +2282,15 @@ function createClient(opts) {
|
|
|
2253
2282
|
del: (path14) => send("DELETE", path14)
|
|
2254
2283
|
};
|
|
2255
2284
|
}
|
|
2256
|
-
var RETRY_DELAYS_MS, sleep;
|
|
2285
|
+
var import_node_http, import_node_https, RETRY_DELAYS_MS, IDLE_MS, sleep;
|
|
2257
2286
|
var init_client = __esm({
|
|
2258
2287
|
"src/reporter-core/client.ts"() {
|
|
2259
2288
|
"use strict";
|
|
2260
2289
|
init_cjs_shims();
|
|
2290
|
+
import_node_http = require("http");
|
|
2291
|
+
import_node_https = require("https");
|
|
2261
2292
|
RETRY_DELAYS_MS = [300, 900];
|
|
2293
|
+
IDLE_MS = 3e5;
|
|
2262
2294
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2263
2295
|
}
|
|
2264
2296
|
});
|
|
@@ -2367,6 +2399,23 @@ function boundKey(key) {
|
|
|
2367
2399
|
function boundTitle(title) {
|
|
2368
2400
|
return title.length <= TITLE_MAX ? title : `${title.slice(0, TITLE_MAX - 1)}\u2026`;
|
|
2369
2401
|
}
|
|
2402
|
+
function packed(results) {
|
|
2403
|
+
const out = [];
|
|
2404
|
+
let part = [];
|
|
2405
|
+
let bytes = ENVELOPE_BYTES;
|
|
2406
|
+
for (const result of results) {
|
|
2407
|
+
const size = Buffer.byteLength(JSON.stringify(result));
|
|
2408
|
+
if (part.length > 0 && bytes + 1 + size > BATCH_BYTES) {
|
|
2409
|
+
out.push(part);
|
|
2410
|
+
part = [];
|
|
2411
|
+
bytes = ENVELOPE_BYTES;
|
|
2412
|
+
}
|
|
2413
|
+
bytes += (part.length > 0 ? 1 : 0) + size;
|
|
2414
|
+
part.push(result);
|
|
2415
|
+
}
|
|
2416
|
+
if (part.length > 0) out.push(part);
|
|
2417
|
+
return out;
|
|
2418
|
+
}
|
|
2370
2419
|
function chunk(items, size) {
|
|
2371
2420
|
const out = [];
|
|
2372
2421
|
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
@@ -2568,27 +2617,32 @@ async function startRun(passed, declared) {
|
|
|
2568
2617
|
progress();
|
|
2569
2618
|
return;
|
|
2570
2619
|
}
|
|
2571
|
-
const
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2620
|
+
for (const part of packed(submissions)) {
|
|
2621
|
+
if (offline) {
|
|
2622
|
+
defer(part);
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
const out = await client.post(`/v1/runs/${runId}/results`, {
|
|
2626
|
+
results: part
|
|
2627
|
+
});
|
|
2628
|
+
if (out.ok) {
|
|
2629
|
+
stats.accepted += out.body.counts.accepted ?? 0;
|
|
2630
|
+
stats.duplicate += out.body.counts.duplicate ?? 0;
|
|
2631
|
+
stats.conflict += out.body.counts.conflict ?? 0;
|
|
2632
|
+
stats.rejected += out.body.counts.rejected ?? 0;
|
|
2633
|
+
progress();
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
if (out.kind === "auth") {
|
|
2637
|
+
log('plune: the API token was refused \u2014 run "plune login" with a fresh one.');
|
|
2638
|
+
offline = true;
|
|
2639
|
+
} else if (out.kind === "conflict") {
|
|
2640
|
+
log(`plune: this run is closed \u2014 ${out.detail}`);
|
|
2641
|
+
} else {
|
|
2642
|
+
log(`plune: could not send results (${out.detail || out.kind}).`);
|
|
2643
|
+
}
|
|
2644
|
+
defer(part);
|
|
2590
2645
|
}
|
|
2591
|
-
defer(submissions);
|
|
2592
2646
|
}
|
|
2593
2647
|
function progress() {
|
|
2594
2648
|
const total = expected?.length ?? 0;
|
|
@@ -2604,9 +2658,12 @@ async function startRun(passed, declared) {
|
|
|
2604
2658
|
if (stats.unresolved > 0) parts.push(`${stats.unresolved} with no matching test case`);
|
|
2605
2659
|
if (stats.offered > 0) parts.push(`${stats.offered} offered for review`);
|
|
2606
2660
|
if (stats.created > 0) parts.push(`${stats.created} added as cases (trusted source)`);
|
|
2607
|
-
if (stats.deferred > 0) parts.push(`${stats.deferred} written to ${fallbackPath}`);
|
|
2661
|
+
if (stats.deferred > 0) parts.push(`${stats.deferred} not delivered \u2014 written to ${fallbackPath}`);
|
|
2608
2662
|
if (shortened.size > 0) parts.push(`${shortened.size} test name(s) shortened to fit a key`);
|
|
2609
2663
|
log(`plune: ${parts.join(" \xB7 ")}`);
|
|
2664
|
+
if (stats.deferred > 0 && process.env["GITHUB_ACTIONS"] === "true") {
|
|
2665
|
+
log(`::warning::${stats.deferred} result(s) were not delivered to Plune \u2014 see the reporting step's log.`);
|
|
2666
|
+
}
|
|
2610
2667
|
}
|
|
2611
2668
|
return {
|
|
2612
2669
|
get runId() {
|
|
@@ -2652,7 +2709,7 @@ async function startRun(passed, declared) {
|
|
|
2652
2709
|
}
|
|
2653
2710
|
};
|
|
2654
2711
|
}
|
|
2655
|
-
var import_node_crypto2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, EXPECTED_MAX, KEY_MAX, TITLE_MAX;
|
|
2712
|
+
var import_node_crypto2, RESOLVE_CHUNK, BATCH_MAX, DISCOVER_MAX, BATCH_DEFAULT, EXPECTED_MAX, KEY_MAX, TITLE_MAX, BATCH_BYTES, ENVELOPE_BYTES;
|
|
2656
2713
|
var init_session = __esm({
|
|
2657
2714
|
"src/reporter-core/session.ts"() {
|
|
2658
2715
|
"use strict";
|
|
@@ -2670,6 +2727,8 @@ var init_session = __esm({
|
|
|
2670
2727
|
EXPECTED_MAX = 1e4;
|
|
2671
2728
|
KEY_MAX = 1024;
|
|
2672
2729
|
TITLE_MAX = 300;
|
|
2730
|
+
BATCH_BYTES = 8 * 1024 * 1024;
|
|
2731
|
+
ENVELOPE_BYTES = Buffer.byteLength(JSON.stringify({ results: [] }));
|
|
2673
2732
|
}
|
|
2674
2733
|
});
|
|
2675
2734
|
|
|
@@ -2765,14 +2824,14 @@ async function handleRunExec(options) {
|
|
|
2765
2824
|
if (command === void 0) throw new RunCommandError("Nothing to run \u2014 pass a command after `--`.");
|
|
2766
2825
|
const run = await handleRunStart(options);
|
|
2767
2826
|
const spawnImpl = options.spawnImpl ?? import_node_child_process.spawn;
|
|
2768
|
-
const code = await new Promise((
|
|
2827
|
+
const code = await new Promise((resolve8) => {
|
|
2769
2828
|
const child = spawnImpl(command, args, {
|
|
2770
2829
|
stdio: "inherit",
|
|
2771
2830
|
shell: process.platform === "win32",
|
|
2772
2831
|
env: { ...process.env, PLUNE_RUN: run.externalKey, PLUNE_PROCEED: "1" }
|
|
2773
2832
|
});
|
|
2774
|
-
child.on("close", (exitCode) =>
|
|
2775
|
-
child.on("error", () =>
|
|
2833
|
+
child.on("close", (exitCode) => resolve8(exitCode ?? 1));
|
|
2834
|
+
child.on("error", () => resolve8(1));
|
|
2776
2835
|
});
|
|
2777
2836
|
await handleRunFinish(run.id, options);
|
|
2778
2837
|
return code;
|
|
@@ -3104,21 +3163,163 @@ var init_junit = __esm({
|
|
|
3104
3163
|
}
|
|
3105
3164
|
});
|
|
3106
3165
|
|
|
3166
|
+
// src/reporter-core/failure-detail.ts
|
|
3167
|
+
function failureOf(attempt) {
|
|
3168
|
+
const error = chosen(attempt);
|
|
3169
|
+
const headline = error === void 0 ? void 0 : firstLine(error.text);
|
|
3170
|
+
const steps = chainOf(attempt.steps);
|
|
3171
|
+
const location = error === void 0 ? void 0 : fromRoot(placeOf(error, attempt.testFile), attempt.repoRoot);
|
|
3172
|
+
const artifacts = attempt.attachments.filter((a) => a.path !== void 0 && a.name !== "" && !a.name.startsWith("_") && a.contentType !== METADATA_TYPE).map(({ name, contentType }) => contentType === void 0 ? { name } : { name, contentType });
|
|
3173
|
+
const ciUrl = webLink(attempt.buildHref);
|
|
3174
|
+
const failure = {
|
|
3175
|
+
...headline !== void 0 ? { headline } : {},
|
|
3176
|
+
...steps.length > 0 ? { steps } : {},
|
|
3177
|
+
...location !== void 0 ? { location } : {},
|
|
3178
|
+
...artifacts.length > 0 ? { artifacts } : {},
|
|
3179
|
+
...ciUrl !== void 0 ? { ciUrl } : {}
|
|
3180
|
+
};
|
|
3181
|
+
return Object.keys(failure).length === 0 ? void 0 : failure;
|
|
3182
|
+
}
|
|
3183
|
+
function repoRootOf(dir, stopAt) {
|
|
3184
|
+
for (let here = (0, import_node_path2.resolve)(dir); ; here = (0, import_node_path2.dirname)(here)) {
|
|
3185
|
+
if ((0, import_node_fs2.existsSync)((0, import_node_path2.join)(here, ".git"))) return here;
|
|
3186
|
+
if (here === stopAt || (0, import_node_path2.dirname)(here) === here) return void 0;
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
function errorContextOf(attempt, home = (0, import_node_os2.homedir)()) {
|
|
3190
|
+
let text2 = (0, import_node_util.stripVTControlCharacters)(
|
|
3191
|
+
attempt.errors.map((e) => e.text).filter((t) => t !== "").join("\n\n")
|
|
3192
|
+
).replaceAll("\x1B", "");
|
|
3193
|
+
const root = attempt.repoRoot === void 0 ? void 0 : pathIn(attempt.repoRoot);
|
|
3194
|
+
if (root !== void 0) text2 = text2.replace(new RegExp(`${root.source}[\\\\/]`, root.flags), "");
|
|
3195
|
+
const own = pathIn(home);
|
|
3196
|
+
if (own !== void 0) text2 = text2.replace(new RegExp(`${own.source}(?![\\w.-])`, own.flags), "~");
|
|
3197
|
+
if (attempt.repoRoot === void 0) text2 = text2.replace(ANY_HOME, "~");
|
|
3198
|
+
return cut(text2);
|
|
3199
|
+
}
|
|
3200
|
+
function pathIn(path14) {
|
|
3201
|
+
const parts = path14.split(/[\\/]+/);
|
|
3202
|
+
while (parts.length > 1 && parts.at(-1) === "") parts.pop();
|
|
3203
|
+
if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
|
|
3204
|
+
const escaped = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]");
|
|
3205
|
+
return new RegExp(`(?:file:\\/\\/\\/?)?${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
|
|
3206
|
+
}
|
|
3207
|
+
function cut(text2) {
|
|
3208
|
+
if (Buffer.byteLength(text2) <= TEXT_LIMIT) return text2;
|
|
3209
|
+
const lines = text2.split("\n");
|
|
3210
|
+
const size = lines.map((line) => Buffer.byteLength(line) + 1);
|
|
3211
|
+
const budget = TEXT_LIMIT - Buffer.byteLength(omitted(lines.length));
|
|
3212
|
+
let used = 0;
|
|
3213
|
+
let head = 0;
|
|
3214
|
+
let tail = lines.length;
|
|
3215
|
+
while (head < tail && used + size[head] <= budget / 2) used += size[head++];
|
|
3216
|
+
while (tail > head && used + size[tail - 1] <= budget) used += size[--tail];
|
|
3217
|
+
return [...lines.slice(0, head), omitted(tail - head), ...lines.slice(tail)].join("\n");
|
|
3218
|
+
}
|
|
3219
|
+
function chosen({ status, errors }) {
|
|
3220
|
+
if (status === "timedOut") {
|
|
3221
|
+
const timeout = errors.findIndex((e) => firstLine(e.text)?.startsWith("Test timeout of ") === true);
|
|
3222
|
+
if (timeout !== -1 && timeout + 1 < errors.length) return errors[timeout + 1];
|
|
3223
|
+
}
|
|
3224
|
+
return errors[0];
|
|
3225
|
+
}
|
|
3226
|
+
function chainOf(steps) {
|
|
3227
|
+
const chain = [];
|
|
3228
|
+
for (let failed = steps.find((s) => s.failed); failed !== void 0; failed = failed.steps.find((s) => s.failed)) {
|
|
3229
|
+
chain.push(failed.title);
|
|
3230
|
+
}
|
|
3231
|
+
return chain;
|
|
3232
|
+
}
|
|
3233
|
+
function placeOf(error, testFile) {
|
|
3234
|
+
const own = normal(testFile);
|
|
3235
|
+
let first;
|
|
3236
|
+
for (const line of (0, import_node_util.stripVTControlCharacters)(error.text).split("\n")) {
|
|
3237
|
+
const frame = FRAME.exec(line);
|
|
3238
|
+
if (frame === null) continue;
|
|
3239
|
+
const place = { file: fileOf(frame[1]), line: Number(frame[2]), column: Number(frame[3]) };
|
|
3240
|
+
if (normal(place.file) === own) return place;
|
|
3241
|
+
if (first === void 0 && !normal(place.file).includes("/node_modules/")) first = place;
|
|
3242
|
+
}
|
|
3243
|
+
return error.location ?? first;
|
|
3244
|
+
}
|
|
3245
|
+
function fileOf(raw) {
|
|
3246
|
+
if (!raw.startsWith("file://")) return raw;
|
|
3247
|
+
const path14 = decodeURIComponent(raw.slice("file://".length));
|
|
3248
|
+
return /^\/[A-Za-z]:\//.test(path14) ? path14.slice(1) : path14;
|
|
3249
|
+
}
|
|
3250
|
+
function fromRoot(place, root) {
|
|
3251
|
+
if (place === void 0 || root === void 0 || !Number.isInteger(place.line) || place.line < 1) return void 0;
|
|
3252
|
+
const base = normal(root).replace(/\/+$/, "");
|
|
3253
|
+
const file = normal(place.file);
|
|
3254
|
+
if (!file.startsWith(`${base}/`)) return void 0;
|
|
3255
|
+
const relative2 = file.slice(base.length + 1);
|
|
3256
|
+
if (!REPO_RELATIVE.test(relative2)) return void 0;
|
|
3257
|
+
const column = place.column !== void 0 && Number.isInteger(place.column) && place.column >= 1 ? place.column : void 0;
|
|
3258
|
+
return column === void 0 ? { file: relative2, line: place.line } : { file: relative2, line: place.line, column };
|
|
3259
|
+
}
|
|
3260
|
+
function webLink(href) {
|
|
3261
|
+
if (href === void 0) return void 0;
|
|
3262
|
+
try {
|
|
3263
|
+
const { protocol } = new URL(href);
|
|
3264
|
+
return protocol === "http:" || protocol === "https:" ? href : void 0;
|
|
3265
|
+
} catch {
|
|
3266
|
+
return void 0;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
var import_node_fs2, import_node_os2, import_node_path2, import_node_util, TEXT_LIMIT, ANY_HOME, omitted, METADATA_TYPE, REPO_RELATIVE, FRAME, firstLine, normal;
|
|
3270
|
+
var init_failure_detail = __esm({
|
|
3271
|
+
"src/reporter-core/failure-detail.ts"() {
|
|
3272
|
+
"use strict";
|
|
3273
|
+
init_cjs_shims();
|
|
3274
|
+
import_node_fs2 = require("fs");
|
|
3275
|
+
import_node_os2 = require("os");
|
|
3276
|
+
import_node_path2 = require("path");
|
|
3277
|
+
import_node_util = require("util");
|
|
3278
|
+
TEXT_LIMIT = 524288;
|
|
3279
|
+
ANY_HOME = /(?<=^|[\s'"`(=,[])(?:file:\/\/\/?)?(?:\/home\/|\/Users\/|[A-Za-z]:[\\/]Users[\\/])[^\\/\s'"`:*?<>|]+/g;
|
|
3280
|
+
omitted = (lines) => `\u2026[omitted ${lines} lines]\u2026`;
|
|
3281
|
+
METADATA_TYPE = "application/plune.metadata+json";
|
|
3282
|
+
REPO_RELATIVE = /^(?![A-Za-z][A-Za-z0-9+.-]*:)(?![\\/~])(?!(?:.*[\\/])?\.\.(?:[\\/]|$)).+$/;
|
|
3283
|
+
FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
|
|
3284
|
+
firstLine = (text2) => (0, import_node_util.stripVTControlCharacters)(text2).split("\n").map((line) => line.trim()).find((line) => line !== "");
|
|
3285
|
+
normal = (path14) => import_node_path2.posix.normalize(path14.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
3286
|
+
}
|
|
3287
|
+
});
|
|
3288
|
+
|
|
3107
3289
|
// src/importers/playwright-json.ts
|
|
3108
3290
|
function statedIdOf(test, title) {
|
|
3109
3291
|
const annotation = test.annotations?.find((a) => a.type === "PluneId")?.description;
|
|
3110
3292
|
if (annotation !== void 0 && annotation !== "") return annotation;
|
|
3111
3293
|
return TOKEN.exec(title)?.[1];
|
|
3112
3294
|
}
|
|
3113
|
-
function
|
|
3114
|
-
|
|
3295
|
+
function attemptOf(result, file, ctx) {
|
|
3296
|
+
const steps = (list2) => (list2 ?? []).map((s) => ({ title: s.title ?? "", failed: s.error !== void 0, steps: steps(s.steps) }));
|
|
3297
|
+
return {
|
|
3298
|
+
status: result.status ?? "unknown",
|
|
3299
|
+
errors: (result.errors ?? []).map(({ message, value: value2, location: at }) => ({
|
|
3300
|
+
text: message ?? value2 ?? "",
|
|
3301
|
+
...typeof at?.file === "string" && typeof at.line === "number" ? { location: { file: at.file, line: at.line, ...typeof at.column === "number" ? { column: at.column } : {} } } : {}
|
|
3302
|
+
})),
|
|
3303
|
+
steps: steps(result.steps),
|
|
3304
|
+
attachments: (result.attachments ?? []).map(({ name, contentType, path: path14 }) => ({
|
|
3305
|
+
name: name ?? "",
|
|
3306
|
+
...contentType !== void 0 ? { contentType } : {},
|
|
3307
|
+
...path14 !== void 0 ? { path: path14 } : {}
|
|
3308
|
+
})),
|
|
3309
|
+
...ctx.buildHref !== void 0 ? { buildHref: ctx.buildHref } : {},
|
|
3310
|
+
// Spec files are posix and relative to the root dir; the core reads either slash.
|
|
3311
|
+
testFile: ctx.rootDir === void 0 ? file : `${ctx.rootDir.replace(/[\\/]+$/, "")}/${file}`,
|
|
3312
|
+
...ctx.repoRoot !== void 0 ? { repoRoot: ctx.repoRoot } : {}
|
|
3313
|
+
};
|
|
3115
3314
|
}
|
|
3116
|
-
function pendingFrom(spec, test, result, file, titles) {
|
|
3315
|
+
function pendingFrom(spec, test, result, file, titles, ctx) {
|
|
3117
3316
|
const pathTitle = [file, ...titles].join("#");
|
|
3118
3317
|
const seed = test.id ?? pathTitle;
|
|
3119
3318
|
const stated = statedIdOf(test, spec.title ?? "");
|
|
3120
3319
|
const expected = test.expectedStatus;
|
|
3121
|
-
const
|
|
3320
|
+
const attempt = attemptOf(result, file, ctx);
|
|
3321
|
+
const errorContext = errorContextOf(attempt);
|
|
3322
|
+
const failure = NOT_FAILED.has(attempt.status) ? void 0 : failureOf(attempt);
|
|
3122
3323
|
const keys = [
|
|
3123
3324
|
...test.id !== void 0 ? [{ kind: "playwright-id", value: test.id }] : [],
|
|
3124
3325
|
{ kind: "path-title", value: pathTitle }
|
|
@@ -3146,19 +3347,20 @@ function pendingFrom(spec, test, result, file, titles) {
|
|
|
3146
3347
|
...result.workerIndex !== void 0 ? { worker: String(result.workerIndex) } : {}
|
|
3147
3348
|
}
|
|
3148
3349
|
} : {},
|
|
3149
|
-
...errorContext !== "" ? { errorContext } : {}
|
|
3350
|
+
...errorContext !== "" ? { errorContext } : {},
|
|
3351
|
+
...failure !== void 0 ? { failure } : {}
|
|
3150
3352
|
};
|
|
3151
3353
|
}
|
|
3152
|
-
function walk(suite, describes, depth, out) {
|
|
3354
|
+
function walk(suite, describes, depth, ctx, out) {
|
|
3153
3355
|
const here = depth === 0 ? [] : [...describes, suite.title ?? ""];
|
|
3154
3356
|
for (const spec of suite.specs ?? []) {
|
|
3155
3357
|
const file = spec.file ?? suite.file ?? suite.title ?? "";
|
|
3156
3358
|
const titles = [...here, spec.title ?? ""];
|
|
3157
3359
|
for (const test of spec.tests ?? []) {
|
|
3158
|
-
for (const result of test.results ?? []) out.push(pendingFrom(spec, test, result, file, titles));
|
|
3360
|
+
for (const result of test.results ?? []) out.push(pendingFrom(spec, test, result, file, titles, ctx));
|
|
3159
3361
|
}
|
|
3160
3362
|
}
|
|
3161
|
-
for (const child of suite.suites ?? []) walk(child, here, depth + 1, out);
|
|
3363
|
+
for (const child of suite.suites ?? []) walk(child, here, depth + 1, ctx, out);
|
|
3162
3364
|
}
|
|
3163
3365
|
function readPlaywrightJson(source, file) {
|
|
3164
3366
|
let parsed;
|
|
@@ -3170,22 +3372,34 @@ function readPlaywrightJson(source, file) {
|
|
|
3170
3372
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.suites)) {
|
|
3171
3373
|
throw new JsonReportError(file, 'no "suites" array \u2014 is this a Playwright JSON report?');
|
|
3172
3374
|
}
|
|
3375
|
+
const { config, suites } = parsed;
|
|
3376
|
+
const rootDir = typeof config?.rootDir === "string" ? config.rootDir : void 0;
|
|
3377
|
+
const buildHref = config?.metadata?.ci?.buildHref;
|
|
3378
|
+
const repoRoot = rootDir !== void 0 && (0, import_node_fs3.existsSync)(rootDir) ? repoRootOf(rootDir) : void 0;
|
|
3379
|
+
const ctx = {
|
|
3380
|
+
...rootDir !== void 0 ? { rootDir } : {},
|
|
3381
|
+
...repoRoot !== void 0 ? { repoRoot } : {},
|
|
3382
|
+
...typeof buildHref === "string" ? { buildHref } : {}
|
|
3383
|
+
};
|
|
3173
3384
|
const out = [];
|
|
3174
|
-
for (const suite of
|
|
3175
|
-
|
|
3385
|
+
for (const suite of suites) walk(suite, [], 0, ctx, out);
|
|
3386
|
+
const ciUrl = webLink(ctx.buildHref);
|
|
3387
|
+
return ciUrl === void 0 ? { results: out } : { results: out, ciUrl };
|
|
3176
3388
|
}
|
|
3177
3389
|
function looksLikePlaywrightJson(source) {
|
|
3178
|
-
|
|
3179
|
-
return head.trimStart().startsWith("{") && head.includes('"suites"');
|
|
3390
|
+
return /^\s*\{/.test(source) && source.includes('"suites"');
|
|
3180
3391
|
}
|
|
3181
|
-
var SOURCE2, TOKEN, JsonReportError;
|
|
3392
|
+
var import_node_fs3, SOURCE2, TOKEN, NOT_FAILED, JsonReportError;
|
|
3182
3393
|
var init_playwright_json = __esm({
|
|
3183
3394
|
"src/importers/playwright-json.ts"() {
|
|
3184
3395
|
"use strict";
|
|
3185
3396
|
init_cjs_shims();
|
|
3397
|
+
import_node_fs3 = require("fs");
|
|
3398
|
+
init_failure_detail();
|
|
3186
3399
|
init_result_key();
|
|
3187
3400
|
SOURCE2 = "playwright";
|
|
3188
3401
|
TOKEN = /@P([A-Za-z0-9_-]+)/;
|
|
3402
|
+
NOT_FAILED = /* @__PURE__ */ new Set(["passed", "skipped"]);
|
|
3189
3403
|
JsonReportError = class extends Error {
|
|
3190
3404
|
constructor(file, detail) {
|
|
3191
3405
|
super(`${file} \u2014 ${detail}`);
|
|
@@ -3216,11 +3430,9 @@ function detectFormat(source, file) {
|
|
|
3216
3430
|
);
|
|
3217
3431
|
}
|
|
3218
3432
|
function readReport(source, file, format) {
|
|
3219
|
-
const
|
|
3220
|
-
return {
|
|
3221
|
-
|
|
3222
|
-
results: chosen === "junit" ? readJUnit(source, file) : readPlaywrightJson(source, file)
|
|
3223
|
-
};
|
|
3433
|
+
const chosen2 = format ?? detectFormat(source, file);
|
|
3434
|
+
if (chosen2 === "junit") return { format: chosen2, results: readJUnit(source, file) };
|
|
3435
|
+
return { format: chosen2, ...readPlaywrightJson(source, file) };
|
|
3224
3436
|
}
|
|
3225
3437
|
var IMPORT_FORMATS, UnknownFormatError;
|
|
3226
3438
|
var init_importers = __esm({
|
|
@@ -3255,7 +3467,7 @@ async function handleRunImport(options) {
|
|
|
3255
3467
|
} catch {
|
|
3256
3468
|
throw new RunCommandError(`Cannot read ${options.file}.`);
|
|
3257
3469
|
}
|
|
3258
|
-
const { format, results } = readReport(source, options.file, options.format);
|
|
3470
|
+
const { format, results, ciUrl } = readReport(source, options.file, options.format);
|
|
3259
3471
|
if (results.length === 0) {
|
|
3260
3472
|
throw new RunCommandError(`No test cases in ${options.file} \u2014 it parsed, but there is nothing in it.`);
|
|
3261
3473
|
}
|
|
@@ -3271,7 +3483,8 @@ async function handleRunImport(options) {
|
|
|
3271
3483
|
...options.key !== void 0 ? { externalKey: options.key } : {},
|
|
3272
3484
|
...options.create === true ? { offerDiscovered: true } : {},
|
|
3273
3485
|
kind: "automated",
|
|
3274
|
-
|
|
3486
|
+
// The CI run the report names, never this machine's: whoever imports may be another job (#790).
|
|
3487
|
+
meta: { runner: format, ...ciUrl !== void 0 ? { ciUrl } : {} },
|
|
3275
3488
|
log: write
|
|
3276
3489
|
},
|
|
3277
3490
|
// The file is the complete list of what ran, so the platform can resolve every test in one
|
|
@@ -3312,7 +3525,9 @@ function describe(result, apiUrl) {
|
|
|
3312
3525
|
// «Imported 555 result(s): 0 accepted, 0 already there, 0 unmatched» was a sentence whose two
|
|
3313
3526
|
// halves disagreed, and readers stop at the verb — a run that reached nobody read as one that
|
|
3314
3527
|
// worked (#622).
|
|
3315
|
-
`
|
|
3528
|
+
// `not delivered` always, zero included (#790): a number that only appears when it is bad is one
|
|
3529
|
+
// nobody learns to look for. The `[0-9]+ unmatched` the workflows read stays whole.
|
|
3530
|
+
`Read ${result.parsed} result(s) from a ${result.format} report: ${result.accepted} accepted, ${result.duplicate} already there, ${result.unresolved} unmatched, ${result.deferred} not delivered.`
|
|
3316
3531
|
];
|
|
3317
3532
|
if (result.conflict > 0 || result.rejected > 0) {
|
|
3318
3533
|
lines.push(`${result.conflict} conflicted and ${result.rejected} were refused.`);
|
|
@@ -4545,11 +4760,11 @@ module.exports = __toCommonJS(cli_exports);
|
|
|
4545
4760
|
init_cjs_shims();
|
|
4546
4761
|
init_api_url();
|
|
4547
4762
|
var import_commander = require("commander");
|
|
4548
|
-
var
|
|
4549
|
-
var
|
|
4763
|
+
var import_node_fs4 = require("fs");
|
|
4764
|
+
var import_node_path3 = require("path");
|
|
4550
4765
|
var import_node_url = require("url");
|
|
4551
|
-
var __dirname = (0,
|
|
4552
|
-
var pkg = JSON.parse((0,
|
|
4766
|
+
var __dirname = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
4767
|
+
var pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path3.join)(__dirname, "..", "package.json"), "utf-8"));
|
|
4553
4768
|
function toFormat(s) {
|
|
4554
4769
|
return s === "json" || s === "markdown" ? s : "console";
|
|
4555
4770
|
}
|
|
@@ -4558,7 +4773,7 @@ function shouldColor(outputPath) {
|
|
|
4558
4773
|
}
|
|
4559
4774
|
function writeReport(text2, outputPath) {
|
|
4560
4775
|
if (outputPath !== void 0) {
|
|
4561
|
-
(0,
|
|
4776
|
+
(0, import_node_fs4.writeFileSync)(outputPath, text2);
|
|
4562
4777
|
} else {
|
|
4563
4778
|
process.stdout.write(text2 + "\n");
|
|
4564
4779
|
}
|
|
@@ -4624,7 +4839,7 @@ function createProgram() {
|
|
|
4624
4839
|
Promise.resolve().then(() => (init_errors(), errors_exports)),
|
|
4625
4840
|
Promise.resolve().then(() => (init_errors2(), errors_exports2))
|
|
4626
4841
|
]);
|
|
4627
|
-
loadEnv2(configPath !== void 0 ? (0,
|
|
4842
|
+
loadEnv2(configPath !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(configPath)) : process.cwd());
|
|
4628
4843
|
try {
|
|
4629
4844
|
const result = await handleRun2({
|
|
4630
4845
|
dryRun: options.dryRun,
|
|
@@ -4755,7 +4970,8 @@ function createProgram() {
|
|
|
4755
4970
|
Promise.resolve().then(() => (init_run_import(), run_import_exports)),
|
|
4756
4971
|
Promise.resolve().then(() => (init_importers(), importers_exports))
|
|
4757
4972
|
]);
|
|
4758
|
-
const
|
|
4973
|
+
const run = command.parent;
|
|
4974
|
+
const format = options.format ?? (run?.getOptionValueSource("format") === "cli" ? run.opts().format : void 0);
|
|
4759
4975
|
if (format !== void 0 && !IMPORT_FORMATS2.includes(format)) {
|
|
4760
4976
|
process.stderr.write(
|
|
4761
4977
|
`Unknown --format "${format}". Use ${IMPORT_FORMATS2.join(" or ")}.
|
|
@@ -4788,7 +5004,7 @@ function createProgram() {
|
|
|
4788
5004
|
Promise.resolve().then(() => (init_report(), report_exports)),
|
|
4789
5005
|
Promise.resolve().then(() => (init_reporters(), reporters_exports))
|
|
4790
5006
|
]);
|
|
4791
|
-
const cwd = globals.config !== void 0 ? (0,
|
|
5007
|
+
const cwd = globals.config !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(globals.config)) : process.cwd();
|
|
4792
5008
|
loadEnv2(cwd);
|
|
4793
5009
|
try {
|
|
4794
5010
|
const result = handleReport2({ cwd });
|
|
@@ -4886,7 +5102,7 @@ function createProgram() {
|
|
|
4886
5102
|
const globals = command.optsWithGlobals();
|
|
4887
5103
|
const verbose = globals.verbose === true;
|
|
4888
5104
|
const { handleSync: handleSync2, reportSyncFailure: reportSyncFailure2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
4889
|
-
const cwd = globals.config !== void 0 ? (0,
|
|
5105
|
+
const cwd = globals.config !== void 0 ? (0, import_node_path3.dirname)((0, import_node_path3.resolve)(globals.config)) : process.cwd();
|
|
4890
5106
|
try {
|
|
4891
5107
|
const { id, url } = await handleSync2({
|
|
4892
5108
|
cwd,
|
|
@@ -5004,7 +5220,7 @@ function isMainModule() {
|
|
|
5004
5220
|
const entry = process.argv[1];
|
|
5005
5221
|
if (entry === void 0) return false;
|
|
5006
5222
|
try {
|
|
5007
|
-
return (0,
|
|
5223
|
+
return (0, import_node_fs4.realpathSync)(entry) === (0, import_node_fs4.realpathSync)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
5008
5224
|
} catch {
|
|
5009
5225
|
return false;
|
|
5010
5226
|
}
|