@piwitests/reporter 0.44.0 → 0.46.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.
@@ -21,6 +21,7 @@ declare const ATTACHMENT_NAMES: {
21
21
  readonly userPick: "piwi-user-pick";
22
22
  readonly aiUsage: "piwi-ai-usage";
23
23
  readonly aiMeta: "piwi-ai-meta";
24
+ readonly resources: "piwi-resources";
24
25
  };
25
26
  /** Set of every internal attachment name — used to skip them when collecting user attachments. */
26
27
  declare const INTERNAL_ATTACHMENT_NAMES: ReadonlySet<string>;
@@ -41,7 +41,8 @@ var ATTACHMENT_NAMES = {
41
41
  pageState: "piwi-page-state",
42
42
  userPick: "piwi-user-pick",
43
43
  aiUsage: "piwi-ai-usage",
44
- aiMeta: "piwi-ai-meta"
44
+ aiMeta: "piwi-ai-meta",
45
+ resources: "piwi-resources"
45
46
  };
46
47
  var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
47
48
  var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
@@ -1,7 +1,7 @@
1
1
  import { ProbeArg } from '@piwitests/picker-dom';
2
2
  export { ProbeArg, ProbedAttrs, probeElementAttrs } from '@piwitests/picker-dom';
3
- import { Page, TestType, Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, Locator } from '@playwright/test';
4
- import { LocatorPageUse } from '@piwitests/core/wire';
3
+ import { Page, TestInfo, TestType, Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, Locator } from '@playwright/test';
4
+ import { LocatorPageUse, ProcessRole, RoleCost } from '@piwitests/core/wire';
5
5
  import { JsCoverageEntry } from '@piwitests/core/code-reach';
6
6
 
7
7
  /** A main-frame navigation, full or same-document: the page starts over. */
@@ -66,6 +66,364 @@ declare function startCodeReach(page: Page): Promise<boolean>;
66
66
  /** Stop JavaScript coverage and return its entries; null when it cannot be read. */
67
67
  declare function stopCodeReach(page: Page): Promise<JsCoverageEntry[] | null>;
68
68
 
