@piwitests/reporter 0.61.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 +369 -223
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1794 -266
- package/dist/internal/capture/attachments.d.ts +24 -1
- package/dist/internal/capture/attachments.js +21 -2
- package/dist/internal/capture/capture-fixtures.d.ts +114 -6
- package/dist/internal/capture/capture-fixtures.js +1597 -176
- 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
|
@@ -33,10 +33,33 @@ declare const ATTACHMENT_NAMES: {
|
|
|
33
33
|
};
|
|
34
34
|
/** Set of every internal attachment name — used to skip them when collecting user attachments. */
|
|
35
35
|
declare const INTERNAL_ATTACHMENT_NAMES: ReadonlySet<string>;
|
|
36
|
+
/** A name of `ATTACHMENT_NAMES`. */
|
|
37
|
+
type AttachmentName = (typeof ATTACHMENT_NAMES)[keyof typeof ATTACHMENT_NAMES];
|
|
38
|
+
/**
|
|
39
|
+
* The content type the capture fixtures write `piwi-aria-snapshot` with, and
|
|
40
|
+
* the only one the reporter reads it with. An attachment made from a browser
|
|
41
|
+
* server's `x-playwright-attachment` header has the content type `text/plain`.
|
|
42
|
+
*/
|
|
43
|
+
declare const ARIA_SNAPSHOT_CONTENT_TYPE = "text/plain; charset=utf-8";
|
|
44
|
+
/** Whether `attachment` has the name `name` and the content type the capture fixtures write it with. */
|
|
45
|
+
declare function isPiwiAttachment(attachment: {
|
|
46
|
+
name: string;
|
|
47
|
+
contentType?: string;
|
|
48
|
+
}, name: AttachmentName): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The last of `attachments` that `isPiwiAttachment` accepts for `name`. A
|
|
51
|
+
* reader of an attachment the fixtures write more than once
|
|
52
|
+
* (`piwi-browser-functions`, `piwi-css-rules`) goes through every match with
|
|
53
|
+
* `isPiwiAttachment` instead.
|
|
54
|
+
*/
|
|
55
|
+
declare function findPiwiAttachment<T extends {
|
|
56
|
+
name: string;
|
|
57
|
+
contentType?: string;
|
|
58
|
+
}>(attachments: readonly T[] | undefined, name: AttachmentName): T | undefined;
|
|
36
59
|
|
|
37
60
|
/** Annotation type that surfaces a locator-healing suggestion in the report/trace. */
|
|
38
61
|
declare const LOCATOR_SUGGESTION_ANNOTATION: "piwi-locator-suggestion";
|
|
39
62
|
/** Annotation type that surfaces a human-confirmed replacement locator in the report/trace. */
|
|
40
63
|
declare const USER_PICK_ANNOTATION: "piwi-user-pick";
|
|
41
64
|
|
|
42
|
-
export { ATTACHMENT_NAMES, INTERNAL_ATTACHMENT_NAMES, LOCATOR_SUGGESTION_ANNOTATION, USER_PICK_ANNOTATION };
|
|
65
|
+
export { ARIA_SNAPSHOT_CONTENT_TYPE, ATTACHMENT_NAMES, type AttachmentName, INTERNAL_ATTACHMENT_NAMES, LOCATOR_SUGGESTION_ANNOTATION, USER_PICK_ANNOTATION, findPiwiAttachment, isPiwiAttachment };
|
|
@@ -20,11 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/internal/capture/attachments.ts
|
|
21
21
|
var attachments_exports = {};
|
|
22
22
|
__export(attachments_exports, {
|
|
23
|
+
ARIA_SNAPSHOT_CONTENT_TYPE: () => ARIA_SNAPSHOT_CONTENT_TYPE,
|
|
23
24
|
ATTACHMENT_NAMES: () => ATTACHMENT_NAMES,
|
|
24
25
|
FAILURE_SCREENSHOT_ATTACHMENT: () => FAILURE_SCREENSHOT_ATTACHMENT,
|
|
25
26
|
INTERNAL_ATTACHMENT_NAMES: () => INTERNAL_ATTACHMENT_NAMES,
|
|
26
27
|
LOCATOR_SUGGESTION_ANNOTATION: () => LOCATOR_SUGGESTION_ANNOTATION,
|
|
27
|
-
USER_PICK_ANNOTATION: () => USER_PICK_ANNOTATION
|
|
28
|
+
USER_PICK_ANNOTATION: () => USER_PICK_ANNOTATION,
|
|
29
|
+
findPiwiAttachment: () => findPiwiAttachment,
|
|
30
|
+
isPiwiAttachment: () => isPiwiAttachment
|
|
28
31
|
});
|
|
29
32
|
module.exports = __toCommonJS(attachments_exports);
|
|
30
33
|
|
|
@@ -57,13 +60,29 @@ var ATTACHMENT_NAMES = {
|
|
|
57
60
|
pauses: "piwi-pauses"
|
|
58
61
|
};
|
|
59
62
|
var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
|
|
63
|
+
var ARIA_SNAPSHOT_CONTENT_TYPE = "text/plain; charset=utf-8";
|
|
64
|
+
function contentTypeOf(name) {
|
|
65
|
+
return name === ATTACHMENT_NAMES.ariaSnapshot ? ARIA_SNAPSHOT_CONTENT_TYPE : "application/json";
|
|
66
|
+
}
|
|
67
|
+
function isPiwiAttachment(attachment, name) {
|
|
68
|
+
return attachment.name === name && attachment.contentType === contentTypeOf(name);
|
|
69
|
+
}
|
|
70
|
+
function findPiwiAttachment(attachments, name) {
|
|
71
|
+
for (let i = (attachments?.length ?? 0) - 1; i >= 0; i--) {
|
|
72
|
+
if (isPiwiAttachment(attachments[i], name)) return attachments[i];
|
|
73
|
+
}
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
60
76
|
var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
|
|
61
77
|
var USER_PICK_ANNOTATION = ATTACHMENT_NAMES.userPick;
|
|
62
78
|
// Annotate the CommonJS export names for ESM import in node:
|
|
63
79
|
0 && (module.exports = {
|
|
80
|
+
ARIA_SNAPSHOT_CONTENT_TYPE,
|
|
64
81
|
ATTACHMENT_NAMES,
|
|
65
82
|
FAILURE_SCREENSHOT_ATTACHMENT,
|
|
66
83
|
INTERNAL_ATTACHMENT_NAMES,
|
|
67
84
|
LOCATOR_SUGGESTION_ANNOTATION,
|
|
68
|
-
USER_PICK_ANNOTATION
|
|
85
|
+
USER_PICK_ANNOTATION,
|
|
86
|
+
findPiwiAttachment,
|
|
87
|
+
isPiwiAttachment
|
|
69
88
|
});
|
|
@@ -3,6 +3,7 @@ export { ProbeArg, ProbedAttrs, probeElementAttrs } from '@piwitests/picker-dom'
|
|
|
3
3
|
import { PageInteraction } from '@piwitests/core/page-interaction';
|
|
4
4
|
import { Page, TestInfo, TestType, Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, Locator } from '@playwright/test';
|
|
5
5
|
import { LocatorPageUse, ProcessRole, RoleCost } from '@piwitests/core/wire';
|
|
6
|
+
import { RunIntent, IntentAnswer } from '@piwitests/core/run-channel';
|
|
6
7
|
import { JsCoverageEntry } from '@piwitests/core/code-reach';
|
|
7
8
|
|
|
8
9
|
/** A main-frame navigation, full or same-document: the page starts over. */
|
|
@@ -26,6 +27,23 @@ declare class LocatorPageLog {
|
|
|
26
27
|
list(): LocatorPageUse[];
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The pause bar's Pick a locator and Record here, started from outside the page (the editor's hub, for Piwi Picker):
|
|
32
|
+
* the bar takes an intent while it waits for its answer, and one handed to it while it is about to (its highlight
|
|
33
|
+
* still drawing) waits for it; while a pick or a recording runs, none is taken.
|
|
34
|
+
*/
|
|
35
|
+
declare class BarIntents {
|
|
36
|
+
private take;
|
|
37
|
+
private queued;
|
|
38
|
+
private busy;
|
|
39
|
+
/** Hand the bar an intent; false while it runs one, or holds one already. */
|
|
40
|
+
offer(intent: RunIntent): boolean;
|
|
41
|
+
/** The bar waits for its answer: settles with the next intent handed to it. */
|
|
42
|
+
wait(): Promise<RunIntent>;
|
|
43
|
+
/** The bar got its answer: it takes no intent until it waits again. */
|
|
44
|
+
answered(): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
/**
|
|
30
48
|
* Register this package's `dist/` directory as a boxed stack prefix — the list
|
|
31
49
|
* Playwright filters its own frames with — so a wrapped action's step location
|
|
@@ -166,6 +184,12 @@ interface TestMetrics {
|
|
|
166
184
|
loopDelayP99Ms: number;
|
|
167
185
|
loopDelayMaxMs: number;
|
|
168
186
|
heapUsedMb: number;
|
|
187
|
+
/**
|
|
188
|
+
* The heap right after the lowest full garbage collection V8 ran during
|
|
189
|
+
* the test, or null when it ran none: what the worker really holds, without
|
|
190
|
+
* the garbage `heapUsedMb` counts until it is collected.
|
|
191
|
+
*/
|
|
192
|
+
heapAfterGcMb?: number | null;
|
|
169
193
|
/** Open file descriptors at the test's end (Linux). */
|
|
170
194
|
fds: number | null;
|
|
171
195
|
};
|
|
@@ -177,13 +201,19 @@ interface PageMain {
|
|
|
177
201
|
/** Main-thread CPU since Piwi first read the page. */
|
|
178
202
|
cpuMs: number;
|
|
179
203
|
heapMb: number;
|
|
204
|
+
/** DOM nodes alive in the renderer, detached ones not yet collected included. */
|
|
180
205
|
nodes: number;
|
|
181
206
|
listeners: number;
|
|
207
|
+
/** Elements attached to the main frame's document: exact, with no garbage in it. */
|
|
208
|
+
elements?: number;
|
|
182
209
|
}
|
|
183
210
|
declare class WorkerMetrics {
|
|
184
211
|
private readonly proc;
|
|
185
212
|
private readonly pid;
|
|
186
213
|
private readonly histogram;
|
|
214
|
+
private readonly gcObserver;
|
|
215
|
+
/** The lowest heap read right after a full garbage collection in the current window. */
|
|
216
|
+
private heapAfterGc;
|
|
187
217
|
private readonly linux;
|
|
188
218
|
private loop;
|
|
189
219
|
private usage;
|
|
@@ -191,6 +221,11 @@ declare class WorkerMetrics {
|
|
|
191
221
|
private subtree;
|
|
192
222
|
private roles;
|
|
193
223
|
constructor(proc?: ProcFs, pid?: number, platform?: NodeJS.Platform);
|
|
224
|
+
/**
|
|
225
|
+
* Read the heap after each full (major) garbage collection V8 runs on its
|
|
226
|
+
* own: nothing is forced, so a test that triggers none reads null.
|
|
227
|
+
*/
|
|
228
|
+
private observeFullCollections;
|
|
194
229
|
/** The worker's processes under it, keyed by pid and start time. */
|
|
195
230
|
private tree;
|
|
196
231
|
/**
|
|
@@ -218,6 +253,12 @@ declare class WorkerMetrics {
|
|
|
218
253
|
declare class PageReader {
|
|
219
254
|
private readonly sessions;
|
|
220
255
|
read(page: object): Promise<PageMain | null>;
|
|
256
|
+
/**
|
|
257
|
+
* The elements attached to the page's main document, through the page's own
|
|
258
|
+
* DevTools session: no Playwright call, so no step and no trace action. The
|
|
259
|
+
* count runs in the page's main world and changes nothing in it.
|
|
260
|
+
*/
|
|
261
|
+
private countElements;
|
|
221
262
|
}
|
|
222
263
|
|
|
223
264
|
/**
|
|
@@ -324,7 +365,24 @@ interface ResourceCensus {
|
|
|
324
365
|
};
|
|
325
366
|
/** Objects the test opened itself and left open (what `PIWI_LEAK_CHECK` judges). */
|
|
326
367
|
leftOpen?: number;
|
|
368
|
+
/**
|
|
369
|
+
* The servers and child processes the test started (its body, its
|
|
370
|
+
* each-hooks or a test-scoped fixture) and left running in the worker, with
|
|
371
|
+
* the line that started each. Present when the ledger tracks them; the
|
|
372
|
+
* `handles` counts then leave servers out.
|
|
373
|
+
*/
|
|
374
|
+
running?: RunningHandle[];
|
|
327
375
|
}
|
|
376
|
+
/** A server or a child process a test left running in the worker. */
|
|
377
|
+
interface RunningHandle {
|
|
378
|
+
type: NodeHandleType;
|
|
379
|
+
/** The line that started it, `file:line`. */
|
|
380
|
+
site: string | null;
|
|
381
|
+
/** A child process: the program it runs, without its arguments. */
|
|
382
|
+
command?: string;
|
|
383
|
+
}
|
|
384
|
+
/** The Node handles the ledger tracks one by one, by Node's own type names. */
|
|
385
|
+
type NodeHandleType = 'TCPServerWrap' | 'ChildProcess';
|
|
328
386
|
/** An object a test opened itself and left open: what `PIWI_LEAK_CHECK` fails or closes. */
|
|
329
387
|
interface LeakedResource {
|
|
330
388
|
id: number;
|
|
@@ -379,6 +437,8 @@ declare class ResourceLedger {
|
|
|
379
437
|
private readonly testInfoSource;
|
|
380
438
|
private readonly now;
|
|
381
439
|
private readonly readers;
|
|
440
|
+
/** Track the servers and child processes the worker starts, by wrapping Node's `listen` and `spawn`. */
|
|
441
|
+
private readonly nodeHandles;
|
|
382
442
|
private nextId;
|
|
383
443
|
private readonly entries;
|
|
384
444
|
private readonly byObject;
|
|
@@ -395,14 +455,39 @@ declare class ResourceLedger {
|
|
|
395
455
|
private listener;
|
|
396
456
|
/** A browser or a context was opened in this worker: there are browser processes for the metrics to read. */
|
|
397
457
|
private sawBrowser;
|
|
458
|
+
/** The servers and child processes still running, while the ledger tracks them. */
|
|
459
|
+
private readonly handles;
|
|
460
|
+
private readonly byHandle;
|
|
461
|
+
private trackingHandles;
|
|
462
|
+
private readonly restores;
|
|
398
463
|
constructor(playwright: PlaywrightLike, workerIndex: number, leakCheck?: LeakCheck, resultsFile?: string | null, testInfoSource?: () => TestInfoLike | null, now?: () => number, readers?: {
|
|
399
464
|
metrics?: WorkerMetrics | null;
|
|
400
465
|
pages?: PageReader | null;
|
|
401
|
-
}
|
|
466
|
+
},
|
|
467
|
+
/** Track the servers and child processes the worker starts, by wrapping Node's `listen` and `spawn`. */
|
|
468
|
+
nodeHandles?: boolean);
|
|
402
469
|
/** Listen to the worker's Playwright and wrap its browser types. False when the instrumentation is missing. */
|
|
403
470
|
install(): boolean;
|
|
404
|
-
/** Stop listening; the objects already tracked stay in the ledger. */
|
|
471
|
+
/** Stop listening, and put Node's `listen` and `spawn` back; the objects already tracked stay in the ledger. */
|
|
405
472
|
uninstall(): void;
|
|
473
|
+
/**
|
|
474
|
+
* Wrap `net.Server.prototype.listen` (every HTTP, HTTPS and TCP server) and
|
|
475
|
+
* `ChildProcess.prototype.spawn` (`spawn`, `exec`, `execFile` and `fork`), so
|
|
476
|
+
* each server and child process the worker starts carries the line and the
|
|
477
|
+
* runnable that started it, as a context does. A worker-scoped fixture's
|
|
478
|
+
* server is then the worker's, never the first test's. Each wrapper calls the
|
|
479
|
+
* original on its receiver and only records; it never throws.
|
|
480
|
+
*/
|
|
481
|
+
private wrapNodeHandles;
|
|
482
|
+
private trackHandle;
|
|
483
|
+
private onServer;
|
|
484
|
+
private onChild;
|
|
485
|
+
/**
|
|
486
|
+
* The servers and child processes a test started (its body, its each-hooks
|
|
487
|
+
* or a test-scoped fixture) that still run. A server whose `listen` has not
|
|
488
|
+
* bound yet is read again one tick later.
|
|
489
|
+
*/
|
|
490
|
+
private runningOf;
|
|
406
491
|
/** Mark a page used: a locator was built on it. */
|
|
407
492
|
notePageUse(page: object): void;
|
|
408
493
|
/** Open the census window of a test. */
|
|
@@ -420,6 +505,8 @@ declare class ResourceLedger {
|
|
|
420
505
|
private readPages;
|
|
421
506
|
/** The census a worker takes as it shuts down, appended to the results file when the reporter set one. */
|
|
422
507
|
workerEnded(): ResourceCensus;
|
|
508
|
+
/** The handle types counted at each boundary: servers leave the count once they are tracked one by one. */
|
|
509
|
+
private countedHandleTypes;
|
|
423
510
|
private guard;
|
|
424
511
|
private wrapBrowserType;
|
|
425
512
|
private track;
|
|
@@ -582,6 +669,26 @@ declare function ariaSnapshotBestEffort(target: Locator, timeout?: number): Prom
|
|
|
582
669
|
* yields null and the capture proceeds with the YAML alone.
|
|
583
670
|
*/
|
|
584
671
|
declare function ariaSnapshotJSONBestEffort(target: Locator, timeout?: number): Promise<string | null>;
|
|
672
|
+
/** What a stop the bar shows says to an intent: whether it can record, whether its page is a guess, whether it holds. */
|
|
673
|
+
interface IntentStop {
|
|
674
|
+
record: boolean;
|
|
675
|
+
guessed: boolean;
|
|
676
|
+
/** Neither answered nor ending: its test goes on, and neither its page nor its context began to close. */
|
|
677
|
+
holds(): boolean;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Start an intent from outside the page, at a stop the bar shows: answered at once, the pick, the recording, the
|
|
681
|
+
* check or the region's pick then running in the test's window as from the bar, and ending with the stop. Refused at
|
|
682
|
+
* a stop that is over or ending, and at one whose page is a guess (`StopPage`), which the person outside the page
|
|
683
|
+
* cannot see.
|
|
684
|
+
*/
|
|
685
|
+
declare function intentAt(intents: BarIntents, what: RunIntent, stop: IntentStop): IntentAnswer;
|
|
686
|
+
/**
|
|
687
|
+
* A failure's error as its stop names it to the editor, as plain text: the failing error's message without its ANSI
|
|
688
|
+
* codes and without the `Error: ` the runner writes before a plain error's message, its lines up to the first blank
|
|
689
|
+
* one or its call log, four at most and `MAX_STOP_ERROR` characters; null for an error with no text.
|
|
690
|
+
*/
|
|
691
|
+
declare function failureError(message: string | undefined): string | null;
|
|
585
692
|
/**
|
|
586
693
|
* The fixtures `piwiFixtures` / `extendPiwiFixtures` contribute. The single
|
|
587
694
|
* added fixture is `piwiCapture`: an auto, test-scoped teardown hook that
|
|
@@ -597,11 +704,12 @@ interface PiwiFixtures {
|
|
|
597
704
|
* The worker fixture `piwiFixtures` / `extendPiwiFixtures` contribute:
|
|
598
705
|
* `piwiResources`, an auto, worker-scoped fixture that keeps the worker's
|
|
599
706
|
* resource ledger (the browsers, contexts, pages and API request contexts its
|
|
600
|
-
* tests open) from before the first hook to the worker's shutdown,
|
|
707
|
+
* tests open) from before the first hook to the worker's shutdown, records
|
|
601
708
|
* the calls tests make through the API request contexts `playwright.request`
|
|
602
|
-
* creates, the `request` fixture's among them
|
|
709
|
+
* creates, the `request` fixture's among them, and holds the worker's run
|
|
710
|
+
* connection to the editor that started the run. Its name is **reserved** like
|
|
603
711
|
* `piwiCapture`'s: a fixture of your own with that name replaces it and turns
|
|
604
|
-
* the ledger
|
|
712
|
+
* the ledger, those calls' capture and the run connection off.
|
|
605
713
|
*/
|
|
606
714
|
interface PiwiWorkerFixtures {
|
|
607
715
|
piwiResources: void;
|
|
@@ -637,4 +745,4 @@ declare const piwiFixtures: Fixtures<PiwiFixtures, PiwiWorkerFixtures, Playwrigh
|
|
|
637
745
|
*/
|
|
638
746
|
declare function extendPiwiFixtures<TestArgs extends FixtureArgs, WorkerArgs extends FixtureArgs>(test: TestType<TestArgs, WorkerArgs>): TestType<TestArgs & PiwiFixtures, WorkerArgs & PiwiWorkerFixtures>;
|
|
639
747
|
|
|
640
|
-
export { CAPTURED_ATTRS_ARG, LocatorPageLog, type PageState, type PiwiFixtures, type PiwiWorkerFixtures, type RawPageState, type RawVitalEntry, activeResourceLedger, ariaSnapshotBestEffort, ariaSnapshotJSONBestEffort, boxCaptureFrames, buildPageState, callMethod, codeReachRoots, computeCoreVitals, configuredCodeReachRoots, extendPiwiFixtures, internalCall, noteLocatorCall, noteNavigation, noteResourceUse, pageMapFetcher, piwiFixtures, readPageInteraction, recordResourceCensus, resolveCodeReach, startCodeReach, startResourceLedger, stopCodeReach, stopResourceLedger };
|
|
748
|
+
export { CAPTURED_ATTRS_ARG, type IntentStop, LocatorPageLog, type PageState, type PiwiFixtures, type PiwiWorkerFixtures, type RawPageState, type RawVitalEntry, activeResourceLedger, ariaSnapshotBestEffort, ariaSnapshotJSONBestEffort, boxCaptureFrames, buildPageState, callMethod, codeReachRoots, computeCoreVitals, configuredCodeReachRoots, extendPiwiFixtures, failureError, intentAt, internalCall, noteLocatorCall, noteNavigation, noteResourceUse, pageMapFetcher, piwiFixtures, readPageInteraction, recordResourceCensus, resolveCodeReach, startCodeReach, startResourceLedger, stopCodeReach, stopResourceLedger };
|