@piwitests/reporter 0.56.0 → 0.57.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 +56 -11
- package/dist/index.js +95 -16
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1137,13 +1137,18 @@ var ASSERTION_MATCHERS = [
|
|
|
1137
1137
|
"toBeHidden",
|
|
1138
1138
|
"toBeEnabled",
|
|
1139
1139
|
"toBeDisabled",
|
|
1140
|
-
"toHaveURL"
|
|
1140
|
+
"toHaveURL",
|
|
1141
|
+
"toContainText",
|
|
1142
|
+
"toHaveCount",
|
|
1143
|
+
"toBeChecked"
|
|
1141
1144
|
];
|
|
1142
1145
|
var VALUE_MATCHERS = /* @__PURE__ */ new Set([
|
|
1143
1146
|
"toHaveText",
|
|
1144
1147
|
"toHaveValue",
|
|
1145
1148
|
"toHaveAccessibleName",
|
|
1146
|
-
"toHaveURL"
|
|
1149
|
+
"toHaveURL",
|
|
1150
|
+
"toContainText",
|
|
1151
|
+
"toHaveCount"
|
|
1147
1152
|
]);
|
|
1148
1153
|
|
|
1149
1154
|
// ../core/src/function-match.ts
|
|
@@ -1833,7 +1838,7 @@ function renderAssertStep(step, index, ctx) {
|
|
|
1833
1838
|
});
|
|
1834
1839
|
return [` // Step ${index + 1}: ${matcher} without an expected value.`];
|
|
1835
1840
|
}
|
|
1836
|
-
arg = quote(matcher === "toHaveURL" ? urlForCode(assertion.expected, ctx) : assertion.expected);
|
|
1841
|
+
arg = matcher === "toHaveCount" ? String(Number.parseInt(assertion.expected, 10) || 0) : quote(matcher === "toHaveURL" ? urlForCode(assertion.expected, ctx) : assertion.expected);
|
|
1837
1842
|
}
|
|
1838
1843
|
const subject = matcher === "toHaveURL" ? ctx.page : locatorForStep(step, index, ctx);
|
|
1839
1844
|
let line = ` await expect(${subject})${not}.${matcher}(${arg});`;
|
|
@@ -1846,10 +1851,25 @@ function renderAssertStep(step, index, ctx) {
|
|
|
1846
1851
|
function fileNames(value) {
|
|
1847
1852
|
return (value ?? "").split("\n").map((name) => name.trim().replace(/^.*[\\/]/, "")).filter(Boolean);
|
|
1848
1853
|
}
|
|
1854
|
+
function renderManualStep(step, index, ctx) {
|
|
1855
|
+
const description = commentText(step.manual?.description ?? "") || "a step to do by hand";
|
|
1856
|
+
ctx.warnings.push({
|
|
1857
|
+
step: index,
|
|
1858
|
+
code: "manual-step",
|
|
1859
|
+
detail: description,
|
|
1860
|
+
message: `Step ${index + 1} cannot be written as code: ${description}`
|
|
1861
|
+
});
|
|
1862
|
+
const lines = [` // Manual step: ${description}`];
|
|
1863
|
+
const call = step.manual?.call ? commentText(step.manual.call) : "";
|
|
1864
|
+
const location = step.manual?.location ? commentText(step.manual.location) : "";
|
|
1865
|
+
if (call || location) lines.push(` // ${[call, location ? `at ${location}` : ""].filter(Boolean).join(" ")}`);
|
|
1866
|
+
return lines;
|
|
1867
|
+
}
|
|
1849
1868
|
function renderRawStep(step, index, ctx) {
|
|
1850
1869
|
const { page } = ctx;
|
|
1851
1870
|
if (step.action === "goto") return [` await ${page}.goto(${quote(urlForCode(step.value ?? step.pageUrl, ctx))});`];
|
|
1852
1871
|
if (step.action === "assert" || step.action === "assertVisible") return renderAssertStep(step, index, ctx);
|
|
1872
|
+
if (step.action === "manual") return renderManualStep(step, index, ctx);
|
|
1853
1873
|
if (step.action === "press" && !step.target) {
|
|
1854
1874
|
return [` await ${page}.keyboard.press(${quote(step.value ?? "Enter")});`];
|
|
1855
1875
|
}
|
|
@@ -2060,6 +2080,7 @@ function expectFailLine(options) {
|
|
|
2060
2080
|
return reason ? ` test.fail(); // ${reason}` : " test.fail();";
|
|
2061
2081
|
}
|
|
2062
2082
|
function samePage(a, b) {
|
|
2083
|
+
if (!a || !b) return true;
|
|
2063
2084
|
const ka = pageKey(a);
|
|
2064
2085
|
const kb = pageKey(b);
|
|
2065
2086
|
return ka === null || kb === null || ka === kb;
|
|
@@ -2111,7 +2132,7 @@ function renderSpec(session, options = {}) {
|
|
|
2111
2132
|
const pageStarts = [];
|
|
2112
2133
|
const enterPageOf = (index) => {
|
|
2113
2134
|
const url = steps[index]?.pageUrl;
|
|
2114
|
-
if (options.testSteps !== "page" || url
|
|
2135
|
+
if (options.testSteps !== "page" || !url) return;
|
|
2115
2136
|
const last = pageStarts[pageStarts.length - 1];
|
|
2116
2137
|
if (!last || !samePage(last.url, url)) pageStarts.push({ line: bodyLines.length, url });
|
|
2117
2138
|
};
|
|
@@ -2123,7 +2144,7 @@ function renderSpec(session, options = {}) {
|
|
|
2123
2144
|
const pattern = pageUrlPattern(next.pageUrl);
|
|
2124
2145
|
enterPageOf(last + 1);
|
|
2125
2146
|
if (pattern) bodyLines.push(` await expect(${page}).toHaveURL(${pattern});`);
|
|
2126
|
-
const acts = next.action !== "assert" && next.action !== "assertVisible";
|
|
2147
|
+
const acts = next.action !== "assert" && next.action !== "assertVisible" && next.action !== "manual";
|
|
2127
2148
|
const chosen = acts && next.target ? chooseLocator(next.target, options) : null;
|
|
2128
2149
|
if (chosen) bodyLines.push(` await expect(${page}.${chosen.text}).toHaveCount(1);`);
|
|
2129
2150
|
};
|
|
@@ -2225,7 +2246,11 @@ function renderSpec(session, options = {}) {
|
|
|
2225
2246
|
}
|
|
2226
2247
|
|
|
2227
2248
|
// ../core/src/steps.ts
|
|
2228
|
-
var STEPS_VERSION =
|
|
2249
|
+
var STEPS_VERSION = 2;
|
|
2250
|
+
var VERSION_2_MATCHERS = /* @__PURE__ */ new Set(["toContainText", "toHaveCount", "toBeChecked"]);
|
|
2251
|
+
function stepsVersionFor(steps) {
|
|
2252
|
+
return steps.some((s) => s.action === "manual" || !!s.assertion && VERSION_2_MATCHERS.has(s.assertion.matcher)) ? 2 : 1;
|
|
2253
|
+
}
|
|
2229
2254
|
var STEPS_LIMITS = {
|
|
2230
2255
|
steps: 200,
|
|
2231
2256
|
alternatives: 10,
|
|
@@ -2252,7 +2277,8 @@ var ACTIONS = /* @__PURE__ */ new Set([
|
|
|
2252
2277
|
"setInputFiles",
|
|
2253
2278
|
"dragTo",
|
|
2254
2279
|
"assertVisible",
|
|
2255
|
-
"assert"
|
|
2280
|
+
"assert",
|
|
2281
|
+
"manual"
|
|
2256
2282
|
]);
|
|
2257
2283
|
function originOf2(url) {
|
|
2258
2284
|
try {
|
|
@@ -2277,7 +2303,7 @@ function toAbsolute(url, origin) {
|
|
|
2277
2303
|
function toStepsDocument(session, meta = {}) {
|
|
2278
2304
|
const origin = originOf2(session.startUrl) ?? originOf2(session.steps[0]?.pageUrl ?? "");
|
|
2279
2305
|
return {
|
|
2280
|
-
v:
|
|
2306
|
+
v: stepsVersionFor(session.steps),
|
|
2281
2307
|
title: meta.title ?? null,
|
|
2282
2308
|
origin,
|
|
2283
2309
|
recordedAt: session.startedAt,
|
|
@@ -2386,6 +2412,9 @@ function checkAssertion(c, v, path24) {
|
|
|
2386
2412
|
return void 0;
|
|
2387
2413
|
}
|
|
2388
2414
|
const expected = c.text(v.expected, `${path24}.expected`, STEPS_LIMITS.valueLength, !VALUE_MATCHERS.has(matcher));
|
|
2415
|
+
if (matcher === "toHaveCount" && expected != null && !/^\d{1,6}$/.test(expected)) {
|
|
2416
|
+
c.fail(`${path24}.expected`, "must be a whole number of elements on a toHaveCount assertion");
|
|
2417
|
+
}
|
|
2389
2418
|
return {
|
|
2390
2419
|
matcher,
|
|
2391
2420
|
expected: VALUE_MATCHERS.has(matcher) ? expected : null,
|
|
@@ -2394,6 +2423,19 @@ function checkAssertion(c, v, path24) {
|
|
|
2394
2423
|
note: c.text(v.note, `${path24}.note`, STEPS_LIMITS.valueLength, true)
|
|
2395
2424
|
};
|
|
2396
2425
|
}
|
|
2426
|
+
function checkManual(c, v, path24) {
|
|
2427
|
+
if (!isObject(v)) {
|
|
2428
|
+
c.fail(path24, "is required on a manual step");
|
|
2429
|
+
return void 0;
|
|
2430
|
+
}
|
|
2431
|
+
const description = c.text(v.description, `${path24}.description`, STEPS_LIMITS.valueLength, false);
|
|
2432
|
+
if (description != null && !description.trim()) c.fail(`${path24}.description`, "must not be empty");
|
|
2433
|
+
return {
|
|
2434
|
+
description: description ?? "",
|
|
2435
|
+
location: c.text(v.location, `${path24}.location`, STEPS_LIMITS.textLength, true),
|
|
2436
|
+
call: c.text(v.call, `${path24}.call`, STEPS_LIMITS.textLength, true)
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2397
2439
|
function checkViewports(c, v, stepCount) {
|
|
2398
2440
|
if (v == null) return [];
|
|
2399
2441
|
if (!Array.isArray(v)) {
|
|
@@ -2466,6 +2508,9 @@ function checkStep(c, v, path24) {
|
|
|
2466
2508
|
step.assertion = { matcher: "toBeVisible", expected: null, actual: null, negated: false, note: null };
|
|
2467
2509
|
} else if (action === "assert") {
|
|
2468
2510
|
step.assertion = checkAssertion(c, v.assertion, `${path24}.assertion`);
|
|
2511
|
+
} else if (action === "manual") {
|
|
2512
|
+
step.value = null;
|
|
2513
|
+
step.manual = checkManual(c, v.manual, `${path24}.manual`);
|
|
2469
2514
|
}
|
|
2470
2515
|
return step;
|
|
2471
2516
|
}
|
|
@@ -2481,8 +2526,8 @@ function parseSteps(input) {
|
|
|
2481
2526
|
if (!isObject(value)) return { ok: false, errors: ["a steps document must be a JSON object"] };
|
|
2482
2527
|
const c = new Checker();
|
|
2483
2528
|
const legacy = value.v == null && typeof value.startUrl === "string";
|
|
2484
|
-
if (!legacy && value.v !== STEPS_VERSION) {
|
|
2485
|
-
return { ok: false, errors: [`v: this reads
|
|
2529
|
+
if (!legacy && value.v !== 1 && value.v !== STEPS_VERSION) {
|
|
2530
|
+
return { ok: false, errors: [`v: this reads versions 1 to ${STEPS_VERSION}, not ${JSON.stringify(value.v)}`] };
|
|
2486
2531
|
}
|
|
2487
2532
|
if (!Array.isArray(value.steps)) return { ok: false, errors: ["steps: must be a list"] };
|
|
2488
2533
|
if (value.steps.length > STEPS_LIMITS.steps) {
|
|
@@ -2505,7 +2550,7 @@ function parseSteps(input) {
|
|
|
2505
2550
|
if (originText && !origin) c.fail("origin", "must be an http(s) origin");
|
|
2506
2551
|
const viewports = checkViewports(c, value.viewports, steps.length);
|
|
2507
2552
|
doc = {
|
|
2508
|
-
v:
|
|
2553
|
+
v: stepsVersionFor(steps),
|
|
2509
2554
|
title: c.text(value.title, "title", STEPS_LIMITS.textLength, true),
|
|
2510
2555
|
origin,
|
|
2511
2556
|
recordedAt: value.recordedAt == null ? 0 : c.number(value.recordedAt, "recordedAt"),
|
package/dist/index.js
CHANGED
|
@@ -4549,6 +4549,30 @@ function runFilesOf(answer) {
|
|
|
4549
4549
|
}
|
|
4550
4550
|
return files;
|
|
4551
4551
|
}
|
|
4552
|
+
function instanceOf(answer) {
|
|
4553
|
+
const instance = isRecord2(answer) ? answer.instance : null;
|
|
4554
|
+
if (!isRecord2(instance) || typeof instance.id !== "string" || !REQUEST_ID.test(instance.id)) return null;
|
|
4555
|
+
const startedAt = typeof instance.startedAt === "string" && !Number.isNaN(Date.parse(instance.startedAt)) ? new Date(instance.startedAt).toISOString() : null;
|
|
4556
|
+
return { id: instance.id, startedAt };
|
|
4557
|
+
}
|
|
4558
|
+
function processKeyOf(id) {
|
|
4559
|
+
const dot = id.lastIndexOf(".");
|
|
4560
|
+
return dot > 0 ? id.slice(0, dot) : null;
|
|
4561
|
+
}
|
|
4562
|
+
function withTestCoverage(files, tests) {
|
|
4563
|
+
if (!tests || tests.files.length === 0) return files;
|
|
4564
|
+
const byKey = new Map(files.map((file) => [`${file.runtime}\0${file.path}`, { ...file }]));
|
|
4565
|
+
for (const ran of tests.files) {
|
|
4566
|
+
const key = `${ran.runtime}\0${ran.path}`;
|
|
4567
|
+
const file = byKey.get(key) ?? { path: ran.path, runtime: ran.runtime };
|
|
4568
|
+
const functions = normalizeFunctionRanges([...file.functions ?? [], ...ran.functions ?? []]);
|
|
4569
|
+
const lines = normalizeLineRanges([...file.lines ?? [], ...ran.lines ?? []]);
|
|
4570
|
+
if (functions.length > 0) file.functions = functions;
|
|
4571
|
+
if (lines.length > 0) file.lines = lines;
|
|
4572
|
+
byKey.set(key, file);
|
|
4573
|
+
}
|
|
4574
|
+
return [...byKey.values()];
|
|
4575
|
+
}
|
|
4552
4576
|
function isCollectAnswer(answer) {
|
|
4553
4577
|
return isRecord2(answer) && answer.v === 1 && Array.isArray(answer.groups);
|
|
4554
4578
|
}
|
|
@@ -4600,11 +4624,16 @@ var ServerCoverageCollector = class {
|
|
|
4600
4624
|
}
|
|
4601
4625
|
/**
|
|
4602
4626
|
* The run-level coverage of an origin, with each server file's executable
|
|
4603
|
-
* lines at `lines` granularity
|
|
4627
|
+
* lines at `lines` granularity, and the lines and functions the tests'
|
|
4628
|
+
* coverage named added to it. The gap says why part of it is missing: the
|
|
4629
|
+
* backend restarted during the run (its answer names another process than
|
|
4630
|
+
* the run's first request ids), or it did not answer with run-level coverage.
|
|
4604
4631
|
*/
|
|
4605
4632
|
async fetchRun(origin) {
|
|
4606
4633
|
const state = this.stateOf(origin);
|
|
4607
4634
|
const query = `?granularity=${encodeURIComponent(this.options.granularity)}`;
|
|
4635
|
+
let answer = null;
|
|
4636
|
+
let failure = null;
|
|
4608
4637
|
for (const base of this.pathBases(origin)) {
|
|
4609
4638
|
const outcome = await this.call(
|
|
4610
4639
|
`${origin}${base}${RUN_PATH}${query}`,
|
|
@@ -4612,21 +4641,47 @@ var ServerCoverageCollector = class {
|
|
|
4612
4641
|
this.limits.runTimeoutMs
|
|
4613
4642
|
);
|
|
4614
4643
|
if (outcome.kind === "failed") {
|
|
4615
|
-
|
|
4616
|
-
|
|
4644
|
+
failure = outcome.reason;
|
|
4645
|
+
break;
|
|
4617
4646
|
}
|
|
4618
4647
|
const answered = outcome.ok ? runFilesOf(outcome.json) : null;
|
|
4619
4648
|
if (answered) {
|
|
4620
4649
|
state.base = base;
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
fileChecksums(isRecord2(outcome.json) ? outcome.json.files : null)
|
|
4624
|
-
);
|
|
4625
|
-
return this.options.blobs ? this.options.blobs.withBlobs(files.files) : files.files;
|
|
4650
|
+
answer = { files: answered, json: outcome.json };
|
|
4651
|
+
break;
|
|
4626
4652
|
}
|
|
4627
4653
|
}
|
|
4628
|
-
this.
|
|
4629
|
-
|
|
4654
|
+
const gap = answer ? this.restartGap(origin, state, instanceOf(answer.json)) : { origin, reason: "unreachable" };
|
|
4655
|
+
if (!answer) failure ??= this.noEndpoint(origin, RUN_PATH);
|
|
4656
|
+
if (gap && !this.closer.signal.aborted) this.logger.warn(this.gapMessage(gap, failure));
|
|
4657
|
+
const checksums = fileChecksums(answer?.json.files ?? null, new Map(state.testChecksums));
|
|
4658
|
+
const files = await this.verifyChecksums(withTestCoverage(answer?.files ?? [], state.testCoverage), checksums);
|
|
4659
|
+
let out = files.files;
|
|
4660
|
+
try {
|
|
4661
|
+
if (this.options.blobs) out = await this.options.blobs.withBlobs(out);
|
|
4662
|
+
} catch {
|
|
4663
|
+
}
|
|
4664
|
+
return { files: out, gap };
|
|
4665
|
+
}
|
|
4666
|
+
/**
|
|
4667
|
+
* The gap of an origin whose run-level coverage `instance` answered: a
|
|
4668
|
+
* restart when it, or a collection answer, names another process than the
|
|
4669
|
+
* run's first request ids; null when the backend names no process.
|
|
4670
|
+
*/
|
|
4671
|
+
restartGap(origin, state, instance) {
|
|
4672
|
+
if (!instance) return null;
|
|
4673
|
+
const keys = [...state.processKeys];
|
|
4674
|
+
const others = [...state.instances.values()].filter((known) => known.id !== instance.id);
|
|
4675
|
+
if (keys.every((key) => key === instance.id) && others.length === 0) return null;
|
|
4676
|
+
const at = keys[0] === instance.id ? others[others.length - 1]?.startedAt ?? null : instance.startedAt;
|
|
4677
|
+
return { origin, reason: "restarted", ...at ? { at } : {} };
|
|
4678
|
+
}
|
|
4679
|
+
gapMessage(gap, failure) {
|
|
4680
|
+
if (gap.reason === "unreachable") {
|
|
4681
|
+
return `Could not fetch the run's server coverage from ${gap.origin} (${failure}): the run's server coverage is marked incomplete, and keeps the lines the tests' coverage named.`;
|
|
4682
|
+
}
|
|
4683
|
+
const when = gap.at ? ` at about ${gap.at}` : "";
|
|
4684
|
+
return `The backend at ${gap.origin} restarted during the run${when}: the run's server coverage is marked incomplete. It keeps the lines the tests' coverage named; what the earlier process ran outside a request is lost.`;
|
|
4630
4685
|
}
|
|
4631
4686
|
/** Abort every call in flight and fail every later one; the tests still waiting resolve incomplete. */
|
|
4632
4687
|
close() {
|
|
@@ -4648,7 +4703,11 @@ var ServerCoverageCollector = class {
|
|
|
4648
4703
|
timer: null,
|
|
4649
4704
|
base: null,
|
|
4650
4705
|
limit: createLimiter(this.limits.concurrency),
|
|
4651
|
-
warned: false
|
|
4706
|
+
warned: false,
|
|
4707
|
+
processKeys: /* @__PURE__ */ new Set(),
|
|
4708
|
+
instances: /* @__PURE__ */ new Map(),
|
|
4709
|
+
testCoverage: null,
|
|
4710
|
+
testChecksums: /* @__PURE__ */ new Map()
|
|
4652
4711
|
};
|
|
4653
4712
|
this.originStates.set(origin, state);
|
|
4654
4713
|
}
|
|
@@ -4718,6 +4777,13 @@ var ServerCoverageCollector = class {
|
|
|
4718
4777
|
}
|
|
4719
4778
|
/** Send one call and hand each group's answer to its test. */
|
|
4720
4779
|
async send(origin, groups) {
|
|
4780
|
+
const state = this.stateOf(origin);
|
|
4781
|
+
for (const group of groups) {
|
|
4782
|
+
for (const id of group.ids) {
|
|
4783
|
+
const key = processKeyOf(id);
|
|
4784
|
+
if (key !== null) state.processKeys.add(key);
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4721
4787
|
let answer = null;
|
|
4722
4788
|
try {
|
|
4723
4789
|
const body = JSON.stringify({
|
|
@@ -4728,6 +4794,8 @@ var ServerCoverageCollector = class {
|
|
|
4728
4794
|
} catch (error) {
|
|
4729
4795
|
this.logger.debug(`Server coverage collection from ${origin} failed: ${errorMessage(error)}`);
|
|
4730
4796
|
}
|
|
4797
|
+
const instance = instanceOf(answer);
|
|
4798
|
+
if (instance && !state.instances.has(instance.id)) state.instances.set(instance.id, instance);
|
|
4731
4799
|
const answered = /* @__PURE__ */ new Map();
|
|
4732
4800
|
for (const group of answer?.groups ?? []) {
|
|
4733
4801
|
if (isRecord2(group) && typeof group.key === "string") answered.set(group.key, group);
|
|
@@ -4739,9 +4807,11 @@ var ServerCoverageCollector = class {
|
|
|
4739
4807
|
} else {
|
|
4740
4808
|
if (Array.isArray(result.missing) && result.missing.length > 0) group.entry.incomplete = true;
|
|
4741
4809
|
fileChecksums(result.files, group.entry.checksums);
|
|
4810
|
+
fileChecksums(result.files, state.testChecksums);
|
|
4742
4811
|
const capture = sanitizeCoverage({ v: 1, files: result.files });
|
|
4743
4812
|
if (capture) {
|
|
4744
4813
|
group.entry.captures.push(capture);
|
|
4814
|
+
state.testCoverage = mergeCoverage(state.testCoverage, capture);
|
|
4745
4815
|
if (capture.incomplete) group.entry.incomplete = true;
|
|
4746
4816
|
}
|
|
4747
4817
|
}
|
|
@@ -10496,17 +10566,26 @@ var CoverageCollection = class {
|
|
|
10496
10566
|
}
|
|
10497
10567
|
/**
|
|
10498
10568
|
* Send the run-level coverage of every origin, and the browser's, to the
|
|
10499
|
-
* dashboard run, in requests within its limits
|
|
10500
|
-
*
|
|
10569
|
+
* dashboard run, in requests within its limits; the first names the origins
|
|
10570
|
+
* whose server coverage is incomplete (`incomplete`), and is sent for them
|
|
10571
|
+
* alone when no file is. A failure is logged once, never thrown.
|
|
10501
10572
|
*/
|
|
10502
10573
|
async sendRunCoverage(httpClient, runId, auth) {
|
|
10503
10574
|
if (!this.collector && !this.browserBlobs) return;
|
|
10504
10575
|
this.fetchRunCoverage();
|
|
10505
|
-
const
|
|
10576
|
+
const runs = await Promise.all(this.runFiles.values());
|
|
10577
|
+
const server = runs.flatMap((result) => result?.files ?? []);
|
|
10578
|
+
const gaps = runs.flatMap((result) => result?.gap ? [result.gap] : []);
|
|
10506
10579
|
const files = [...server, ...await this.browserRunFiles()];
|
|
10507
|
-
|
|
10580
|
+
const chunks = chunkRunCoverage(files);
|
|
10581
|
+
if (chunks.length === 0 && gaps.length > 0) chunks.push([]);
|
|
10582
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
10508
10583
|
try {
|
|
10509
|
-
await httpClient.putJSON(
|
|
10584
|
+
await httpClient.putJSON(
|
|
10585
|
+
`/api/test-runs/${runId}/coverage`,
|
|
10586
|
+
{ v: 1, files: chunk, ...i === 0 && gaps.length > 0 ? { incomplete: gaps } : {} },
|
|
10587
|
+
auth
|
|
10588
|
+
);
|
|
10510
10589
|
} catch (error) {
|
|
10511
10590
|
this.logger.warn(`Could not send the run's coverage to the dashboard: ${errorMessage(error)}`);
|
|
10512
10591
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piwitests/reporter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.0",
|
|
4
4
|
"description": "Playwright reporter that streams results, traces and HTML reports to a Piwi Dashboard instance",
|
|
5
5
|
"url": "https://github.com/PiwiTests/platform",
|
|
6
6
|
"homepage": "https://piwitests.dev",
|