69
+ interface ProcStat {
70
+ pid: number;
71
+ ppid: number;
72
+ comm: string;
73
+ /** User plus system CPU time. */
74
+ cpuMs: number;
75
+ /** Start time in clock ticks since boot: with the pid, it names one process even when the pid is reused. */
76
+ start: number;
77
+ }
78
+ /** CPU ticks of the whole machine, from the first line of `/proc/stat`. */
79
+ interface CpuTicks {
80
+ busy: number;
81
+ idle: number;
82
+ iowait: number;
83
+ steal: number;
84
+ total: number;
85
+ }
86
+ /** Linux's `/proc`, under a root a test can replace. */
87
+ declare class ProcFs {
88
+ readonly root: string;
89
+ constructor(root?: string);
90
+ /** Whether this looks like a Linux `/proc` at all. */
91
+ available(): boolean;
92
+ /** Every process, keyed by pid. */
93
+ table(): Map<number, ProcStat>;
94
+ stat(pid: number): ProcStat | null;
95
+ runWaitMs(pid: number): number | null;
96
+ cmdline(pid: number): string;
97
+ /** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
98
+ statusKb(pid: number, key: string): number | null;
99
+ /** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
100
+ pssKb(pid: number): Promise<number | null>;
101
+ /** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
102
+ resetPeakRss(pid: number): boolean;
103
+ cpuTicks(): CpuTicks | null;
104
+ /** Total stall time of a resource's pressure file, in µs. */
105
+ pressureUs(resource: 'cpu' | 'memory' | 'io'): number | null;
106
+ memAvailableKb(): number | null;
107
+ /** Open file descriptors of a process. */
108
+ fdCount(pid: number): number | null;
109
+ }
110
+
111
+ /**
112
+ * What a test cost its worker, read at the test's start and end: the worker
113
+ * process's own CPU and health (event loop, involuntary context switches,
114
+ * heap), and on Linux the processes it started — its browsers — by role, with
115
+ * their CPU, the time they waited for a CPU and their peak memory during the
116
+ * test. Plus, over the Chrome DevTools Protocol, the main-thread CPU and weight
117
+ * of the pages that outlive their test. Every read is best-effort.
118
+ */
119
+ interface TestMetrics {
120
+ worker: {
121
+ cpuMs: number;
122
+ involuntarySwitches: number;
123
+ /** Share of the test's time the event loop was busy. */
124
+ loopUtilization: number;
125
+ loopDelayP99Ms: number;
126
+ loopDelayMaxMs: number;
127
+ heapUsedMb: number;
128
+ /** Open file descriptors at the test's end (Linux). */
129
+ fds: number | null;
130
+ };
131
+ /** The processes the worker started, by role (Linux). */
132
+ roles?: Partial<Record<ProcessRole, RoleCost>>;
133
+ }
134
+ /** A page's main thread and weight, over CDP (Chromium). */
135
+ interface PageMain {
136
+ /** Main-thread CPU since Piwi first read the page. */
137
+ cpuMs: number;
138
+ heapMb: number;
139
+ nodes: number;
140
+ listeners: number;
141
+ }
142
+ declare class WorkerMetrics {
143
+ private readonly proc;
144
+ private readonly pid;
145
+ private readonly histogram;
146
+ private readonly linux;
147
+ private loop;
148
+ private usage;
149
+ /** Each process's CPU and wait at the start of the current window; null before the first. */
150
+ private subtree;
151
+ private roles;
152
+ constructor(proc?: ProcFs, pid?: number, platform?: NodeJS.Platform);
153
+ /** The worker's processes under it, keyed by pid and start time. */
154
+ private tree;
155
+ /**
156
+ * The processes under the worker now, and the reset of their peak memory: the
157
+ * start of the next window. Taken once per test, at its end, so the time
158
+ * between two tests counts toward the next one.
159
+ */
160
+ private snapshot;
161
+ /**
162
+ * Start a test's window. `processes` says whether the worker has started any
163
+ * browser to read; a worker that never did skips the process tree.
164
+ */
165
+ start(processes?: boolean): void;
166
+ /** Close the window. Null when the test's start was never read. */
167
+ end(processes?: boolean): TestMetrics | null;
168
+ /** The window's cost by role; what it reads becomes the start of the next window. */
169
+ private roleCosts;
170
+ dispose(): void;
171
+ }
172
+ /**
173
+ * Reads pages over CDP, one session per page, opened the first time a page is
174
+ * read and kept while it lives. On a browser without CDP the read is null, and
175
+ * the page is never tried again.
176
+ */
177
+ declare class PageReader {
178
+ private readonly sessions;
179
+ read(page: object): Promise<PageMain | null>;
180
+ }
181
+
182
+ /**
183
+ * The resource ledger: every browser, context, page and API request context a
184
+ * worker opens, where it was opened, whether anything ran on it, and when it
185
+ * closed. One per worker process, started by the `piwiResources` worker fixture
186
+ * before any hook runs. At the end of each test the capture fixture takes a
187
+ * census — what was born and closed since the last one, and what is open now —
188
+ * and attaches it as `piwi-resources`; the reporter turns the censuses into
189
+ * findings (`internal/collect/resource-verdicts.ts`). It never throws into a
190
+ * test, and every Playwright internal it reads is feature-detected.
191
+ */
192
+ type ResourceKind = 'browser' | 'context' | 'page' | 'request';
193
+ /** What the capture fixtures do with an object a test left open: list it, fail the test, or close it. */
194
+ type LeakCheck = 'report' | 'fail' | 'close';
195
+ /** The kind of code Playwright was running when an object was opened or closed. */
196
+ type ResourcePhase = 'test' | 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll' | 'worker';
197
+ /** A test, as a census and the objects it saw name it. */
198
+ interface ResourceTestRef {
199
+ id: string;
200
+ /** Spec file relative to the working directory, POSIX separators. */
201
+ file: string;
202
+ /** The describe blocks between the file and the test. */
203
+ suite: string[];
204
+ /** Set on the test a census closes. */
205
+ title?: string;
206
+ /** Set on the test a census closes when it failed: Playwright shuts its worker down right after it. */
207
+ failed?: boolean;
208
+ }
209
+ interface ResourceBirth {
210
+ id: number;
211
+ kind: ResourceKind;
212
+ /** The context of a page, the browser of a context. */
213
+ parent: number | null;
214
+ at: number;
215
+ phase: ResourcePhase;
216
+ /** The fixture being set up when the object was opened; `worker` when that fixture is worker-scoped. */
217
+ fixture: {
218
+ title: string;
219
+ location: string | null;
220
+ worker: boolean;
221
+ } | null;
222
+ /** The test it was opened during; for a `beforeAll`, the test that ran the hook. */
223
+ test: ResourceTestRef | null;
224
+ /** The line that opened it, `file:line`. */
225
+ site: string | null;
226
+ /** A popup: the page that opened it. */
227
+ opener?: number;
228
+ /** A popup: the call that was running or last ran when it appeared. */
229
+ trigger?: string;
230
+ /** A context `browser.newPage()` opened for its page. */
231
+ implicit?: boolean;
232
+ /** A context Playwright reuses across tests on purpose (UI mode, `--reuse-context`). */
233
+ reused?: boolean;
234
+ }
235
+ interface ResourceClose {
236
+ id: number;
237
+ at: number;
238
+ phase: ResourcePhase;
239
+ test: ResourceTestRef | null;
240
+ /** Pages: whether anything ever ran on it. */
241
+ used?: boolean;
242
+ /** Closed by `PIWI_LEAK_CHECK=close`. */
243
+ byPiwi?: boolean;
244
+ }
245
+ interface ResourceOpen {
246
+ id: number;
247
+ /** Pages: whether anything ran on it so far. */
248
+ used?: boolean;
249
+ /** Pages: the current URL, without query or hash. */
250
+ url?: string;
251
+ /** Pages: listeners on Playwright's page events. */
252
+ listeners?: number;
253
+ /** Pages and contexts: `route()` handlers. */
254
+ routes?: number;
255
+ /** Contexts: open pages. */
256
+ pages?: number;
257
+ /** Pages, over CDP: main-thread CPU and weight. */
258
+ main?: PageMain;
259
+ }
260
+ /** One census: what changed since the previous one in this worker, and what is open now. */
261
+ interface ResourceCensus {
262
+ v: 1;
263
+ /** Playwright's worker index and the worker's process id. */
264
+ worker: number;
265
+ pid: number;
266
+ at: number;
267
+ /** The test this census closes; null for the census a worker takes as it shuts down. */
268
+ test: ResourceTestRef | null;
269
+ born: ResourceBirth[];
270
+ closed: ResourceClose[];
271
+ open: ResourceOpen[];
272
+ /** Counts of the tracked Node handle types in the worker at the test's start and end. */
273
+ handles?: {
274
+ start: Record<string, number>;
275
+ end: Record<string, number>;
276
+ };
277
+ /** What the test cost the worker and the browsers it started. */
278
+ metrics?: TestMetrics;
279
+ /** Contexts and pages already open in the worker when the test started. */
280
+ openAtStart?: {
281
+ contexts: number;
282
+ pages: number;
283
+ };
284
+ /** Objects the test opened itself and left open (what `PIWI_LEAK_CHECK` judges). */
285
+ leftOpen?: number;
286
+ }
287
+ /** An object a test opened itself and left open: what `PIWI_LEAK_CHECK` fails or closes. */
288
+ interface LeakedResource {
289
+ id: number;
290
+ kind: ResourceKind;
291
+ site: string | null;
292
+ /** Contexts and browsers: how many open pages go with it. */
293
+ pages: number;
294
+ implicit: boolean;
295
+ /** A popup: the call that opened it. */
296
+ trigger?: string;
297
+ }
298
+ interface Instrumentation {
299
+ addListener?: (listener: object) => void;
300
+ removeListener?: (listener: object) => void;
301
+ }
302
+ type BrowserTypeLike = Record<string, unknown>;
303
+ interface PlaywrightLike {
304
+ _instrumentation?: Instrumentation;
305
+ chromium?: BrowserTypeLike;
306
+ firefox?: BrowserTypeLike;
307
+ webkit?: BrowserTypeLike;
308
+ }
309
+ /** The parts of Playwright's worker `TestInfo` the ledger reads (`_timeoutManager` is internal). */
310
+ interface TestInfoLike {
311
+ testId?: string;
312
+ title?: string;
313
+ file?: string;
314
+ titlePath?: string[];
315
+ status?: string;
316
+ expectedStatus?: string;
317
+ _timeoutManager?: {
318
+ _running?: {
319
+ runnable?: {
320
+ type?: string;
321
+ fixture?: {
322
+ title?: string;
323
+ location?: {
324
+ file?: string;
325
+ line?: number;
326
+ };
327
+ slot?: unknown;
328
+ };
329
+ };
330
+ } | null;
331
+ };
332
+ }
333
+ declare class ResourceLedger {
334
+ private readonly playwright;
335
+ readonly workerIndex: number;
336
+ readonly leakCheck: LeakCheck;
337
+ private readonly resultsFile;
338
+ private readonly testInfoSource;
339
+ private readonly now;
340
+ private readonly readers;
341
+ private nextId;
342
+ private readonly entries;
343
+ private readonly byObject;
344
+ private readonly pendingCalls;
345
+ private lastCall;
346
+ /** The page a wrapped page method is calling into: its first reported API call marks it used. */
347
+ private usingPage;
348
+ private born;
349
+ private closed;
350
+ private handlesAtStart;
351
+ private openAtStart;
352
+ /** The test between `testStarted` and `testEnded`, for when Playwright's own test info cannot be read. */
353
+ private window;
354
+ private listener;
355
+ /** A browser or a context was opened in this worker: there are browser processes for the metrics to read. */
356
+ private sawBrowser;
357
+ constructor(playwright: PlaywrightLike, workerIndex: number, leakCheck?: LeakCheck, resultsFile?: string | null, testInfoSource?: () => TestInfoLike | null, now?: () => number, readers?: {
358
+ metrics?: WorkerMetrics | null;
359
+ pages?: PageReader | null;
360
+ });
361
+ /** Listen to the worker's Playwright and wrap its browser types. False when the instrumentation is missing. */
362
+ install(): boolean;
363
+ /** Stop listening; the objects already tracked stay in the ledger. */
364
+ uninstall(): void;
365
+ /** Mark a page used: a locator was built on it. */
366
+ notePageUse(page: object): void;
367
+ /** Open the census window of a test. */
368
+ testStarted(info: TestInfoLike): void;
369
+ /**
370
+ * Close the census window of a test: apply `PIWI_LEAK_CHECK=close` to what the
371
+ * test left open, then take the census. Called after the test-scoped fixtures
372
+ * tore down, so what the test opened through them is closed by now.
373
+ */
374
+ testEnded(info: TestInfoLike): Promise<{
375
+ census: ResourceCensus;
376
+ leaks: LeakedResource[];
377
+ }>;
378
+ /** The open pages' main thread and weight, over CDP, within a deadline. */
379
+ private readPages;
380
+ /** The census a worker takes as it shuts down, appended to the results file when the reporter set one. */
381
+ workerEnded(): ResourceCensus;
382
+ private guard;
383
+ private wrapBrowserType;
384
+ private track;
385
+ private onBrowser;
386
+ private onContext;
387
+ /** `initial` is set for a page that came with its context (a persistent context's), which is no popup. */
388
+ private onPage;
389
+ private onClosed;
390
+ private latestPendingCall;
391
+ private openEntries;
392
+ /**
393
+ * Objects a test opened itself, by its body or its each-hooks, and left open.
394
+ * A page in a leaked context, or a context in a leaked browser, goes with it;
395
+ * the context `browser.newPage()` opened for its page goes with the page.
396
+ */
397
+ private leaksOf;
398
+ /** Open pages under a context, or under any context of a browser. */
399
+ private pagesUnder;
400
+ private isImplicit;
401
+ private openPagesOf;
402
+ private closeLeaks;
403
+ private census;
404
+ private pageUrl;
405
+ private listenerCount;
406
+ private routeCount;
407
+ }
408
+ /**
409
+ * Start this worker's ledger on the Playwright the worker fixtures hold, unless
410
+ * `PIWI_CAPTURE_RESOURCES=false` or that Playwright has no instrumentation to
411
+ * listen to. Starting it again returns the running one.
412
+ */
413
+ declare function startResourceLedger(playwright: unknown, workerIndex: number): ResourceLedger | null;
414
+ /** This worker's running ledger, if any. */
415
+ declare function activeResourceLedger(): ResourceLedger | null;
416
+ /** Take the shutdown census of this worker's ledger and stop it. */
417
+ declare function stopResourceLedger(): ResourceCensus | null;
418
+ /** Mark a page used, for the locator wrappers. */
419
+ declare function noteResourceUse(page: object): void;
420
+ /**
421
+ * Take the census at the end of a test, attach it as `piwi-resources`, and fail
422
+ * the test under `PIWI_LEAK_CHECK=fail` when it left open what it opened. A
423
+ * no-op when no ledger runs in this worker.
424
+ */
425
+ declare function recordResourceCensus(testInfo: TestInfo): Promise<void>;
426
+
69
427
  type FixtureArgs = {
70
428
  [key: string]: any;
71
429
  };
