@empiricalrun/playwright-utils 0.29.0 → 0.30.1

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @empiricalrun/playwright-utils
2
2
 
3
+ ## 0.30.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [29eaffb]
8
+ - Updated dependencies [897486a]
9
+ - Updated dependencies [758c41a]
10
+ - @empiricalrun/test-gen@0.78.1
11
+ - @empiricalrun/llm@0.25.0
12
+
13
+ ## 0.30.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 7cbf29a: feat: add setVideoLabel for multi-page scenarios to have labels
18
+
3
19
  ## 0.29.0
4
20
 
5
21
  ### Minor Changes
@@ -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,oBA0BxB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,iBAarB,CAAC"}
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"}
@@ -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,
@@ -1,4 +1,4 @@
1
- import { GenericPlaywrightAttachment, TestCaseRunEndEventV2 } from "@empiricalrun/shared-types";
1
+ import { GenericPlaywrightAttachment, TestCaseRunEndEventV2 } from "@empiricalrun/shared-types/playwright-utils";
2
2
  import { JSONReportSpec, JSONReport as PlaywrightJSONReport, TestCase } from "@playwright/test/reporter";
3
3
  export declare function getPackageJsonPath(folderPath: string): string;
4
4
  export declare function resolveReporterOutputPath(defaultValue: string, configDir: string, configValue: string | undefined): string;
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/reporter/util.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,cAAc,EAGd,UAAU,IAAI,oBAAoB,EAElC,QAAQ,EACT,MAAM,2BAA2B,CAAC;AAoBnC,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAmB7D;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,UAMhC;AAED,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5E,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAyCD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ;;;EAwBrD;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAyDf;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GACnC,GAAG,CAIL;AAED,wBAAsB,gCAAgC,CACpD,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,iBAuFjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,GAAG,EACb,iBAAiB,EAAE,GAAG,CACpB,MAAM,EACN;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CACjD,QA+BF;AAGD,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,oBAAoB,EACjC,MAAM,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,QAavC;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,OAAe,GAC9B,GAAG,CAAC,MAAM,EAAE;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CAAC,CA4C/D;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,6CAoClB"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/reporter/util.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACtB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EACL,cAAc,EAGd,UAAU,IAAI,oBAAoB,EAElC,QAAQ,EACT,MAAM,2BAA2B,CAAC;AAoBnC,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAmB7D;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,UAMhC;AAED,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5E,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAyCD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ;;;EAwBrD;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAyDf;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GACnC,GAAG,CAIL;AAED,wBAAsB,gCAAgC,CACpD,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,iBAuFjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,GAAG,EACb,iBAAiB,EAAE,GAAG,CACpB,MAAM,EACN;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CACjD,QA+BF;AAGD,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,oBAAoB,EACjC,MAAM,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,QAavC;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,OAAe,GAC9B,GAAG,CAAC,MAAM,EAAE;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CAAC,CA4C/D;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,6CAoClB"}
@@ -126,7 +126,7 @@ async function sendTestCaseUpdateToDashboard(params) {
126
126
  return;
127
127
  }
128
128
  if (!process.env.EMPIRICALRUN_API_KEY) {
129
- console.error("No API token found. Skipping send message to dashboard.");
129
+ console.log("No API token found. Skipping send message to dashboard.");
130
130
  return;
131
131
  }
132
132
  await (0, async_retry_1.default)(async () => {
@@ -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
@@ -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;AAM1B,OAAO,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,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;AAUF,QAAA,MAAM,eAAe,GACnB,QAAQ,QAAQ,CACd,kBAAkB,GAAG,qBAAqB,EAC1C,oBAAoB,GAAG,uBAAuB,CAC/C,EACD,UAAS,eAAgC,uHAuD1C,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,6BAA6B,EAAE,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"}
@@ -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
- const videoStore = "videos-store";
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: path_1.default.join(testInfo.project.outputDir, videoStore, testInfo.testId),
57
+ dir: testInfo.outputDir,
58
+ size: { width: 1280, height: 720 },
41
59
  },
42
60
  });
43
- contexts.push(pageContext);
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
- for (const context of contexts) {
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 pathToTestVideos = path_1.default.join(testInfo.project.outputDir, "videos-store", testInfo.testId);
57
- if (fs_1.default.existsSync(pathToTestVideos)) {
58
- for (let name of fs_1.default.readdirSync(pathToTestVideos)) {
59
- await testInfo.attach("video", {
60
- path: path_1.default.join(pathToTestVideos, name),
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,36 @@
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 '@empiricalrun/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
+ ## Notes
34
+
35
+ - The default behavior is to label videos for multiple pages as: `video-0.webm`, `video-1.webm`, etc.
36
+ - If setVideoLabel is called twice for the same page, the last label will be set
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/playwright-utils",
3
- "version": "0.29.0",
3
+ "version": "0.30.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -43,9 +43,9 @@
43
43
  "mailosaur": "^8.6.1",
44
44
  "puppeteer-extra-plugin-recaptcha": "^3.6.8",
45
45
  "rimraf": "^6.0.1",
46
- "@empiricalrun/llm": "^0.24.0",
46
+ "@empiricalrun/llm": "^0.25.0",
47
47
  "@empiricalrun/r2-uploader": "^0.4.0",
48
- "@empiricalrun/test-gen": "^0.78.0"
48
+ "@empiricalrun/test-gen": "^0.78.1"
49
49
  },
50
50
  "scripts": {
51
51
  "dev": "tsc --build --watch",
@@ -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"}