@trackunit/iris-app-playwright 0.1.34 → 0.1.36
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/package.json +1 -1
- package/src/fixtures/harRecording.fixture.d.ts +51 -10
- package/src/fixtures/harRecording.fixture.js +71 -18
- package/src/fixtures/withTimeout.d.ts +10 -0
- package/src/fixtures/withTimeout.js +28 -0
- package/src/plugins/harRecordingPath.d.ts +26 -0
- package/src/plugins/harRecordingPath.js +24 -0
- package/src/plugins/logsReporter.d.ts +16 -7
- package/src/plugins/logsReporter.js +38 -31
- package/src/fixtures/finalizeHarRecording.d.ts +0 -27
- package/src/fixtures/finalizeHarRecording.js +0 -29
package/package.json
CHANGED
|
@@ -1,14 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Budget for the `context.close()` we issue after the test body. Closing is
|
|
3
|
+
* normally fast, but on a hard test timeout the page is often unresponsive and
|
|
4
|
+
* an unbounded close would consume this fixture's teardown budget. We bound it
|
|
5
|
+
* and move on; Playwright's built-in `context` teardown runs afterwards and
|
|
6
|
+
* awaits the real close within its own budget, so native video/trace
|
|
7
|
+
* persistence is never gated behind our close.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CONTEXT_CLOSE_TIMEOUT_MS = 10000;
|
|
10
|
+
/**
|
|
11
|
+
* Dedicated per-fixture teardown budget for the `context` override (see the tuple
|
|
12
|
+
* passed to `base.extend` below). Without it, this override's teardown draws from
|
|
13
|
+
* the single teardown budget shared with Playwright's built-in `context` teardown —
|
|
14
|
+
* so a slow close could starve native video/trace persistence. Giving the override
|
|
15
|
+
* its own budget decouples the two. It comfortably exceeds the bounded close; the
|
|
16
|
+
* unit test asserts that invariant.
|
|
17
|
+
*/
|
|
18
|
+
export declare const HAR_CONTEXT_FIXTURE_TIMEOUT_MS = 25000;
|
|
19
|
+
/** Minimal structural view of the browser context teardown needs. */
|
|
20
|
+
interface ClosableContext {
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Closes the context (bounded, best-effort).
|
|
25
|
+
*
|
|
26
|
+
* Extracted from the fixture so the teardown bounding can be unit tested without a
|
|
27
|
+
* browser. The bounded close guarantees we return control even if the page is hung,
|
|
28
|
+
* so Playwright's native context teardown (which persists video/trace) is never
|
|
29
|
+
* starved. The HAR is **not** handled here — `LogsReporter.onTestEnd` reads the raw
|
|
30
|
+
* HAR from `harRecordingPath` after all teardown, which is the only point that
|
|
31
|
+
* reliably runs after a late close flushes the HAR on the hard-timeout path.
|
|
32
|
+
*/
|
|
33
|
+
export declare const closeContextWithinBudget: (context: ClosableContext, options?: {
|
|
34
|
+
closeTimeoutMs?: number;
|
|
35
|
+
}) => Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Records a per-test HAR without leaking it into stored artifacts.
|
|
38
|
+
*
|
|
39
|
+
* `recordHar` (configured via `contextOptions`) only flushes the HAR to disk when
|
|
40
|
+
* the browser context closes. We record it to a temp path outside `outputDir`
|
|
41
|
+
* (see `harRecordingPath`) so a close that flushes late — e.g. during native
|
|
42
|
+
* teardown on a hard timeout — can never leak an unredacted HAR into uploaded
|
|
43
|
+
* artifacts. `LogsReporter.onTestEnd` is the single owner of that file: it runs
|
|
44
|
+
* after all teardown, redacts + retains it on failure, and always deletes the raw
|
|
45
|
+
* source. Persisting the HAR there (rather than via `testInfo.attach` during this
|
|
46
|
+
* fixture's teardown) is what makes a hard-timeout HAR survive — the attach seam
|
|
47
|
+
* ran before the late close flushed the file and lost it.
|
|
3
48
|
*
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* exist and any cleanup there is a no-op (this was the original leak: a `test.har` survived
|
|
9
|
-
* for every test). We therefore override `context` itself: after the test body we close the
|
|
10
|
-
* context to flush the HAR, then attach + clean it up via `finalizeHarRecording`. The
|
|
11
|
-
* built-in `context` teardown still runs afterwards — `close()` on an already-closed context
|
|
12
|
-
* is a no-op — so trace/video/screenshot retain-on-failure are unaffected.
|
|
49
|
+
* The `context` override exists only to bound the close so a hung page can't starve
|
|
50
|
+
* native video/trace persistence; it carries its own `timeout`
|
|
51
|
+
* (`HAR_CONTEXT_FIXTURE_TIMEOUT_MS`) so its teardown draws from a dedicated budget
|
|
52
|
+
* rather than the one shared with the built-in `context` teardown.
|
|
13
53
|
*/
|
|
14
54
|
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
55
|
+
export {};
|
|
@@ -1,32 +1,85 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.test = void 0;
|
|
3
|
+
exports.test = exports.closeContextWithinBudget = exports.HAR_CONTEXT_FIXTURE_TIMEOUT_MS = exports.CONTEXT_CLOSE_TIMEOUT_MS = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
4
5
|
const test_1 = require("@playwright/test");
|
|
5
|
-
const
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path = tslib_1.__importStar(require("path"));
|
|
8
|
+
const harRecordingPath_1 = require("../plugins/harRecordingPath");
|
|
9
|
+
const withTimeout_1 = require("./withTimeout");
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
11
|
+
* Budget for the `context.close()` we issue after the test body. Closing is
|
|
12
|
+
* normally fast, but on a hard test timeout the page is often unresponsive and
|
|
13
|
+
* an unbounded close would consume this fixture's teardown budget. We bound it
|
|
14
|
+
* and move on; Playwright's built-in `context` teardown runs afterwards and
|
|
15
|
+
* awaits the real close within its own budget, so native video/trace
|
|
16
|
+
* persistence is never gated behind our close.
|
|
17
|
+
*/
|
|
18
|
+
exports.CONTEXT_CLOSE_TIMEOUT_MS = 10000;
|
|
19
|
+
/**
|
|
20
|
+
* Dedicated per-fixture teardown budget for the `context` override (see the tuple
|
|
21
|
+
* passed to `base.extend` below). Without it, this override's teardown draws from
|
|
22
|
+
* the single teardown budget shared with Playwright's built-in `context` teardown —
|
|
23
|
+
* so a slow close could starve native video/trace persistence. Giving the override
|
|
24
|
+
* its own budget decouples the two. It comfortably exceeds the bounded close; the
|
|
25
|
+
* unit test asserts that invariant.
|
|
26
|
+
*/
|
|
27
|
+
exports.HAR_CONTEXT_FIXTURE_TIMEOUT_MS = 25000;
|
|
28
|
+
/**
|
|
29
|
+
* Closes the context (bounded, best-effort).
|
|
8
30
|
*
|
|
9
|
-
*
|
|
10
|
-
* browser
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
31
|
+
* Extracted from the fixture so the teardown bounding can be unit tested without a
|
|
32
|
+
* browser. The bounded close guarantees we return control even if the page is hung,
|
|
33
|
+
* so Playwright's native context teardown (which persists video/trace) is never
|
|
34
|
+
* starved. The HAR is **not** handled here — `LogsReporter.onTestEnd` reads the raw
|
|
35
|
+
* HAR from `harRecordingPath` after all teardown, which is the only point that
|
|
36
|
+
* reliably runs after a late close flushes the HAR on the hard-timeout path.
|
|
37
|
+
*/
|
|
38
|
+
const closeContextWithinBudget = async (context, options = {}) => {
|
|
39
|
+
try {
|
|
40
|
+
await (0, withTimeout_1.withTimeout)(context.close(), options.closeTimeoutMs ?? exports.CONTEXT_CLOSE_TIMEOUT_MS);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Bounded-out or failed close: leave it to Playwright's built-in context
|
|
44
|
+
// teardown to await the real close within its own budget.
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
exports.closeContextWithinBudget = closeContextWithinBudget;
|
|
48
|
+
/**
|
|
49
|
+
* Records a per-test HAR without leaking it into stored artifacts.
|
|
50
|
+
*
|
|
51
|
+
* `recordHar` (configured via `contextOptions`) only flushes the HAR to disk when
|
|
52
|
+
* the browser context closes. We record it to a temp path outside `outputDir`
|
|
53
|
+
* (see `harRecordingPath`) so a close that flushes late — e.g. during native
|
|
54
|
+
* teardown on a hard timeout — can never leak an unredacted HAR into uploaded
|
|
55
|
+
* artifacts. `LogsReporter.onTestEnd` is the single owner of that file: it runs
|
|
56
|
+
* after all teardown, redacts + retains it on failure, and always deletes the raw
|
|
57
|
+
* source. Persisting the HAR there (rather than via `testInfo.attach` during this
|
|
58
|
+
* fixture's teardown) is what makes a hard-timeout HAR survive — the attach seam
|
|
59
|
+
* ran before the late close flushed the file and lost it.
|
|
60
|
+
*
|
|
61
|
+
* The `context` override exists only to bound the close so a hung page can't starve
|
|
62
|
+
* native video/trace persistence; it carries its own `timeout`
|
|
63
|
+
* (`HAR_CONTEXT_FIXTURE_TIMEOUT_MS`) so its teardown draws from a dedicated budget
|
|
64
|
+
* rather than the one shared with the built-in `context` teardown.
|
|
18
65
|
*/
|
|
19
66
|
exports.test = test_1.test.extend({
|
|
20
67
|
contextOptions: async ({ contextOptions }, provide, testInfo) => {
|
|
68
|
+
const harPath = (0, harRecordingPath_1.harRecordingPath)(testInfo);
|
|
69
|
+
// `recordHar` doesn't create the parent directory; ensure it exists since the
|
|
70
|
+
// path lives in a temp dir outside Playwright's managed output tree.
|
|
71
|
+
(0, fs_1.mkdirSync)(path.dirname(harPath), { recursive: true });
|
|
21
72
|
await provide({
|
|
22
73
|
...contextOptions,
|
|
23
|
-
recordHar: { path:
|
|
74
|
+
recordHar: { path: harPath, mode: "minimal" },
|
|
24
75
|
});
|
|
25
76
|
},
|
|
26
|
-
context:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
77
|
+
context: [
|
|
78
|
+
async ({ context }, provide) => {
|
|
79
|
+
await provide(context);
|
|
80
|
+
await (0, exports.closeContextWithinBudget)(context);
|
|
81
|
+
},
|
|
82
|
+
{ scope: "test", timeout: exports.HAR_CONTEXT_FIXTURE_TIMEOUT_MS },
|
|
83
|
+
],
|
|
31
84
|
});
|
|
32
85
|
//# sourceMappingURL=harRecording.fixture.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Races `operation` against a timer, rejecting if it doesn't settle within `ms`.
|
|
3
|
+
*
|
|
4
|
+
* Note: this does not cancel `operation` — it keeps running in the background.
|
|
5
|
+
* That is intentional for the HAR teardown path: a bounded `context.close()`
|
|
6
|
+
* returns control so Playwright's built-in context teardown can await the real
|
|
7
|
+
* close within its own budget, rather than our override hogging the shared
|
|
8
|
+
* teardown budget that native video/trace persistence depends on.
|
|
9
|
+
*/
|
|
10
|
+
export declare const withTimeout: <TResult>(operation: Promise<TResult>, ms: number) => Promise<TResult>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withTimeout = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Races `operation` against a timer, rejecting if it doesn't settle within `ms`.
|
|
6
|
+
*
|
|
7
|
+
* Note: this does not cancel `operation` — it keeps running in the background.
|
|
8
|
+
* That is intentional for the HAR teardown path: a bounded `context.close()`
|
|
9
|
+
* returns control so Playwright's built-in context teardown can await the real
|
|
10
|
+
* close within its own budget, rather than our override hogging the shared
|
|
11
|
+
* teardown budget that native video/trace persistence depends on.
|
|
12
|
+
*/
|
|
13
|
+
const withTimeout = async (operation, ms) => {
|
|
14
|
+
let timer;
|
|
15
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
16
|
+
timer = setTimeout(() => reject(new Error(`withTimeout: operation exceeded ${ms}ms`)), ms);
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
return await Promise.race([operation, timeout]);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
if (timer !== undefined) {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.withTimeout = withTimeout;
|
|
28
|
+
//# sourceMappingURL=withTimeout.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Name of the per-test HAR file written by `recordHar`. */
|
|
2
|
+
export declare const HAR_FILENAME = "test.har";
|
|
3
|
+
/**
|
|
4
|
+
* Identifies a single test attempt. `testId` is Playwright's `TestInfo.testId`,
|
|
5
|
+
* which is documented to equal `TestCase.id` in the reporter API — so the
|
|
6
|
+
* `harRecording` fixture (which has `TestInfo`) and `LogsReporter` (which has
|
|
7
|
+
* `TestCase`/`TestResult`) compute the exact same path for the same attempt.
|
|
8
|
+
*/
|
|
9
|
+
export interface HarRecordingPathInfo {
|
|
10
|
+
testId: string;
|
|
11
|
+
retry: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Absolute path the per-test HAR is recorded to, deliberately **outside** the
|
|
15
|
+
* Playwright `outputDir` (which is uploaded wholesale as CI artifacts).
|
|
16
|
+
*
|
|
17
|
+
* `recordHar` only flushes when the browser context closes, and on a hard test
|
|
18
|
+
* timeout that close completes during Playwright's native context teardown —
|
|
19
|
+
* after the `harRecording` fixture's own teardown has already run. Recording
|
|
20
|
+
* here (a temp dir, never uploaded) means that late flush writes a raw,
|
|
21
|
+
* unredacted HAR (it carries Authorization/Cookie) somewhere CI never stores.
|
|
22
|
+
* `LogsReporter.onTestEnd` runs after all teardown, reads this path, and writes
|
|
23
|
+
* only the **redacted** copy into the artifacts `hars/` folder. Keyed by
|
|
24
|
+
* `testId` + `retry` so parallel workers and retries never collide.
|
|
25
|
+
*/
|
|
26
|
+
export declare const harRecordingPath: (info: HarRecordingPathInfo) => string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.harRecordingPath = exports.HAR_FILENAME = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const os_1 = require("os");
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
/** Name of the per-test HAR file written by `recordHar`. */
|
|
8
|
+
exports.HAR_FILENAME = "test.har";
|
|
9
|
+
/**
|
|
10
|
+
* Absolute path the per-test HAR is recorded to, deliberately **outside** the
|
|
11
|
+
* Playwright `outputDir` (which is uploaded wholesale as CI artifacts).
|
|
12
|
+
*
|
|
13
|
+
* `recordHar` only flushes when the browser context closes, and on a hard test
|
|
14
|
+
* timeout that close completes during Playwright's native context teardown —
|
|
15
|
+
* after the `harRecording` fixture's own teardown has already run. Recording
|
|
16
|
+
* here (a temp dir, never uploaded) means that late flush writes a raw,
|
|
17
|
+
* unredacted HAR (it carries Authorization/Cookie) somewhere CI never stores.
|
|
18
|
+
* `LogsReporter.onTestEnd` runs after all teardown, reads this path, and writes
|
|
19
|
+
* only the **redacted** copy into the artifacts `hars/` folder. Keyed by
|
|
20
|
+
* `testId` + `retry` so parallel workers and retries never collide.
|
|
21
|
+
*/
|
|
22
|
+
const harRecordingPath = (info) => path.join((0, os_1.tmpdir)(), "tu-iris-playwright-har", `${info.testId}-attempt-${info.retry}.${exports.HAR_FILENAME}`);
|
|
23
|
+
exports.harRecordingPath = harRecordingPath;
|
|
24
|
+
//# sourceMappingURL=harRecordingPath.js.map
|
|
@@ -42,14 +42,23 @@ export declare class LogsReporter implements Reporter {
|
|
|
42
42
|
onEnd(_result: FullResult): Promise<void>;
|
|
43
43
|
/** Returns false so Playwright adds its own terminal reporter alongside this one. */
|
|
44
44
|
printsToStdio(): boolean;
|
|
45
|
+
private getResultTracePath;
|
|
45
46
|
/**
|
|
46
|
-
*
|
|
47
|
-
* `
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
47
|
+
* Persists (on failure) or cleans up (always) the per-test HAR recorded by the
|
|
48
|
+
* `harRecording` fixture.
|
|
49
|
+
*
|
|
50
|
+
* The raw HAR is recorded to a deterministic temp path outside `outputDir`
|
|
51
|
+
* (see `harRecordingPath`). `recordHar` only flushes it when the browser
|
|
52
|
+
* context closes — and on a hard timeout that close completes during
|
|
53
|
+
* Playwright's *native* context teardown, after the fixture's own teardown has
|
|
54
|
+
* bounded out. Because `onTestEnd` runs after all teardown, the file is
|
|
55
|
+
* reliably present here regardless of how late the close flushed: this is what
|
|
56
|
+
* makes a hard-timeout HAR survive (the earlier `testInfo.attach` seam ran too
|
|
57
|
+
* early on that path and lost it). On failure we redact (the HAR carries
|
|
58
|
+
* Authorization/Cookie and request/response bodies) and move it into the shared
|
|
59
|
+
* `hars/` artifacts folder; in every case we delete the raw temp source via
|
|
60
|
+
* `finally` so an unredacted HAR never lingers on disk, even if redaction throws.
|
|
51
61
|
*/
|
|
52
|
-
private
|
|
53
|
-
private getResultTracePath;
|
|
62
|
+
private persistHar;
|
|
54
63
|
}
|
|
55
64
|
export default LogsReporter;
|
|
@@ -6,6 +6,7 @@ const fs_1 = require("fs");
|
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
7
|
const fileNameBuilder_1 = require("../utils/fileNameBuilder");
|
|
8
8
|
const createLogFile_1 = require("./createLogFile");
|
|
9
|
+
const harRecordingPath_1 = require("./harRecordingPath");
|
|
9
10
|
const redactHar_1 = require("./redactHar");
|
|
10
11
|
const redactSensitive_1 = require("./redactSensitive");
|
|
11
12
|
const redactTrace_1 = require("./redactTrace");
|
|
@@ -41,7 +42,8 @@ class LogsReporter {
|
|
|
41
42
|
const describeTitle = titlePath.slice(0, -1).join(" > ");
|
|
42
43
|
const testTitle = test.title;
|
|
43
44
|
const fullTitle = describeTitle ? `${describeTitle} - ${testTitle}` : testTitle;
|
|
44
|
-
|
|
45
|
+
const isFailure = result.status === "failed" || result.status === "timedOut";
|
|
46
|
+
if (isFailure) {
|
|
45
47
|
const fileName = (0, fileNameBuilder_1.fileNameBuilder)(fullTitle, "failed", result.retry);
|
|
46
48
|
const logs = result.stdout.concat(result.stderr).map(entry => {
|
|
47
49
|
const message = typeof entry === "string" ? entry : entry.toString();
|
|
@@ -52,19 +54,6 @@ class LogsReporter {
|
|
|
52
54
|
};
|
|
53
55
|
});
|
|
54
56
|
this.pendingWrites.push((0, createLogFile_1.createLogFile)(this.nxRoot, this.logsFolder, fileName, logs, this.formatter));
|
|
55
|
-
if (this.harsFolder !== undefined) {
|
|
56
|
-
const tempHarPath = this.getResultHarPath(result);
|
|
57
|
-
if (tempHarPath !== undefined && (0, fs_1.existsSync)(tempHarPath)) {
|
|
58
|
-
if (!(0, fs_1.existsSync)(this.harsFolder)) {
|
|
59
|
-
(0, fs_1.mkdirSync)(this.harsFolder, { recursive: true });
|
|
60
|
-
}
|
|
61
|
-
const harFileName = `${(0, fileNameBuilder_1.fileNameBuilder)(fullTitle, "failed", result.retry)}.har`;
|
|
62
|
-
// HARs include Authorization, Cookie, and request/response bodies for
|
|
63
|
-
// authenticated traffic. Redact before moving to the shared artifacts folder.
|
|
64
|
-
(0, redactHar_1.redactHarFile)(tempHarPath, path.join(this.harsFolder, harFileName));
|
|
65
|
-
(0, fs_1.rmSync)(tempHarPath);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
57
|
// Defense in depth for trace artifacts: `login()`'s slow path does
|
|
69
58
|
// `page.goto("/auth/manager-classic#session_token=...")`, and Playwright
|
|
70
59
|
// records the full `goto` URL as the action argument. With
|
|
@@ -78,12 +67,7 @@ class LogsReporter {
|
|
|
78
67
|
this.pendingWrites.push((0, redactTrace_1.redactTraceFile)(tracePath));
|
|
79
68
|
}
|
|
80
69
|
}
|
|
81
|
-
|
|
82
|
-
const tempHarPath = this.getResultHarPath(result);
|
|
83
|
-
if (tempHarPath !== undefined && (0, fs_1.existsSync)(tempHarPath)) {
|
|
84
|
-
(0, fs_1.rmSync)(tempHarPath);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
70
|
+
this.persistHar(fullTitle, test, result, isFailure);
|
|
87
71
|
}
|
|
88
72
|
/**
|
|
89
73
|
* Awaits in-flight log writes started in `onTestEnd` before the reporter
|
|
@@ -98,21 +82,44 @@ class LogsReporter {
|
|
|
98
82
|
printsToStdio() {
|
|
99
83
|
return false;
|
|
100
84
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Returns the HAR attachment path for *this specific result*. Looking at
|
|
103
|
-
* `test.results.flatMap(r => r.attachments)` would mix attachments across
|
|
104
|
-
* retries — on a retry, the first attempt's HAR could be retained instead
|
|
105
|
-
* of (or alongside) the current attempt's HAR. Per-result scoping keeps
|
|
106
|
-
* each attempt's artifacts paired with its own log file.
|
|
107
|
-
*/
|
|
108
|
-
getResultHarPath(result) {
|
|
109
|
-
const attachment = result.attachments.find(a => a.name === "har" && a.path !== undefined);
|
|
110
|
-
return attachment?.path;
|
|
111
|
-
}
|
|
112
85
|
getResultTracePath(result) {
|
|
113
86
|
const attachment = result.attachments.find(a => a.name === "trace" && a.path !== undefined);
|
|
114
87
|
return attachment?.path;
|
|
115
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Persists (on failure) or cleans up (always) the per-test HAR recorded by the
|
|
91
|
+
* `harRecording` fixture.
|
|
92
|
+
*
|
|
93
|
+
* The raw HAR is recorded to a deterministic temp path outside `outputDir`
|
|
94
|
+
* (see `harRecordingPath`). `recordHar` only flushes it when the browser
|
|
95
|
+
* context closes — and on a hard timeout that close completes during
|
|
96
|
+
* Playwright's *native* context teardown, after the fixture's own teardown has
|
|
97
|
+
* bounded out. Because `onTestEnd` runs after all teardown, the file is
|
|
98
|
+
* reliably present here regardless of how late the close flushed: this is what
|
|
99
|
+
* makes a hard-timeout HAR survive (the earlier `testInfo.attach` seam ran too
|
|
100
|
+
* early on that path and lost it). On failure we redact (the HAR carries
|
|
101
|
+
* Authorization/Cookie and request/response bodies) and move it into the shared
|
|
102
|
+
* `hars/` artifacts folder; in every case we delete the raw temp source via
|
|
103
|
+
* `finally` so an unredacted HAR never lingers on disk, even if redaction throws.
|
|
104
|
+
*/
|
|
105
|
+
persistHar(fullTitle, test, result, isFailure) {
|
|
106
|
+
const harPath = (0, harRecordingPath_1.harRecordingPath)({ testId: test.id, retry: result.retry });
|
|
107
|
+
if (!(0, fs_1.existsSync)(harPath)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
if (isFailure && this.harsFolder !== undefined) {
|
|
112
|
+
if (!(0, fs_1.existsSync)(this.harsFolder)) {
|
|
113
|
+
(0, fs_1.mkdirSync)(this.harsFolder, { recursive: true });
|
|
114
|
+
}
|
|
115
|
+
const harFileName = `${(0, fileNameBuilder_1.fileNameBuilder)(fullTitle, "failed", result.retry)}.har`;
|
|
116
|
+
(0, redactHar_1.redactHarFile)(harPath, path.join(this.harsFolder, harFileName));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
(0, fs_1.rmSync)(harPath, { force: true });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
116
123
|
}
|
|
117
124
|
exports.LogsReporter = LogsReporter;
|
|
118
125
|
exports.default = LogsReporter;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/** Name of the per-test HAR file written by `recordHar` and read by `LogsReporter`. */
|
|
2
|
-
export declare const HAR_FILENAME = "test.har";
|
|
3
|
-
/**
|
|
4
|
-
* Minimal slice of Playwright's `TestInfo` needed to finalize HAR recording.
|
|
5
|
-
* Declared structurally so the behavior can be unit-tested without constructing
|
|
6
|
-
* a full `TestInfo`; the real `TestInfo` satisfies it.
|
|
7
|
-
*/
|
|
8
|
-
export interface HarTestInfo {
|
|
9
|
-
outputPath(...pathSegments: Array<string>): string;
|
|
10
|
-
attach(name: string, options: {
|
|
11
|
-
path: string;
|
|
12
|
-
}): Promise<void>;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Attaches the per-test HAR (so it appears in the HTML report and is discoverable
|
|
16
|
-
* by `LogsReporter`), then deletes the original `recordHar` output file.
|
|
17
|
-
*
|
|
18
|
-
* `recordHar` writes the HAR to `testInfo.outputPath("test.har")`. `testInfo.attach`
|
|
19
|
-
* copies that file into Playwright's managed attachments location and points the
|
|
20
|
-
* attachment at the copy — which is the only path `LogsReporter` ever sees and
|
|
21
|
-
* cleans up (deleted on pass, redacted and retained on failure). The original file
|
|
22
|
-
* is left untouched, so without this deletion every test (passing included) leaks a
|
|
23
|
-
* `test.har` into the persisted workspace and stored CI artifacts, growing with the
|
|
24
|
-
* suite. Removing it here closes the leak while leaving the attachment copy for the
|
|
25
|
-
* reporter to retain on failure.
|
|
26
|
-
*/
|
|
27
|
-
export declare const finalizeHarRecording: (testInfo: HarTestInfo) => Promise<void>;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.finalizeHarRecording = exports.HAR_FILENAME = void 0;
|
|
4
|
-
const fs_1 = require("fs");
|
|
5
|
-
/** Name of the per-test HAR file written by `recordHar` and read by `LogsReporter`. */
|
|
6
|
-
exports.HAR_FILENAME = "test.har";
|
|
7
|
-
/**
|
|
8
|
-
* Attaches the per-test HAR (so it appears in the HTML report and is discoverable
|
|
9
|
-
* by `LogsReporter`), then deletes the original `recordHar` output file.
|
|
10
|
-
*
|
|
11
|
-
* `recordHar` writes the HAR to `testInfo.outputPath("test.har")`. `testInfo.attach`
|
|
12
|
-
* copies that file into Playwright's managed attachments location and points the
|
|
13
|
-
* attachment at the copy — which is the only path `LogsReporter` ever sees and
|
|
14
|
-
* cleans up (deleted on pass, redacted and retained on failure). The original file
|
|
15
|
-
* is left untouched, so without this deletion every test (passing included) leaks a
|
|
16
|
-
* `test.har` into the persisted workspace and stored CI artifacts, growing with the
|
|
17
|
-
* suite. Removing it here closes the leak while leaving the attachment copy for the
|
|
18
|
-
* reporter to retain on failure.
|
|
19
|
-
*/
|
|
20
|
-
const finalizeHarRecording = async (testInfo) => {
|
|
21
|
-
const harPath = testInfo.outputPath(exports.HAR_FILENAME);
|
|
22
|
-
if (!(0, fs_1.existsSync)(harPath)) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
await testInfo.attach("har", { path: harPath });
|
|
26
|
-
(0, fs_1.rmSync)(harPath, { force: true });
|
|
27
|
-
};
|
|
28
|
-
exports.finalizeHarRecording = finalizeHarRecording;
|
|
29
|
-
//# sourceMappingURL=finalizeHarRecording.js.map
|