@@ -184,6 +542,17 @@ declare function callMethod(target: object, prop: string | symbol, args: unknown
184
542
  interface PiwiFixtures {
185
543
  piwiCapture: void;
186
544
  }
545
+ /**
546
+ * The worker fixture `piwiFixtures` / `extendPiwiFixtures` contribute:
547
+ * `piwiResources`, an auto, worker-scoped fixture that keeps the worker's
548
+ * resource ledger (the browsers, contexts, pages and API request contexts its
549
+ * tests open) from before the first hook to the worker's shutdown. Its name is
550
+ * **reserved** like `piwiCapture`'s: a fixture of your own with that name
551
+ * replaces it and turns the ledger off.
552
+ */
553
+ interface PiwiWorkerFixtures {
554
+ piwiResources: void;
555
+ }
187
556
  /**
188
557
  * Playwright fixtures that collect network requests, console entries,
189
558
  * web vitals, ARIA snapshots, and locator interaction data during a test.
@@ -193,7 +562,7 @@ interface PiwiFixtures {
193
562
  * `browser.newContext()`. Collected data is attached as `piwi-*`
194
563
  * test-info attachments which the Piwi Dashboard reporter parses on `onTestEnd`.
195
564
  */
196
- declare const piwiFixtures: Fixtures<PiwiFixtures, {}, PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
565
+ declare const piwiFixtures: Fixtures<PiwiFixtures, PiwiWorkerFixtures, PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
197
566
  /**
198
567
  * Extend a Playwright `test` object with the Piwi capture fixtures. The
199
568
  * returned `test` carries the existing fixtures plus {@link PiwiFixtures}.
@@ -210,6 +579,6 @@ declare const piwiFixtures: Fixtures<PiwiFixtures, {}, PlaywrightTestArgs & Play
210
579
  * export const test = extendPiwiFixtures(base);
211
580
  * ```
212
581
  */
213
- declare function extendPiwiFixtures<TestArgs extends FixtureArgs, WorkerArgs extends FixtureArgs>(test: TestType<TestArgs, WorkerArgs>): TestType<TestArgs & PiwiFixtures, WorkerArgs>;
582
+ declare function extendPiwiFixtures<TestArgs extends FixtureArgs, WorkerArgs extends FixtureArgs>(test: TestType<TestArgs, WorkerArgs>): TestType<TestArgs & PiwiFixtures, WorkerArgs & PiwiWorkerFixtures>;
214
583
 
215
- export { CAPTURED_ATTRS_ARG, LocatorPageLog, type PageState, type PiwiFixtures, type RawPageState, type RawVitalEntry, ariaSnapshotBestEffort, ariaSnapshotJSONBestEffort, boxCaptureFrames, buildPageState, callMethod, codeReachRoots, computeCoreVitals, configuredCodeReachRoots, extendPiwiFixtures, internalCall, noteLocatorCall, noteNavigation, pageMapFetcher, piwiFixtures, resolveCodeReach, startCodeReach, stopCodeReach };
584
+ 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, recordResourceCensus, resolveCodeReach, startCodeReach, startResourceLedger, stopCodeReach, stopResourceLedger };