@empiricalrun/playwright-utils 0.29.0 → 0.30.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/CHANGELOG.md +6 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +9 -10
- package/dist/test/index.d.ts +2 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +58 -13
- package/dist/test/video-labels.d.ts +5 -0
- package/dist/test/video-labels.d.ts.map +1 -0
- package/dist/test/video-labels.js +25 -0
- package/docs/video-labels.md +42 -0
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAczD,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;AAwCD,eAAO,MAAM,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAczD,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;AAwCD,eAAO,MAAM,UAAU,EAAE,oBAyBxB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,iBAarB,CAAC"}
|
package/dist/config/index.js
CHANGED
|
@@ -77,20 +77,19 @@ exports.baseConfig = {
|
|
|
77
77
|
workers: undefined,
|
|
78
78
|
// maxFailures counts retries as individual failures, so this is equivalent to 20 failed tests
|
|
79
79
|
maxFailures: process.env.CI ? 30 : undefined,
|
|
80
|
+
timeout: 15 * 60 * 1_000,
|
|
81
|
+
expect: {
|
|
82
|
+
timeout: 15_000,
|
|
83
|
+
},
|
|
80
84
|
use: {
|
|
81
|
-
screenshot: "on",
|
|
82
|
-
trace: "on",
|
|
83
|
-
video: {
|
|
84
|
-
mode: "on",
|
|
85
|
-
size: { width: 1280, height: 720 },
|
|
86
|
-
},
|
|
87
85
|
actionTimeout: 15_000,
|
|
88
86
|
navigationTimeout: 30_000,
|
|
87
|
+
screenshot: "on",
|
|
88
|
+
trace: "on",
|
|
89
|
+
// Video recording is handled by the context fixture override
|
|
90
|
+
// so that we can use video labels
|
|
91
|
+
video: "off",
|
|
89
92
|
},
|
|
90
|
-
expect: {
|
|
91
|
-
timeout: 15_000,
|
|
92
|
-
},
|
|
93
|
-
timeout: 15 * 60 * 1_000,
|
|
94
93
|
};
|
|
95
94
|
exports.devices = Object.entries(deviceDescriptorsSource_json_1.default).reduce((acc, [name, device]) => ({
|
|
96
95
|
...acc,
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BrowserContext, BrowserContextOptions, expect, Page, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestType } from "@playwright/test";
|
|
2
2
|
import { injectLocatorHighlightScripts } from "./scripts";
|
|
3
3
|
import { HighlighterOpts } from "./types";
|
|
4
|
+
import { setVideoLabel } from "./video-labels";
|
|
4
5
|
declare global {
|
|
5
6
|
namespace PlaywrightTest {
|
|
6
7
|
interface Matchers<R> {
|
|
@@ -18,5 +19,5 @@ type TestOptions = {
|
|
|
18
19
|
saveVideos: void;
|
|
19
20
|
};
|
|
20
21
|
declare const baseTestFixture: (testFn: TestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>, options?: HighlighterOpts) => TestType<PlaywrightTestArgs & PlaywrightTestOptions & TestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
|
|
21
|
-
export { baseTestFixture, extendExpect, injectLocatorHighlightScripts };
|
|
22
|
+
export { baseTestFixture, extendExpect, injectLocatorHighlightScripts, setVideoLabel, };
|
|
22
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/test/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,IAAI,EACJ,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,QAAQ,EACT,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,IAAI,EACJ,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAA8B,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE3E,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,cAAc,CAAC;QACvB,UAAU,QAAQ,CAAC,CAAC;YAClB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;SAC9C;KACF;CACF;AAED,QAAA,MAAM,YAAY,GAAa,gBAAgB,OAAO,MAAM,0CAG3D,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,yBAAyB,EAAE,CACzB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,OAAO,CAAC;QAAE,OAAO,EAAE,cAAc,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACtD,UAAU,EAAE,IAAI,CAAC;CAClB,CAAC;AAYF,QAAA,MAAM,eAAe,GACnB,QAAQ,QAAQ,CACd,kBAAkB,GAAG,qBAAqB,EAC1C,oBAAoB,GAAG,uBAAuB,CAC/C,EACD,UAAS,eAAgC,uHAmG1C,CAAC;AAEF,OAAO,EACL,eAAe,EACf,YAAY,EACZ,6BAA6B,EAC7B,aAAa,GACd,CAAC"}
|
package/dist/test/index.js
CHANGED
|
@@ -3,19 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.injectLocatorHighlightScripts = exports.extendExpect = exports.baseTestFixture = void 0;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
6
|
+
exports.setVideoLabel = exports.injectLocatorHighlightScripts = exports.extendExpect = exports.baseTestFixture = void 0;
|
|
8
7
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const rimraf_1 = require("rimraf");
|
|
10
8
|
const expect_1 = require("./expect");
|
|
11
9
|
const scripts_1 = require("./scripts");
|
|
12
10
|
Object.defineProperty(exports, "injectLocatorHighlightScripts", { enumerable: true, get: function () { return scripts_1.injectLocatorHighlightScripts; } });
|
|
11
|
+
const video_labels_1 = require("./video-labels");
|
|
12
|
+
Object.defineProperty(exports, "setVideoLabel", { enumerable: true, get: function () { return video_labels_1.setVideoLabel; } });
|
|
13
13
|
const extendExpect = function (expectInstance) {
|
|
14
14
|
expectInstance.extend({ toLookRight: expect_1.toLookRight });
|
|
15
15
|
return expectInstance;
|
|
16
16
|
};
|
|
17
17
|
exports.extendExpect = extendExpect;
|
|
18
|
-
|
|
18
|
+
// Track pages per test to save videos with labels
|
|
19
|
+
const testPages = new Map();
|
|
19
20
|
const defaultOptions = {
|
|
20
21
|
mousePointerHighlighter: true,
|
|
21
22
|
locatorHighlighter: true,
|
|
@@ -24,6 +25,22 @@ const defaultOptions = {
|
|
|
24
25
|
};
|
|
25
26
|
const baseTestFixture = function (testFn, options = defaultOptions) {
|
|
26
27
|
const extendedTestFn = testFn.extend({
|
|
28
|
+
context: async ({ browser }, use, testInfo) => {
|
|
29
|
+
const context = await browser.newContext({
|
|
30
|
+
recordVideo: {
|
|
31
|
+
dir: testInfo.outputDir,
|
|
32
|
+
size: { width: 1280, height: 720 },
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const pages = [];
|
|
36
|
+
context.on("page", (page) => pages.push(page));
|
|
37
|
+
await use(context);
|
|
38
|
+
if (!testPages.has(testInfo.testId)) {
|
|
39
|
+
testPages.set(testInfo.testId, []);
|
|
40
|
+
}
|
|
41
|
+
testPages.get(testInfo.testId).push(...pages);
|
|
42
|
+
await context.close();
|
|
43
|
+
},
|
|
27
44
|
page: async ({ page }, use) => {
|
|
28
45
|
(0, scripts_1.injectLocatorHighlightScripts)(page, extendedTestFn, {
|
|
29
46
|
...defaultOptions,
|
|
@@ -37,32 +54,60 @@ const baseTestFixture = function (testFn, options = defaultOptions) {
|
|
|
37
54
|
const pageContext = await browser.newContext({
|
|
38
55
|
...contextOptions,
|
|
39
56
|
recordVideo: {
|
|
40
|
-
dir:
|
|
57
|
+
dir: testInfo.outputDir,
|
|
58
|
+
size: { width: 1280, height: 720 },
|
|
41
59
|
},
|
|
42
60
|
});
|
|
43
|
-
|
|
61
|
+
const pages = [];
|
|
62
|
+
pageContext.on("page", (page) => pages.push(page));
|
|
44
63
|
const customPage = await pageContext.newPage();
|
|
64
|
+
contexts.push({ context: pageContext, pages });
|
|
45
65
|
(0, scripts_1.injectLocatorHighlightScripts)(customPage, extendedTestFn, options);
|
|
46
66
|
return { context: pageContext, page: customPage };
|
|
47
67
|
}
|
|
48
68
|
await use(createContext);
|
|
49
|
-
|
|
69
|
+
// Store pages for video renaming
|
|
70
|
+
if (!testPages.has(testInfo.testId)) {
|
|
71
|
+
testPages.set(testInfo.testId, []);
|
|
72
|
+
}
|
|
73
|
+
for (const { pages } of contexts) {
|
|
74
|
+
testPages.get(testInfo.testId).push(...pages);
|
|
75
|
+
}
|
|
76
|
+
for (const { context } of contexts) {
|
|
50
77
|
await context.close();
|
|
51
78
|
}
|
|
52
79
|
},
|
|
53
80
|
saveVideos: [
|
|
54
81
|
async ({}, use, testInfo) => {
|
|
55
82
|
await use();
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
const pages = testPages.get(testInfo.testId) || [];
|
|
84
|
+
const usedLabels = new Set();
|
|
85
|
+
// Save videos with custom labels using saveAs
|
|
86
|
+
for (let i = 0; i < pages.length; i++) {
|
|
87
|
+
const page = pages[i];
|
|
88
|
+
if (!page)
|
|
89
|
+
continue;
|
|
90
|
+
const video = page.video();
|
|
91
|
+
if (!video)
|
|
92
|
+
continue;
|
|
93
|
+
try {
|
|
94
|
+
const srcPath = await video.path();
|
|
95
|
+
const ext = path_1.default.extname(srcPath) || ".webm";
|
|
96
|
+
const rawLabel = (0, video_labels_1.getVideoLabel)(page) || `video-${i}`;
|
|
97
|
+
const label = (0, video_labels_1.dedupeLabel)(rawLabel, usedLabels);
|
|
98
|
+
const outPath = testInfo.outputPath(`${label}${ext}`);
|
|
99
|
+
await video.saveAs(outPath);
|
|
100
|
+
await testInfo.attach(`video: ${label}`, {
|
|
101
|
+
path: outPath,
|
|
61
102
|
contentType: "video/webm",
|
|
62
103
|
});
|
|
104
|
+
await video.delete();
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
// Skip if video not available
|
|
63
108
|
}
|
|
64
|
-
(0, rimraf_1.rimrafSync)(pathToTestVideos);
|
|
65
109
|
}
|
|
110
|
+
testPages.delete(testInfo.testId);
|
|
66
111
|
},
|
|
67
112
|
{ auto: true },
|
|
68
113
|
],
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Page } from "@playwright/test";
|
|
2
|
+
export declare function setVideoLabel(page: Page, label: string): void;
|
|
3
|
+
export declare function getVideoLabel(page: Page): string | undefined;
|
|
4
|
+
export declare function dedupeLabel(baseLabel: string, usedLabels: Set<string>): string;
|
|
5
|
+
//# sourceMappingURL=video-labels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-labels.d.ts","sourceRoot":"","sources":["../../src/test/video-labels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAQ7C,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAGtD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAE5D;AAED,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,MAAM,CAQR"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setVideoLabel = setVideoLabel;
|
|
4
|
+
exports.getVideoLabel = getVideoLabel;
|
|
5
|
+
exports.dedupeLabel = dedupeLabel;
|
|
6
|
+
const videoLabels = new WeakMap();
|
|
7
|
+
function sanitizeLabel(label) {
|
|
8
|
+
return label.replace(/[^a-z0-9._-]/gi, "_").slice(0, 80) || "video";
|
|
9
|
+
}
|
|
10
|
+
function setVideoLabel(page, label) {
|
|
11
|
+
// Sanitize immediately when setting
|
|
12
|
+
videoLabels.set(page, sanitizeLabel(label));
|
|
13
|
+
}
|
|
14
|
+
function getVideoLabel(page) {
|
|
15
|
+
return videoLabels.get(page);
|
|
16
|
+
}
|
|
17
|
+
function dedupeLabel(baseLabel, usedLabels) {
|
|
18
|
+
let label = baseLabel;
|
|
19
|
+
let counter = 1;
|
|
20
|
+
while (usedLabels.has(label)) {
|
|
21
|
+
label = `${baseLabel}-${counter++}`;
|
|
22
|
+
}
|
|
23
|
+
usedLabels.add(label);
|
|
24
|
+
return label;
|
|
25
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Video Labels
|
|
2
|
+
|
|
3
|
+
Pages generate video recordings after test execution, with 1 page generating 1 video file (webm).
|
|
4
|
+
|
|
5
|
+
If your test case relies on multiple pages (e.g. for multi-user or multi-app flows), it can get difficult to
|
|
6
|
+
know which page does "video-1.webm" belong to.
|
|
7
|
+
|
|
8
|
+
To solve this, you should set video labels for pages. This will enable you to identify videos faster.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { setVideoLabel } from 'playwright-utils/test';
|
|
14
|
+
|
|
15
|
+
test('my test', async ({ page }) => {
|
|
16
|
+
setVideoLabel(page, 'checkout-flow');
|
|
17
|
+
// Video will be saved as 'checkout-flow.webm'
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Multiple Contexts
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
test('multi-user scenario', async ({ page, customContextPageProvider }) => {
|
|
25
|
+
setVideoLabel(page, 'host-page');
|
|
26
|
+
|
|
27
|
+
const { page: guestPage } = await customContextPageProvider({ storageState: undefined });
|
|
28
|
+
setVideoLabel(guestPage, 'guest-page');
|
|
29
|
+
// Videos saved as 'guest-page.webm' and 'host-page.webm'
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Default Behavior
|
|
34
|
+
|
|
35
|
+
If no label is set:
|
|
36
|
+
- Default `page` fixture: `video.webm`
|
|
37
|
+
- `customContextPageProvider`: `video-0.webm`, `video-1.webm`, etc.
|
|
38
|
+
|
|
39
|
+
## Note
|
|
40
|
+
|
|
41
|
+
If setVideoLabel is called twice for the same page:
|
|
42
|
+
- The last label will be set
|
package/package.json
CHANGED
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/email.ts","./src/index.ts","./src/logger.ts","./src/playwright-extensions.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/captcha/vision.ts","./src/config/index.ts","./src/config/devices/types.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/reporter/empirical-reporter.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/expect.ts","./src/test/index.ts","./src/test/types.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-highlights.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/mouse-pointer.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/highlight/click.ts","./src/test/scripts/pw-locator-patch/highlight/expect.ts","./src/test/scripts/pw-locator-patch/highlight/hover.ts","./src/test/scripts/pw-locator-patch/highlight/inner-text.ts","./src/test/scripts/pw-locator-patch/highlight/input-value.ts","./src/test/scripts/pw-locator-patch/highlight/is-checked.ts","./src/test/scripts/pw-locator-patch/highlight/is-disabled.ts","./src/test/scripts/pw-locator-patch/highlight/is-editable.ts","./src/test/scripts/pw-locator-patch/highlight/text-content.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"],"version":"5.8.3"}
|
|
1
|
+
{"root":["./src/email.ts","./src/index.ts","./src/logger.ts","./src/playwright-extensions.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/captcha/vision.ts","./src/config/index.ts","./src/config/devices/types.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/reporter/empirical-reporter.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/expect.ts","./src/test/index.ts","./src/test/types.ts","./src/test/video-labels.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-highlights.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/mouse-pointer.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/highlight/click.ts","./src/test/scripts/pw-locator-patch/highlight/expect.ts","./src/test/scripts/pw-locator-patch/highlight/hover.ts","./src/test/scripts/pw-locator-patch/highlight/inner-text.ts","./src/test/scripts/pw-locator-patch/highlight/input-value.ts","./src/test/scripts/pw-locator-patch/highlight/is-checked.ts","./src/test/scripts/pw-locator-patch/highlight/is-disabled.ts","./src/test/scripts/pw-locator-patch/highlight/is-editable.ts","./src/test/scripts/pw-locator-patch/highlight/text-content.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"],"version":"5.8.3"}
|