@empiricalrun/test-gen 0.66.1 → 0.66.2

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/agent/browsing/run.js +4 -4
  3. package/dist/agent/browsing/utils.d.ts +1 -0
  4. package/dist/agent/browsing/utils.d.ts.map +1 -1
  5. package/dist/agent/browsing/utils.js +5 -4
  6. package/dist/agent/chat/index.d.ts +1 -0
  7. package/dist/agent/chat/index.d.ts.map +1 -1
  8. package/dist/agent/chat/index.js +1 -0
  9. package/dist/agent/chat/prompt/index.d.ts.map +1 -1
  10. package/dist/agent/chat/prompt/index.js +3 -1
  11. package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.d.ts +14 -0
  12. package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.d.ts.map +1 -0
  13. package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.js +62 -0
  14. package/dist/agent/cua/pw-codegen/pw-pause/index.d.ts.map +1 -1
  15. package/dist/agent/cua/pw-codegen/pw-pause/index.js +19 -15
  16. package/dist/agent/cua/pw-codegen/pw-pause/types.d.ts +14 -0
  17. package/dist/agent/cua/pw-codegen/pw-pause/types.d.ts.map +1 -0
  18. package/dist/agent/cua/pw-codegen/pw-pause/types.js +2 -0
  19. package/dist/artifacts/utils.d.ts +1 -1
  20. package/dist/artifacts/utils.d.ts.map +1 -1
  21. package/dist/artifacts/utils.js +5 -5
  22. package/dist/bin/index.js +7 -1
  23. package/dist/bin/utils/index.d.ts +2 -1
  24. package/dist/bin/utils/index.d.ts.map +1 -1
  25. package/dist/bin/utils/index.js +34 -5
  26. package/dist/browser-injected-scripts/annotate-elements.js +1 -4
  27. package/dist/file/client.d.ts +1 -0
  28. package/dist/file/client.d.ts.map +1 -1
  29. package/dist/file/client.js +3 -0
  30. package/dist/file/server.d.ts +2 -0
  31. package/dist/file/server.d.ts.map +1 -1
  32. package/dist/file/server.js +9 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +16 -0
  36. package/dist/recorder/display.d.ts +2 -0
  37. package/dist/recorder/display.d.ts.map +1 -0
  38. package/dist/recorder/display.js +50 -0
  39. package/dist/recorder/index.d.ts +4 -0
  40. package/dist/recorder/index.d.ts.map +1 -0
  41. package/dist/recorder/index.js +108 -0
  42. package/dist/recorder/request.d.ts +6 -0
  43. package/dist/recorder/request.d.ts.map +1 -0
  44. package/dist/recorder/request.js +55 -0
  45. package/dist/recorder/temp-files.d.ts +3 -0
  46. package/dist/recorder/temp-files.d.ts.map +1 -0
  47. package/dist/recorder/temp-files.js +39 -0
  48. package/dist/recorder/upload.d.ts +2 -0
  49. package/dist/recorder/upload.d.ts.map +1 -0
  50. package/dist/recorder/upload.js +85 -0
  51. package/dist/recorder/validation.d.ts +2 -0
  52. package/dist/recorder/validation.d.ts.map +1 -0
  53. package/dist/recorder/validation.js +24 -0
  54. package/package.json +1 -1
  55. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @empiricalrun/test-gen
2
2
 
3
+ ## 0.66.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 45dfd06: feat: show version comparison in banner
8
+ - 7c66a47: feat: first working version of --use-recorder
9
+ - 5bc45e1: feat: create requests from recorder cli
10
+ - d0569de: fix: ensure browser agent is not stuck on page.pause
11
+ - fd719f1: feat: upload recorder video and attach to request
12
+ - a65a4d2: fix: path and ui issues
13
+
3
14
  ## 0.66.1
4
15
 
5
16
  ### Patch Changes
@@ -83,10 +83,10 @@ async function runBrowsingAgent({ testCaseName, testCaseSuites, testFilePath, fi
83
83
  if (error) {
84
84
  // Clean up the file if there is any error
85
85
  try {
86
- const fileContent = fs_1.default.readFileSync(filePathToUpdate, "utf-8");
87
- const updatedContent = (0, web_1.replaceCreateTestWithNewCode)(filePathToUpdate, fileContent, "");
88
- fs_1.default.writeFileSync(filePathToUpdate, updatedContent, "utf-8");
89
- await (0, web_1.lintErrors)(filePathToUpdate);
86
+ const fileContent = fs_1.default.readFileSync(absFilePathToUpdate, "utf-8");
87
+ const updatedContent = (0, web_1.replaceCreateTestWithNewCode)(absFilePathToUpdate, fileContent, "");
88
+ fs_1.default.writeFileSync(absFilePathToUpdate, updatedContent, "utf-8");
89
+ await (0, web_1.lintErrors)(absFilePathToUpdate);
90
90
  }
91
91
  catch (e) {
92
92
  console.error(`[generateTestsUsingMasterAgent] Failed to remove extra scripts from files post test gen error:`, e);
@@ -4,6 +4,7 @@ import { Page } from "playwright";
4
4
  import { PlaywrightTestConfig } from "playwright/test";
5
5
  export declare function isRegExp(obj: any): obj is RegExp;
6
6
  export declare function prepareBrowsingAgentTask(steps: string[]): string;
7
+ export declare function addImportForMethod(testFilePath: string, methodName: string): Promise<void>;
7
8
  export declare function replaceTodoWithCreateTest(testFilePath: string, repoDir: string): Promise<void>;
8
9
  export declare function markTestAsOnly({ testCaseName, testCaseSuites, specPath, }: {
9
10
  testCaseName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAI3D,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAsBvD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,MAAM,CAKhD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,UAIvD;AAiFD,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,iBAwBhB;AAED,wBAAsB,cAAc,CAAC,EACnC,YAAY,EACZ,cAAc,EACd,QAAQ,GACT,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB,iBAoBA;AAED,wBAAsB,yBAAyB,CAAC,EAC9C,QAAQ,EACR,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyDlB;AAyBD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,iBA8HxD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,CAAC,CA2B/B;AAWD,wBAAsB,oBAAoB,CACxC,gBAAgB,EAAE,oBAAoB,GACrC,OAAO,CAAC,MAAM,EAAE,CAAC,CAQnB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,oBAAoB,EACtC,gBAAgB,GAAE,MAAM,EAAU,GACjC,OAAO,CAAC,MAAM,CAAC,CA+CjB"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAI3D,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAsBvD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,MAAM,CAKhD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,UAIvD;AAED,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,iBAgBnB;AAgED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,iBAwBhB;AAED,wBAAsB,cAAc,CAAC,EACnC,YAAY,EACZ,cAAc,EACd,QAAQ,GACT,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB,iBAoBA;AAED,wBAAsB,yBAAyB,CAAC,EAC9C,QAAQ,EACR,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyDlB;AAyBD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,iBA8HxD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,CAAC,CA2B/B;AAWD,wBAAsB,oBAAoB,CACxC,gBAAgB,EAAE,oBAAoB,GACrC,OAAO,CAAC,MAAM,EAAE,CAAC,CAQnB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,oBAAoB,EACtC,gBAAgB,GAAE,MAAM,EAAU,GACjC,OAAO,CAAC,MAAM,CAAC,CA+CjB"}
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.isRegExp = isRegExp;
7
7
  exports.prepareBrowsingAgentTask = prepareBrowsingAgentTask;
8
+ exports.addImportForMethod = addImportForMethod;
8
9
  exports.replaceTodoWithCreateTest = replaceTodoWithCreateTest;
9
10
  exports.markTestAsOnly = markTestAsOnly;
10
11
  exports.prepareFileForMasterAgent = prepareFileForMasterAgent;
@@ -33,14 +34,14 @@ function prepareBrowsingAgentTask(steps) {
33
34
  const task = `${sanitizedSteps.join("\n")}\n`;
34
35
  return task;
35
36
  }
36
- async function addImportForCreateTest(testFilePath) {
37
+ async function addImportForMethod(testFilePath, methodName) {
37
38
  // Instead of using "@empiricalrun/test-gen", we use the local dist file
38
39
  // This is to avoid assuming that the test-gen package is installed in the project
39
40
  const importSource = path_1.default.join(__dirname, "../../../dist/index.js");
40
41
  if (!fs_1.default.existsSync(importSource)) {
41
42
  throw new Error(`createTest import source not found at ${importSource}`);
42
43
  }
43
- fs_1.default.writeFileSync(testFilePath, (0, web_1.addNewImport)(fs_1.default.readFileSync(testFilePath, "utf-8"), ["createTest"], importSource));
44
+ fs_1.default.writeFileSync(testFilePath, (0, web_1.addNewImport)(fs_1.default.readFileSync(testFilePath, "utf-8"), [methodName], importSource));
44
45
  }
45
46
  async function prepareFileForUpdateScenario({ testCase, specPath, trace, }) {
46
47
  const { name, suites } = testCase;
@@ -77,7 +78,7 @@ async function prepareFileForUpdateScenario({ testCase, specPath, trace, }) {
77
78
  },
78
79
  });
79
80
  await (0, web_1.appendScopeToCreateTest)(createTestFilePath, scopeVariables);
80
- await addImportForCreateTest(createTestFilePath);
81
+ await addImportForMethod(createTestFilePath, "createTest");
81
82
  const { pomPrompt, nonSpecFilePrompt } = await (0, context_1.contextForGeneration)(createTestFilePath);
82
83
  await (0, fix_ts_errors_1.validateAndFixTypescriptErrors)({
83
84
  trace,
@@ -106,7 +107,7 @@ async function replaceTodoWithCreateTest(testFilePath, repoDir) {
106
107
  const [, pageVarName] = todoMatch;
107
108
  const pageVariable = pageVarName || "page"; // Default to "page" if not specified
108
109
  fs_1.default.writeFileSync(absoluteTestFilePath, fileContent.replace(todoRegex, (_, __, todoText) => `await createTest("${todoText.replace(/"/g, '\\"')}", ${pageVariable});`));
109
- await addImportForCreateTest(absoluteTestFilePath);
110
+ await addImportForMethod(absoluteTestFilePath, "createTest");
110
111
  }
111
112
  async function markTestAsOnly({ testCaseName, testCaseSuites, specPath, }) {
112
113
  const testFileContent = fs_1.default.readFileSync(specPath, "utf-8");
@@ -1,4 +1,5 @@
1
1
  import { SupportedChatModels } from "@empiricalrun/shared-types";
2
+ export declare function fetchEnvironmentVariables(): Promise<Record<string, string>>;
2
3
  export declare function runChatAgentForCLI({ useDiskForChatState, selectedModel, initialPromptContent, }: {
3
4
  selectedModel: SupportedChatModels;
4
5
  useDiskForChatState: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/chat/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAkEpC,wBAAsB,kBAAkB,CAAC,EACvC,mBAAmB,EACnB,aAAa,EACb,oBAAoB,GACrB,EAAE;IACD,aAAa,EAAE,mBAAmB,CAAC;IACnC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,iBA6HA;AAuBD,wBAAsB,wBAAwB,CAAC,EAC7C,aAAa,EACb,aAAa,GACd,EAAE;IACD,aAAa,EAAE,mBAAmB,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC;CACvB,iBA6DA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/chat/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAwCpC,wBAAsB,yBAAyB,IAAI,OAAO,CACxD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CACvB,CAwBA;AAED,wBAAsB,kBAAkB,CAAC,EACvC,mBAAmB,EACnB,aAAa,EACb,oBAAoB,GACrB,EAAE;IACD,aAAa,EAAE,mBAAmB,CAAC;IACnC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,iBA6HA;AAuBD,wBAAsB,wBAAwB,CAAC,EAC7C,aAAa,EACb,aAAa,GACd,EAAE;IACD,aAAa,EAAE,mBAAmB,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC;CACvB,iBA6DA"}
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchEnvironmentVariables = fetchEnvironmentVariables;
3
4
  exports.runChatAgentForCLI = runChatAgentForCLI;
4
5
  exports.runChatAgentForDashboard = runChatAgentForDashboard;
5
6
  const llm_1 = require("@empiricalrun/llm");
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/agent/chat/prompt/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI1C,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,mBA0GzD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/agent/chat/prompt/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI1C,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,mBA4GzD"}
@@ -67,7 +67,9 @@ ONLY when explicitly asked for by the user.
67
67
 
68
68
  1. You can't delete some steps from the test to make it pass. The test needs to accomplish its objective (which is to validate a particular user scenario)
69
69
  2. Do not add any conditional logic or try catch blocks in a test. A good test deterministically tests a user scenario
70
- 3. Trust Playwright's ability to auto-wait while taking actions on elements. For example, do not add checks on locator.isVisible() before clicking on it: Playwright already does this
70
+ 3. Trust Playwright's ability to auto-wait while taking actions on elements.
71
+ - Example 1: Do not add checks on locator.isVisible() before clicking on it: Playwright already waits for visibility on locator.click()
72
+ - Example 2: Do not add page.waitForLoadState after a page.goto: Playwright already waits for page "load" event in page.goto()
71
73
  4. Do not add waitForTimeout or waitForLoadState in a test. Playwright will automatically wait for the page to load.
72
74
  5. Try/catch blocks are a code smell for tests: you should not use them.
73
75
  6. Do not use then() or catch() syntax in a test. Use async/await only
@@ -0,0 +1,14 @@
1
+ import type { Page } from "playwright";
2
+ import { SourcesPayload } from "./types";
3
+ export declare class PlaywrightPauseCodegenForRecorder {
4
+ private sourcesCallback;
5
+ private port;
6
+ private page;
7
+ private server;
8
+ private codeForLastAction;
9
+ constructor(sourcesCallback: (code: SourcesPayload[]) => Promise<void>);
10
+ private saveCode;
11
+ initialize(page: Page): Promise<void>;
12
+ startPlaywrightCodegen(page: Page): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=for-recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"for-recorder.d.ts","sourceRoot":"","sources":["../../../../../src/agent/cua/pw-codegen/pw-pause/for-recorder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,qBAAa,iCAAiC;IAO1C,OAAO,CAAC,eAAe;IANzB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAmB;IAC/B,OAAO,CAAC,MAAM,CAA4C;IAC1D,OAAO,CAAC,iBAAiB,CAAqB;gBAGpC,eAAe,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC;YAKtD,QAAQ;IAWhB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBrC,sBAAsB,CAAC,IAAI,EAAE,IAAI;CAiBxC"}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PlaywrightPauseCodegenForRecorder = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const ipc_1 = require("./ipc");
9
+ class PlaywrightPauseCodegenForRecorder {
10
+ sourcesCallback;
11
+ port = 0;
12
+ page;
13
+ server;
14
+ codeForLastAction;
15
+ constructor(sourcesCallback) {
16
+ this.sourcesCallback = sourcesCallback;
17
+ this.port = ipc_1.PW_PAUSE_IPC_PORT;
18
+ }
19
+ async saveCode(code) {
20
+ const generatedCode = code.map((c) => c.actions.join("\n")).join("\n");
21
+ if (generatedCode) {
22
+ console.log(`[PlaywrightPauseCodegen] Received code from Playwright: ${generatedCode}`);
23
+ this.codeForLastAction = generatedCode;
24
+ await this.sourcesCallback(code);
25
+ }
26
+ }
27
+ async initialize(page) {
28
+ // Start server to receive generated code from patch
29
+ const app = (0, express_1.default)();
30
+ app.use(express_1.default.json());
31
+ app.post("/sources", async (req, res) => {
32
+ const { payload } = req.body;
33
+ await this.saveCode(JSON.parse(payload));
34
+ return res.send({ success: true });
35
+ });
36
+ await new Promise((resolve) => {
37
+ this.server = app.listen(this.port, () => resolve());
38
+ });
39
+ console.log(`Server started on port ${this.port}`);
40
+ // Start page.pause experience in the page
41
+ this.page = page;
42
+ await this.startPlaywrightCodegen(page);
43
+ }
44
+ async startPlaywrightCodegen(page) {
45
+ // Similar to the same name method in the main codegen
46
+ const timerPromise = new Promise((resolve) => {
47
+ setTimeout(resolve, 1000);
48
+ });
49
+ const pausePromise = page.pause();
50
+ await timerPromise;
51
+ const evaluatePromise = page.evaluate(() => {
52
+ // @ts-ignore
53
+ console.log(window["__pw_recorderSetMode"]("recording"));
54
+ const glassPane = document.querySelector("x-pw-glass");
55
+ if (glassPane) {
56
+ glassPane.remove();
57
+ }
58
+ });
59
+ await Promise.all([pausePromise, evaluatePromise]);
60
+ }
61
+ }
62
+ exports.PlaywrightPauseCodegenForRecorder = PlaywrightPauseCodegenForRecorder;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/agent/cua/pw-codegen/pw-pause/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAIjD,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAE9E,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,oBAqCvD;AAiBD,qBAAa,sBAAuB,YAAW,qBAAqB;IAClE,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAmB;IAC/B,OAAO,CAAC,MAAM,CAA4C;IAC1D,OAAO,CAAC,iBAAiB,CAAqB;;YAMhC,QAAQ;IAOhB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBrC,sBAAsB,CAAC,IAAI,EAAE,IAAI;IAsBjC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;CAU9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/agent/cua/pw-codegen/pw-pause/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAKjD,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAE9E,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,oBAuCvD;AAED,qBAAa,sBAAuB,YAAW,qBAAqB;IAClE,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,IAAI,CAAmB;IAC/B,OAAO,CAAC,MAAM,CAA4C;IAC1D,OAAO,CAAC,iBAAiB,CAAqB;;YAMhC,QAAQ;IAUhB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBrC,sBAAsB,CAAC,IAAI,EAAE,IAAI;IAyBjC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;CAU9C"}
@@ -54,6 +54,7 @@ class PlaywrightPauseCodegen {
54
54
  async saveCode(code) {
55
55
  const generatedCode = code.map((c) => c.actions.join("\n")).join("\n");
56
56
  if (generatedCode) {
57
+ console.log(`[PlaywrightPauseCodegen] Received code from Playwright: ${generatedCode}`);
57
58
  this.codeForLastAction = generatedCode;
58
59
  }
59
60
  }
@@ -78,22 +79,25 @@ class PlaywrightPauseCodegen {
78
79
  // TODO: Glass pane needs to be removed on every page load
79
80
  // We use bindings that Playwright exposes to the page
80
81
  // Ref: https://github.com/microsoft/playwright/blob/e1c8e0f6b33923c95cc4b9416aefa6977b1d3c55/packages/playwright-core/src/server/recorder.ts#L191
81
- await page.evaluate(() => {
82
- setTimeout(() => {
83
- // First, we start recording
84
- // @ts-ignore
85
- console.log(window["__pw_recorderSetMode"]("recording"));
86
- // Then, we will resume the effect of pause()
87
- // @ts-ignore
88
- console.log(window["__pw_resume"]());
89
- // Then, we remove highlights that Playwright shows on the screen
90
- const glassPane = document.querySelector("x-pw-glass");
91
- if (glassPane) {
92
- glassPane.remove();
93
- }
94
- }, 1000);
82
+ const timerPromise = new Promise((resolve) => {
83
+ setTimeout(resolve, 1000);
95
84
  });
96
- await page.pause();
85
+ const pausePromise = page.pause();
86
+ await timerPromise;
87
+ const evaluatePromise = page.evaluate(() => {
88
+ // First, we start recording
89
+ // @ts-ignore
90
+ console.log(window["__pw_recorderSetMode"]("recording"));
91
+ // Then, we will resume the effect of pause()
92
+ // @ts-ignore
93
+ console.log(window["__pw_resume"]());
94
+ // Then, we remove highlights that Playwright shows on the screen
95
+ const glassPane = document.querySelector("x-pw-glass");
96
+ if (glassPane) {
97
+ glassPane.remove();
98
+ }
99
+ });
100
+ await Promise.all([pausePromise, evaluatePromise]);
97
101
  }
98
102
  async recordAction() {
99
103
  // Record action is no-op
@@ -0,0 +1,14 @@
1
+ export type SourcesPayload = {
2
+ isRecorded: boolean;
3
+ label: "library";
4
+ group: "Node.js";
5
+ id: "javascript";
6
+ text: string;
7
+ header: string;
8
+ footer: string;
9
+ actions: string[];
10
+ language: "javascript";
11
+ revealLine: number;
12
+ highlight: string[];
13
+ };
14
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/agent/cua/pw-codegen/pw-pause/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;IACjB,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,YAAY,CAAC;IAEvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -5,7 +5,7 @@ import { JSONReport as PlaywrightJSONReport } from "@playwright/test/reporter";
5
5
  * @param report The Playwright JSON report to extract attachments from
6
6
  * @returns An array of objects containing path and contentType for each attachment
7
7
  */
8
- export declare function extractAttachmentsFromPlaywrightJSONReport(report: PlaywrightJSONReport): ArtifactInputPath[];
8
+ export declare function extractAttachmentsFromPlaywrightJSONReport(report: PlaywrightJSONReport, testNameFilter?: string): ArtifactInputPath[];
9
9
  /**
10
10
  * Scans the given repository directory for Playwright artifacts (attachments) by:
11
11
  * - Constructing the path to the Playwright report directory using the provided repoDir.
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/artifacts/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,UAAU,IAAI,oBAAoB,EAGnC,MAAM,2BAA2B,CAAC;AAInC;;;;GAIG;AACH,wBAAgB,0CAA0C,CACxD,MAAM,EAAE,oBAAoB,GAC3B,iBAAiB,EAAE,CAqErB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAmCxE"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/artifacts/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,UAAU,IAAI,oBAAoB,EAGnC,MAAM,2BAA2B,CAAC;AAInC;;;;GAIG;AACH,wBAAgB,0CAA0C,CACxD,MAAM,EAAE,oBAAoB,EAC5B,cAAc,CAAC,EAAE,MAAM,GACtB,iBAAiB,EAAE,CA2DrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAmCxE"}
@@ -12,7 +12,7 @@ const path_1 = __importDefault(require("path"));
12
12
  * @param report The Playwright JSON report to extract attachments from
13
13
  * @returns An array of objects containing path and contentType for each attachment
14
14
  */
15
- function extractAttachmentsFromPlaywrightJSONReport(report) {
15
+ function extractAttachmentsFromPlaywrightJSONReport(report, testNameFilter) {
16
16
  const attachments = [];
17
17
  if (!report || !report.suites || report.suites.length === 0) {
18
18
  return attachments;
@@ -26,6 +26,9 @@ function extractAttachmentsFromPlaywrightJSONReport(report) {
26
26
  if (!spec.tests) {
27
27
  return;
28
28
  }
29
+ if (testNameFilter && !testTitle.includes(testNameFilter)) {
30
+ return;
31
+ }
29
32
  for (const test of spec.tests) {
30
33
  if (!test.results) {
31
34
  continue;
@@ -38,7 +41,7 @@ function extractAttachmentsFromPlaywrightJSONReport(report) {
38
41
  // Only collect attachments that have a 'path' (i.e., not embedded content)
39
42
  if (attachment.path && attachment.contentType) {
40
43
  attachments.push({
41
- name: `${testTitle} ${attachment.name}`,
44
+ name: `${testTitle} - ${attachment.name}`,
42
45
  path: attachment.path,
43
46
  contentType: attachment.contentType,
44
47
  });
@@ -56,19 +59,16 @@ function extractAttachmentsFromPlaywrightJSONReport(report) {
56
59
  return;
57
60
  }
58
61
  for (const suite of suites) {
59
- // Process specs directly within the current suite
60
62
  if (suite.specs) {
61
63
  for (const spec of suite.specs) {
62
64
  processSpec(spec);
63
65
  }
64
66
  }
65
- // Recursively call for any nested suites
66
67
  if (suite.suites) {
67
68
  traverseSuites(suite.suites);
68
69
  }
69
70
  }
70
71
  }
71
- // Start the recursive traversal from the top-level suites
72
72
  traverseSuites(report.suites);
73
73
  return attachments;
74
74
  }
package/dist/bin/index.js CHANGED
@@ -16,6 +16,7 @@ const diagnosis_agent_1 = require("../agent/diagnosis-agent");
16
16
  const enrich_prompt_1 = require("../agent/enrich-prompt");
17
17
  const infer_agent_1 = require("../agent/infer-agent");
18
18
  const run_3 = require("../agent/planner/run");
19
+ const recorder_1 = require("../recorder");
19
20
  const reporter_1 = require("../reporter");
20
21
  const session_1 = require("../session");
21
22
  const test_build_1 = require("../test-build");
@@ -200,7 +201,7 @@ async function runAgentsWorkflow(testGenConfig, testGenToken) {
200
201
  }
201
202
  async function main() {
202
203
  const removeListeners = setupProcessListeners(flushEvents);
203
- (0, utils_2.printBanner)();
204
+ await (0, utils_2.printBanner)();
204
205
  const program = new commander_1.Command();
205
206
  program
206
207
  .option("--token <token>", "Test generation token")
@@ -209,6 +210,7 @@ async function main() {
209
210
  .option("--file <test-file>", "File path of the test case (inside tests dir)")
210
211
  .option("--suites <suites>", "Comma separated list of describe blocks")
211
212
  .option("--use-chat", "Use chat agent (and not the workflow)")
213
+ .option("--use-recorder", "Run the recorder flow to create a request")
212
214
  .option("--chat-session-id <chat-session-id>", "Identifier for chat session (fetched from dash.empirical.run)")
213
215
  .option("--use-disk-for-chat-state", "Save and load chat state from disk")
214
216
  .option("--chat-model <model>", "Chat model to use (claude-3-7-sonnet-20250219 or claude-3-5-sonnet-20241022 or gemini-2.5-pro-preview-06-05)")
@@ -242,6 +244,10 @@ async function main() {
242
244
  apiKey: process.env.EMPIRICALRUN_API_KEY,
243
245
  });
244
246
  }
247
+ if (completedOptions.useRecorder) {
248
+ await (0, recorder_1.runRecorder)({ name: completedOptions.name });
249
+ return;
250
+ }
245
251
  if (completedOptions.useChat) {
246
252
  await runChatAgent({
247
253
  chatSessionId: completedOptions.chatSessionId,
@@ -11,7 +11,8 @@ export interface CLIOptions {
11
11
  initialPrompt?: string;
12
12
  chatSessionId?: string;
13
13
  chatModel?: (typeof ARGS_TO_MODEL_MAP)[keyof typeof ARGS_TO_MODEL_MAP];
14
+ useRecorder?: boolean;
14
15
  }
15
16
  export declare function validateAndCompleteCliOptions(options: CLIOptions): Promise<CLIOptions>;
16
- export declare function printBanner(): void;
17
+ export declare function printBanner(): Promise<void>;
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAgBjE,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;CACxE;AAQD,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,UAAU,CAAC,CAyDrB;AAED,wBAAgB,WAAW,SAgC1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAKjE,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAgBjE,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;IAGvE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAQD,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,UAAU,CAAC,CA8DrB;AAeD,wBAAsB,WAAW,kBAgDhC"}
@@ -7,6 +7,7 @@ exports.ARGS_TO_MODEL_MAP = void 0;
7
7
  exports.validateAndCompleteCliOptions = validateAndCompleteCliOptions;
8
8
  exports.printBanner = printBanner;
9
9
  const inquirer_1 = __importDefault(require("inquirer"));
10
+ const PACKAGE_NAME = "@empiricalrun/test-gen";
10
11
  exports.ARGS_TO_MODEL_MAP = {
11
12
  "claude-3-5": "claude-3-5-sonnet-20241022",
12
13
  "claude-3-7": "claude-3-7-sonnet-20250219",
@@ -32,6 +33,9 @@ async function validateAndCompleteCliOptions(options) {
32
33
  // Chat agent can prompt the user directly, nothing is required in CLI args
33
34
  requiredFields = [];
34
35
  }
36
+ if (options.useRecorder) {
37
+ requiredFields = ["name"];
38
+ }
35
39
  const questions = [];
36
40
  if (!options.name && requiredFields.includes("name")) {
37
41
  questions.push({
@@ -76,7 +80,19 @@ async function validateAndCompleteCliOptions(options) {
76
80
  }
77
81
  return options;
78
82
  }
79
- function printBanner() {
83
+ async function getLatestVersion(packageName) {
84
+ try {
85
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
86
+ if (!response.ok)
87
+ return null;
88
+ const data = await response.json();
89
+ return data.version;
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ async function printBanner() {
80
96
  const gray = "\x1b[90m";
81
97
  const reset = "\x1b[0m";
82
98
  const asciiArtRaw = `
@@ -85,7 +101,17 @@ function printBanner() {
85
101
  /)__)
86
102
  --"-"-`;
87
103
  const version = require("../../../package.json").version;
88
- const logLine1 = `Running test-gen v${version}`;
104
+ const latestVersion = await getLatestVersion(PACKAGE_NAME);
105
+ let versionSuffix = "";
106
+ if (latestVersion) {
107
+ if (version === latestVersion) {
108
+ versionSuffix = ` ${gray}(latest)${reset}`;
109
+ }
110
+ else {
111
+ versionSuffix = ` (latest is ${latestVersion})`;
112
+ }
113
+ }
114
+ const logLine1 = `Running ${PACKAGE_NAME} v${version}${versionSuffix}`;
89
115
  const logLine2 = `from ${__dirname.split("/bin/utils")[0]}`;
90
116
  // Process ASCII art
91
117
  const asciiLines = asciiArtRaw
@@ -94,20 +120,23 @@ function printBanner() {
94
120
  const maxWidth = Math.max(...asciiLines.map((line) => line.length));
95
121
  const middleIndex = Math.floor(asciiLines.length / 2);
96
122
  const spacing = " "; // 4 spaces for padding
123
+ const leftPadding = " "; // Left padding
124
+ console.log(""); // Top padding
97
125
  // Print formatted output (art default, logs gray)
98
126
  asciiLines.forEach((line, index) => {
99
127
  const paddedLine = line.padEnd(maxWidth, " ");
100
128
  if (index === middleIndex) {
101
129
  // Print art line (default color) + first log line (gray)
102
- console.log(`${paddedLine}${spacing}${gray}${logLine1}${reset}`);
130
+ console.log(`${leftPadding}${paddedLine}${spacing}${gray}${logLine1}${reset}`);
103
131
  }
104
132
  else if (index === middleIndex + 1) {
105
133
  // Print corresponding art line (default color) + second log line (gray)
106
- console.log(`${paddedLine}${spacing}${gray}${logLine2}${reset}`);
134
+ console.log(`${leftPadding}${paddedLine}${spacing}${gray}${logLine2}${reset}`);
107
135
  }
108
136
  else {
109
137
  // Print other art lines (default color)
110
- console.log(paddedLine);
138
+ console.log(`${leftPadding}${paddedLine}`);
111
139
  }
112
140
  });
141
+ console.log(""); // Bottom padding
113
142
  }
@@ -1,5 +1,3 @@
1
- /* eslint-disable autofix/no-unused-vars */
2
-
3
1
  /**
4
2
  * Annotates all clickable elements on the page with unique hint markers.
5
3
  * Returns an object containing annotations and methods to enable/disable them.
@@ -10,6 +8,7 @@
10
8
  * @param {string} options.markerClass - CSS class to apply to hint markers.
11
9
  * @returns {Object} An object containing annotations map and enable/disable methods.
12
10
  */
11
+ // eslint-disable-next-line no-unused-vars, autofix/no-unused-vars
13
12
  function annotateElementsWithPreference({
14
13
  options = {},
15
14
  preference = {},
@@ -32,8 +31,6 @@ function annotateElementsWithPreference({
32
31
  // Check if the element is not blocked and visible for clicking
33
32
  function isElementClickNotBlocked(element, windowToAnnotate) {
34
33
  const rect = element.getBoundingClientRect();
35
- const originalScrollX = windowToAnnotate.scrollX;
36
- const originalScrollY = windowToAnnotate.scrollY;
37
34
 
38
35
  // Calculate the center point of the element
39
36
  const centerX = rect.left + rect.width / 2;
@@ -5,6 +5,7 @@ declare class FileServiceClient {
5
5
  constructor();
6
6
  static isAvailable(): boolean;
7
7
  sendAgentResult(payload: BrowserAgentIPCPayload): Promise<any>;
8
+ sendCodegenSources(payload: any): Promise<any>;
8
9
  post(path: string, body: any): Promise<any>;
9
10
  }
10
11
  export default FileServiceClient;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/file/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAElD,cAAM,iBAAiB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;;IAUzB,MAAM,CAAC,WAAW;IAIZ,eAAe,CAAC,OAAO,EAAE,sBAAsB;IAI/C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;CAgBnC;AAED,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/file/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAElD,cAAM,iBAAiB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;;IAUzB,MAAM,CAAC,WAAW;IAIZ,eAAe,CAAC,OAAO,EAAE,sBAAsB;IAI/C,kBAAkB,CAAC,OAAO,EAAE,GAAG;IAI/B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;CAgBnC;AAED,eAAe,iBAAiB,CAAC"}
@@ -16,6 +16,9 @@ class FileServiceClient {
16
16
  async sendAgentResult(payload) {
17
17
  return this.post("/agent-results", payload);
18
18
  }
19
+ async sendCodegenSources(payload) {
20
+ return this.post("/codegen-sources", payload);
21
+ }
19
22
  async post(path, body) {
20
23
  const resp = await fetch(`${this.baseUrl}${path}`, {
21
24
  method: "POST",
@@ -17,6 +17,7 @@ export declare class FileServiceServer {
17
17
  private artifactsInputs;
18
18
  private result;
19
19
  private usage;
20
+ private codegenSources;
20
21
  constructor({ port, repoDir, updateFile, onComplete, }: {
21
22
  port: number;
22
23
  repoDir: string;
@@ -28,6 +29,7 @@ export declare class FileServiceServer {
28
29
  usage: Usage | undefined;
29
30
  };
30
31
  getArtifactInputsFromServer(): ArtifactInput[];
32
+ getCodegenSources(): string | undefined;
31
33
  setFilePath(filePath: string): void;
32
34
  startFileService(): Promise<number>;
33
35
  stop(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/file/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAKlE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAQlD,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAA4C;IAC1D,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,UAAU,CAAC,CAAa;IAEhC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,KAAK,CAAoB;gBAErB,EACV,IAAI,EACJ,OAAO,EACP,UAAU,EACV,UAAU,GACX,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;KACzB;IAOD,iBAAiB;;;;IAIjB,2BAA2B;IAI3B,WAAW,CAAC,QAAQ,EAAE,MAAM;IAItB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IA4DnC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB5B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/file/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAKlE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AASlD,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAA4C;IAC1D,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,UAAU,CAAC,CAAa;IAEhC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,KAAK,CAAoB;IAEjC,OAAO,CAAC,cAAc,CAAqB;gBAE/B,EACV,IAAI,EACJ,OAAO,EACP,UAAU,EACV,UAAU,GACX,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;KACzB;IAOD,iBAAiB;;;;IAIjB,2BAA2B;IAI3B,iBAAiB;IAIjB,WAAW,CAAC,QAAQ,EAAE,MAAM;IAItB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAqEnC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB5B"}
@@ -19,6 +19,7 @@ class FileServiceServer {
19
19
  artifactsInputs = [];
20
20
  result;
21
21
  usage;
22
+ codegenSources;
22
23
  constructor({ port, repoDir, updateFile, onComplete, }) {
23
24
  this.port = port;
24
25
  this.repoDir = repoDir;
@@ -31,6 +32,9 @@ class FileServiceServer {
31
32
  getArtifactInputsFromServer() {
32
33
  return this.artifactsInputs;
33
34
  }
35
+ getCodegenSources() {
36
+ return this.codegenSources;
37
+ }
34
38
  setFilePath(filePath) {
35
39
  this.filePath = path_1.default.resolve(this.repoDir, filePath);
36
40
  }
@@ -38,6 +42,11 @@ class FileServiceServer {
38
42
  const app = (0, express_1.default)();
39
43
  app.use(express_1.default.json({ limit: "50mb" }));
40
44
  (0, ipc_1.humanLoopRoute)(app);
45
+ app.post("/codegen-sources", async (req) => {
46
+ const payload = req.body;
47
+ this.codegenSources = payload.map((c) => c.actions.join("\n")).join("\n");
48
+ console.log("[FileServiceServer] Received codegen sources", this.codegenSources);
49
+ });
41
50
  app.post("/agent-results", async (req, res) => {
42
51
  const { generatedCode, importPaths, result, usage } = req.body;
43
52
  this.result = result;
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ import { FrameLocator, Page } from "playwright";
2
2
  import { ScopeVars } from "./types";
3
3
  export { downloadBuild } from "./test-build";
4
4
  export declare function createTest(task: string, pageRef: Page | FrameLocator, scope?: ScopeVars): Promise<void>;
5
+ export declare function recordTest(pageRef: Page): Promise<void>;
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAWhD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAsB7C,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,IAAI,GAAG,YAAY,EAC5B,KAAK,CAAC,EAAE,SAAS,iBAwElB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAahD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAsB7C,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,IAAI,GAAG,YAAY,EAC5B,KAAK,CAAC,EAAE,SAAS,iBAwElB;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,IAAI,iBAY7C"}
package/dist/index.js CHANGED
@@ -5,8 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.downloadBuild = void 0;
7
7
  exports.createTest = createTest;
8
+ exports.recordTest = recordTest;
8
9
  const llm_1 = require("@empiricalrun/llm");
9
10
  const cua_1 = require("./agent/cua");
11
+ const pw_pause_1 = require("./agent/cua/pw-codegen/pw-pause");
12
+ const for_recorder_1 = require("./agent/cua/pw-codegen/pw-pause/for-recorder");
10
13
  const run_1 = require("./agent/master/run");
11
14
  const scenarios_1 = require("./bin/utils/scenarios");
12
15
  const client_1 = __importDefault(require("./file/client"));
@@ -94,3 +97,16 @@ async function createTest(task, pageRef, scope) {
94
97
  await flushEvents();
95
98
  }
96
99
  }
100
+ async function recordTest(pageRef) {
101
+ const fileServiceClient = new client_1.default();
102
+ const repoDir = process.cwd();
103
+ const canUsePwPause = await (0, pw_pause_1.canUsePauseCodegen)(repoDir);
104
+ if (!canUsePwPause) {
105
+ return;
106
+ }
107
+ console.log("[getCodegen] using PlaywrightPauseCodegen");
108
+ const codegen = new for_recorder_1.PlaywrightPauseCodegenForRecorder(async (sources) => {
109
+ await fileServiceClient.sendCodegenSources(sources);
110
+ });
111
+ await codegen.initialize(pageRef);
112
+ }
@@ -0,0 +1,2 @@
1
+ export declare function displayResultsAndConfirm(name: string, codegenResult: string): Promise<string>;
2
+ //# sourceMappingURL=display.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../../src/recorder/display.ts"],"names":[],"mappings":"AAEA,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,mBA+CtB"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.displayResultsAndConfirm = displayResultsAndConfirm;
7
+ const inquirer_1 = __importDefault(require("inquirer"));
8
+ async function displayResultsAndConfirm(name, codegenResult) {
9
+ let finalResult = codegenResult || "No code generated";
10
+ let approved = false;
11
+ while (!approved) {
12
+ // Display results in a box format
13
+ console.log("\n" + "=".repeat(80));
14
+ console.log("📝 TEST GENERATION RESULTS");
15
+ console.log("=".repeat(80));
16
+ console.log(`Test Name: ${name}`);
17
+ console.log("-".repeat(80));
18
+ console.log("Generated Code:");
19
+ console.log("-".repeat(80));
20
+ console.log(finalResult);
21
+ console.log("=".repeat(80));
22
+ // Ask user for confirmation
23
+ const { confirmed } = await inquirer_1.default.prompt([
24
+ {
25
+ type: "confirm",
26
+ name: "confirmed",
27
+ message: "Does this look good to you?",
28
+ default: true,
29
+ },
30
+ ]);
31
+ if (confirmed) {
32
+ console.log("✅ Great! The test generation was successful.");
33
+ approved = true;
34
+ }
35
+ else {
36
+ // Open multiline editor for user to modify
37
+ const { editedCode } = await inquirer_1.default.prompt([
38
+ {
39
+ type: "editor",
40
+ name: "editedCode",
41
+ message: "Edit the generated code:",
42
+ default: finalResult,
43
+ },
44
+ ]);
45
+ finalResult = editedCode;
46
+ console.log("Code updated. Let's review the changes...");
47
+ }
48
+ }
49
+ return finalResult;
50
+ }
@@ -0,0 +1,4 @@
1
+ export declare function runRecorder({ name }: {
2
+ name: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/recorder/index.ts"],"names":[],"mappings":"AAyCA,wBAAsB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,iBAiF3D"}
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runRecorder = runRecorder;
7
+ const test_run_1 = require("@empiricalrun/test-run");
8
+ const detect_port_1 = __importDefault(require("detect-port"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const utils_1 = require("../agent/browsing/utils");
12
+ const chat_1 = require("../agent/chat");
13
+ const pw_pause_1 = require("../agent/cua/pw-codegen/pw-pause");
14
+ const utils_2 = require("../artifacts/utils");
15
+ const server_1 = require("../file/server");
16
+ const display_1 = require("./display");
17
+ const request_1 = require("./request");
18
+ const temp_files_1 = require("./temp-files");
19
+ const upload_1 = require("./upload");
20
+ const validation_1 = require("./validation");
21
+ function extractVideoAttachments(repoDir) {
22
+ try {
23
+ const summaryPath = path_1.default.join(repoDir, "summary.json");
24
+ if (!fs_1.default.existsSync(summaryPath)) {
25
+ console.log("summary.json not found");
26
+ return [];
27
+ }
28
+ const summaryContent = JSON.parse(fs_1.default.readFileSync(summaryPath, "utf-8"));
29
+ const attachments = (0, utils_2.extractAttachmentsFromPlaywrightJSONReport)(summaryContent, "temp test");
30
+ const videoPaths = attachments
31
+ .filter((attachment) => attachment.contentType === "video/webm")
32
+ .map((attachment) => attachment.path);
33
+ return videoPaths;
34
+ }
35
+ catch (error) {
36
+ console.warn("Error processing summary.json:", error);
37
+ return [];
38
+ }
39
+ }
40
+ async function runRecorder({ name }) {
41
+ console.log(`Recording for test name: ${name}`);
42
+ const repoDir = process.cwd();
43
+ try {
44
+ await (0, validation_1.validate)(repoDir);
45
+ }
46
+ catch (error) {
47
+ console.error("Error running recorder:", error);
48
+ process.exit(1);
49
+ }
50
+ if (!process.env.EMPIRICALRUN_API_KEY) {
51
+ console.error("EMPIRICALRUN_API_KEY is not set. Please set it in your environment variables.");
52
+ process.exit(1);
53
+ }
54
+ try {
55
+ // Prepare playwright for codegen
56
+ console.log("[generateTestWithBrowserAgent] Preparing playwright for codegen");
57
+ await (0, pw_pause_1.preparePlaywrightForCodegen)(repoDir);
58
+ }
59
+ catch (err) {
60
+ console.warn("[generateTestWithBrowserAgent] Error preparing playwright for codegen", err);
61
+ }
62
+ const envVariables = await (0, chat_1.fetchEnvironmentVariables)();
63
+ await (0, temp_files_1.createTempTestFile)();
64
+ const absFilePath = path_1.default.join(process.cwd(), "tests", "temp-test.spec.ts");
65
+ await (0, utils_1.addImportForMethod)(absFilePath, "recordTest");
66
+ // Start a file service for IPC with the agent (which runs in a different process)
67
+ const availablePort = await (0, detect_port_1.default)(3030);
68
+ const fileServer = new server_1.FileServiceServer({
69
+ port: availablePort,
70
+ repoDir,
71
+ updateFile: false,
72
+ });
73
+ await fileServer.startFileService();
74
+ await (0, test_run_1.runSingleTest)({
75
+ testName: "temp test",
76
+ suites: [],
77
+ filePath: "tests/temp-test.spec.ts",
78
+ projects: ["chromium"],
79
+ repoDir: process.cwd(),
80
+ envOverrides: {
81
+ ...envVariables,
82
+ RUN_PLAYWRIGHT_HEADED: "true",
83
+ PW_CODEGEN_NO_INSPECTOR: "1",
84
+ IPC_FILE_SERVICE_PORT: availablePort.toString(),
85
+ },
86
+ });
87
+ const videoPaths = extractVideoAttachments(repoDir);
88
+ let attachments = [];
89
+ if (videoPaths.length === 0) {
90
+ console.warn("No video attachments found for temp test");
91
+ }
92
+ else {
93
+ const videoUrls = await (0, upload_1.uploadVideosWithSpinner)(videoPaths, name);
94
+ if (videoUrls) {
95
+ attachments = [...attachments, ...videoUrls];
96
+ }
97
+ }
98
+ await (0, temp_files_1.deleteTempTestFile)();
99
+ await (0, pw_pause_1.revertToOriginalPwCode)(repoDir);
100
+ const codegenResult = fileServer.getCodegenSources();
101
+ await fileServer.stop();
102
+ const finalCode = await (0, display_1.displayResultsAndConfirm)(name, codegenResult);
103
+ await (0, request_1.sendToDashboardAsRequest)({
104
+ testName: name,
105
+ codegenResult: finalCode,
106
+ attachments,
107
+ });
108
+ }
@@ -0,0 +1,6 @@
1
+ export declare function sendToDashboardAsRequest({ testName, codegenResult, attachments, }: {
2
+ testName: string;
3
+ codegenResult: string;
4
+ attachments: string[];
5
+ }): Promise<void>;
6
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/recorder/request.ts"],"names":[],"mappings":"AAeA,wBAAsB,wBAAwB,CAAC,EAC7C,QAAQ,EACR,aAAa,EACb,WAAW,GACZ,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,iBAKA"}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendToDashboardAsRequest = sendToDashboardAsRequest;
4
+ const DASHBOARD_DOMAIN = process.env.DASHBOARD_DOMAIN || "https://dash.empirical.run";
5
+ const title = (name) => `Add a test: ${name}`;
6
+ function description(codegenResult, attachments) {
7
+ return [
8
+ `To add this test, refer to the following code that was captured using playwright codegen. Make modifications that you need to make to convert this into a test case that sits well with other tests in this repository.`,
9
+ `Codegen result:`,
10
+ codegenResult,
11
+ `Other attachments:`,
12
+ ...attachments,
13
+ ].join("\n\n");
14
+ }
15
+ async function sendToDashboardAsRequest({ testName, codegenResult, attachments, }) {
16
+ return createRequest({
17
+ title: title(testName),
18
+ description: description(codegenResult, attachments),
19
+ });
20
+ }
21
+ async function createRequest({ title, description, }) {
22
+ if (!DASHBOARD_DOMAIN) {
23
+ console.warn("DASHBOARD_DOMAIN not set, skipping request creation");
24
+ return;
25
+ }
26
+ if (!process.env.EMPIRICALRUN_API_KEY) {
27
+ console.warn("EMPIRICALRUN_API_KEY not set, skipping request creation");
28
+ return;
29
+ }
30
+ try {
31
+ const source = "cli";
32
+ const sourceIdentifier = "random-string";
33
+ const response = await fetch(`${DASHBOARD_DOMAIN}/api/requests`, {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/json",
37
+ Authorization: `Bearer ${process.env.EMPIRICALRUN_API_KEY}`,
38
+ },
39
+ body: JSON.stringify({
40
+ source,
41
+ source_identifier: sourceIdentifier,
42
+ title,
43
+ description,
44
+ }),
45
+ });
46
+ if (!response.ok) {
47
+ throw new Error(`Failed to create request: ${response.statusText}`);
48
+ }
49
+ const data = await response.json();
50
+ console.log("Request created successfully:", data);
51
+ }
52
+ catch (error) {
53
+ console.error("Failed to create request:", error);
54
+ }
55
+ }
@@ -0,0 +1,3 @@
1
+ export declare function createTempTestFile(): Promise<void>;
2
+ export declare function deleteTempTestFile(): Promise<void>;
3
+ //# sourceMappingURL=temp-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temp-files.d.ts","sourceRoot":"","sources":["../../src/recorder/temp-files.ts"],"names":[],"mappings":"AAGA,wBAAsB,kBAAkB,kBAoBvC;AAED,wBAAsB,kBAAkB,kBAUvC"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createTempTestFile = createTempTestFile;
7
+ exports.deleteTempTestFile = deleteTempTestFile;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ async function createTempTestFile() {
11
+ const tempFilePath = path_1.default.join(process.cwd(), "tests", "temp-test.spec.ts");
12
+ // Check if the temp file already exists
13
+ if (fs_1.default.existsSync(tempFilePath)) {
14
+ console.warn("Temporary test file already exists. Skipping creation.");
15
+ return;
16
+ }
17
+ // Create a basic test file
18
+ const content = `import { test, expect } from './fixtures';
19
+
20
+ test('temp test', async ({ page }) => {
21
+ await page.goto('https://example.com');
22
+ expect(await page.title()).toBe('Example Domain');
23
+ await recordTest(page);
24
+ });
25
+ `;
26
+ fs_1.default.writeFileSync(tempFilePath, content);
27
+ console.log("Temporary test file created:", tempFilePath);
28
+ }
29
+ async function deleteTempTestFile() {
30
+ const tempFilePath = path_1.default.join(process.cwd(), "tests", "temp-test.spec.ts");
31
+ // Check if the temp file exists
32
+ if (fs_1.default.existsSync(tempFilePath)) {
33
+ fs_1.default.unlinkSync(tempFilePath);
34
+ console.log("Temporary test file deleted:", tempFilePath);
35
+ }
36
+ else {
37
+ console.warn("Temporary test file does not exist. Skipping deletion.");
38
+ }
39
+ }
@@ -0,0 +1,2 @@
1
+ export declare function uploadVideosWithSpinner(videoPaths: string[], testName: string): Promise<void | string[]>;
2
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/recorder/upload.ts"],"names":[],"mappings":"AAgFA,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAAE,EACpB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC,CAe1B"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.uploadVideosWithSpinner = uploadVideosWithSpinner;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const slug_1 = require("../utils/slug");
11
+ const ASSETS_PRODUCTION_BUCKET = "empirical-assets-production";
12
+ const BUCKET_DOMAINS = {
13
+ "empirical-assets-staging": "assets-staging.empirical.run",
14
+ "empirical-assets-production": "assets.empirical.run",
15
+ };
16
+ function buildVideoUrl(localPath, directory, bucket) {
17
+ const domain = BUCKET_DOMAINS[bucket];
18
+ const fileName = path_1.default.basename(localPath);
19
+ return `https://${domain}/${path_1.default.join(directory, fileName)}`;
20
+ }
21
+ async function uploadVideos(videoPaths, testSlug) {
22
+ if (videoPaths.length === 0) {
23
+ return null;
24
+ }
25
+ const DASHBOARD_DOMAIN = process.env.DASHBOARD_DOMAIN || "https://dash.empirical.run";
26
+ const API_KEY = process.env.EMPIRICALRUN_API_KEY;
27
+ if (!API_KEY) {
28
+ console.warn("EMPIRICALRUN_API_KEY not set, skipping video upload");
29
+ return null;
30
+ }
31
+ const uploadDestinationDir = `recorder-uploads/${testSlug}`;
32
+ const bucket = ASSETS_PRODUCTION_BUCKET;
33
+ try {
34
+ const formData = new FormData();
35
+ formData.append("destination_dir", uploadDestinationDir);
36
+ formData.append("bucket", bucket);
37
+ for (const videoPath of videoPaths) {
38
+ if (fs_1.default.existsSync(videoPath)) {
39
+ const fileBuffer = fs_1.default.readFileSync(videoPath);
40
+ const fileName = path_1.default.basename(videoPath);
41
+ const blob = new Blob([fileBuffer], { type: "video/webm" });
42
+ formData.append("files", blob, fileName);
43
+ }
44
+ }
45
+ const response = await fetch(`${DASHBOARD_DOMAIN}/api/upload`, {
46
+ method: "POST",
47
+ headers: {
48
+ Authorization: `Bearer ${API_KEY}`,
49
+ },
50
+ body: formData,
51
+ });
52
+ if (!response.ok) {
53
+ console.warn(`Failed to upload videos: ${response.status} ${response.statusText}`);
54
+ return null;
55
+ }
56
+ const result = await response.json();
57
+ if (result.error) {
58
+ console.warn("Video upload error:", result.error.message);
59
+ return null;
60
+ }
61
+ return videoPaths.map((p) => buildVideoUrl(p, uploadDestinationDir, bucket));
62
+ }
63
+ catch (error) {
64
+ console.warn("Error uploading videos:", error);
65
+ return null;
66
+ }
67
+ }
68
+ async function uploadVideosWithSpinner(videoPaths, testName) {
69
+ videoPaths.forEach((videoPath) => console.log(videoPath));
70
+ const uploadSpinner = (0, ora_1.default)("Uploading video recordings...").start();
71
+ try {
72
+ const slugifiedTestName = `${(0, slug_1.slugify)(testName)}-${Math.random().toString(36).substring(2, 8)}`;
73
+ const uploaded = await uploadVideos(videoPaths, slugifiedTestName);
74
+ if (uploaded) {
75
+ uploadSpinner.succeed("Video recordings uploaded successfully");
76
+ return uploaded;
77
+ }
78
+ else {
79
+ uploadSpinner.warn("Video upload skipped or failed");
80
+ }
81
+ }
82
+ catch (error) {
83
+ uploadSpinner.fail("Failed to upload video recordings");
84
+ }
85
+ }
@@ -0,0 +1,2 @@
1
+ export declare function validate(repoDir: string): Promise<void>;
2
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/recorder/validation.ts"],"names":[],"mappings":"AAGA,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,iBAoB7C"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validate = validate;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ async function validate(repoDir) {
10
+ // Check if package.json exists
11
+ const packageJsonPath = path_1.default.join(repoDir, "package.json");
12
+ if (!fs_1.default.existsSync(packageJsonPath)) {
13
+ throw new Error("package.json not found in the repository");
14
+ }
15
+ // Check if playwright is installed
16
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf8"));
17
+ const hasPlaywright = (packageJson.dependencies &&
18
+ packageJson.dependencies["@playwright/test"]) ||
19
+ (packageJson.devDependencies &&
20
+ packageJson.devDependencies["@playwright/test"]);
21
+ if (!hasPlaywright) {
22
+ throw new Error("Playwright is not installed. Please install @playwright/test");
23
+ }
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-gen",
3
- "version": "0.66.1",
3
+ "version": "0.66.2",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -1 +1 @@
1
- {"root":["./src/index.ts","./src/actions/assert.ts","./src/actions/click.ts","./src/actions/done.ts","./src/actions/fill.ts","./src/actions/goto.ts","./src/actions/hover.ts","./src/actions/index.ts","./src/actions/next-task.ts","./src/actions/press.ts","./src/actions/skill.ts","./src/actions/text-content.ts","./src/actions/constants/index.ts","./src/actions/utils/index.ts","./src/agent/browsing/index.ts","./src/agent/browsing/run.ts","./src/agent/browsing/utils.ts","./src/agent/chat/agent-loop.ts","./src/agent/chat/exports.ts","./src/agent/chat/index.ts","./src/agent/chat/models.ts","./src/agent/chat/state.ts","./src/agent/chat/types.ts","./src/agent/chat/utils.ts","./src/agent/chat/prompt/index.ts","./src/agent/chat/prompt/pw-utils-docs.ts","./src/agent/chat/prompt/repo.ts","./src/agent/codegen/create-test-block.ts","./src/agent/codegen/fix-ts-errors.ts","./src/agent/codegen/generate-code-apply-changes.ts","./src/agent/codegen/lexical-scoped-vars.ts","./src/agent/codegen/repo-edit.ts","./src/agent/codegen/run.ts","./src/agent/codegen/skills-retriever.ts","./src/agent/codegen/test-update-feedback.ts","./src/agent/codegen/types.ts","./src/agent/codegen/update-flow.ts","./src/agent/codegen/use-skill.ts","./src/agent/codegen/utils.ts","./src/agent/cua/computer.ts","./src/agent/cua/index.ts","./src/agent/cua/model.ts","./src/agent/cua/pw-codegen/element-from-point.ts","./src/agent/cua/pw-codegen/types.ts","./src/agent/cua/pw-codegen/pw-pause/index.ts","./src/agent/cua/pw-codegen/pw-pause/ipc.ts","./src/agent/cua/pw-codegen/pw-pause/patch.ts","./src/agent/diagnosis-agent/index.ts","./src/agent/diagnosis-agent/strict-mode-violation.ts","./src/agent/enrich-prompt/index.ts","./src/agent/enrich-prompt/utils.ts","./src/agent/infer-agent/index.ts","./src/agent/master/action-tool-calls.ts","./src/agent/master/element-annotation.ts","./src/agent/master/execute-browser-action.ts","./src/agent/master/execute-skill-action.ts","./src/agent/master/next-action.ts","./src/agent/master/planner.ts","./src/agent/master/run.ts","./src/agent/master/scroller.ts","./src/agent/master/with-hints.ts","./src/agent/master/browser-tests/cua.spec.ts","./src/agent/master/browser-tests/fixtures.ts","./src/agent/master/browser-tests/index.spec.ts","./src/agent/master/browser-tests/skills.spec.ts","./src/agent/master/icon-descriptor/index.ts","./src/agent/master/icon-descriptor/normalize-svg.ts","./src/agent/planner/run-time-planner.ts","./src/agent/planner/run.ts","./src/artifacts/index.ts","./src/artifacts/utils.ts","./src/bin/index.ts","./src/bin/logger/index.ts","./src/bin/utils/context.ts","./src/bin/utils/index.ts","./src/bin/utils/fs/index.ts","./src/bin/utils/platform/web/index.ts","./src/bin/utils/platform/web/test-files/ts-path-import-validate.ts","./src/bin/utils/scenarios/index.ts","./src/browser-injected-scripts/annotate-elements.spec.ts","./src/constants/index.ts","./src/errors/index.ts","./src/evals/add-scenario-agent.evals.ts","./src/evals/append-create-test-agent.evals.ts","./src/evals/fetch-pom-skills-agent.evals.ts","./src/evals/infer-master-or-code-agent.evals.ts","./src/evals/master-agent.evals.ts","./src/evals/type.ts","./src/evals/update-scenario-agent.evals.ts","./src/file/client.ts","./src/file/server.ts","./src/human-in-the-loop/cli.ts","./src/human-in-the-loop/index.ts","./src/human-in-the-loop/ipc.ts","./src/page/index.ts","./src/prompts/lib/ts-transformer.ts","./src/reporter/index.ts","./src/reporter/lib.ts","./src/session/index.ts","./src/test-build/index.ts","./src/tool-call-service/index.ts","./src/tool-call-service/utils.ts","./src/tools/commit-and-create-pr.ts","./src/tools/diagnosis-fetcher.ts","./src/tools/download-build.ts","./src/tools/list-environments.ts","./src/tools/str_replace_editor.ts","./src/tools/test-gen-browser.ts","./src/tools/test-run.ts","./src/tools/grep/index.ts","./src/tools/grep/ripgrep/index.ts","./src/tools/grep/ripgrep/types.ts","./src/tools/test-run-fetcher/index.ts","./src/tools/test-run-fetcher/types.ts","./src/tools/upgrade-packages/index.ts","./src/tools/upgrade-packages/utils.ts","./src/tools/utils/index.ts","./src/types/handlebars.d.ts","./src/types/index.ts","./src/uploader/index.ts","./src/uploader/utils.ts","./src/utils/checkpoint.ts","./src/utils/env.ts","./src/utils/exec.ts","./src/utils/file-tree.ts","./src/utils/file.ts","./src/utils/git.ts","./src/utils/html.ts","./src/utils/index.ts","./src/utils/json.ts","./src/utils/repo-tree.ts","./src/utils/slug.ts","./src/utils/string.ts","./src/utils/stripAnsi.ts"],"version":"5.8.3"}
1
+ {"root":["./src/index.ts","./src/actions/assert.ts","./src/actions/click.ts","./src/actions/done.ts","./src/actions/fill.ts","./src/actions/goto.ts","./src/actions/hover.ts","./src/actions/index.ts","./src/actions/next-task.ts","./src/actions/press.ts","./src/actions/skill.ts","./src/actions/text-content.ts","./src/actions/constants/index.ts","./src/actions/utils/index.ts","./src/agent/browsing/index.ts","./src/agent/browsing/run.ts","./src/agent/browsing/utils.ts","./src/agent/chat/agent-loop.ts","./src/agent/chat/exports.ts","./src/agent/chat/index.ts","./src/agent/chat/models.ts","./src/agent/chat/state.ts","./src/agent/chat/types.ts","./src/agent/chat/utils.ts","./src/agent/chat/prompt/index.ts","./src/agent/chat/prompt/pw-utils-docs.ts","./src/agent/chat/prompt/repo.ts","./src/agent/codegen/create-test-block.ts","./src/agent/codegen/fix-ts-errors.ts","./src/agent/codegen/generate-code-apply-changes.ts","./src/agent/codegen/lexical-scoped-vars.ts","./src/agent/codegen/repo-edit.ts","./src/agent/codegen/run.ts","./src/agent/codegen/skills-retriever.ts","./src/agent/codegen/test-update-feedback.ts","./src/agent/codegen/types.ts","./src/agent/codegen/update-flow.ts","./src/agent/codegen/use-skill.ts","./src/agent/codegen/utils.ts","./src/agent/cua/computer.ts","./src/agent/cua/index.ts","./src/agent/cua/model.ts","./src/agent/cua/pw-codegen/element-from-point.ts","./src/agent/cua/pw-codegen/types.ts","./src/agent/cua/pw-codegen/pw-pause/for-recorder.ts","./src/agent/cua/pw-codegen/pw-pause/index.ts","./src/agent/cua/pw-codegen/pw-pause/ipc.ts","./src/agent/cua/pw-codegen/pw-pause/patch.ts","./src/agent/cua/pw-codegen/pw-pause/types.ts","./src/agent/diagnosis-agent/index.ts","./src/agent/diagnosis-agent/strict-mode-violation.ts","./src/agent/enrich-prompt/index.ts","./src/agent/enrich-prompt/utils.ts","./src/agent/infer-agent/index.ts","./src/agent/master/action-tool-calls.ts","./src/agent/master/element-annotation.ts","./src/agent/master/execute-browser-action.ts","./src/agent/master/execute-skill-action.ts","./src/agent/master/next-action.ts","./src/agent/master/planner.ts","./src/agent/master/run.ts","./src/agent/master/scroller.ts","./src/agent/master/with-hints.ts","./src/agent/master/browser-tests/cua.spec.ts","./src/agent/master/browser-tests/fixtures.ts","./src/agent/master/browser-tests/index.spec.ts","./src/agent/master/browser-tests/skills.spec.ts","./src/agent/master/icon-descriptor/index.ts","./src/agent/master/icon-descriptor/normalize-svg.ts","./src/agent/planner/run-time-planner.ts","./src/agent/planner/run.ts","./src/artifacts/index.ts","./src/artifacts/utils.ts","./src/bin/index.ts","./src/bin/logger/index.ts","./src/bin/utils/context.ts","./src/bin/utils/index.ts","./src/bin/utils/fs/index.ts","./src/bin/utils/platform/web/index.ts","./src/bin/utils/platform/web/test-files/ts-path-import-validate.ts","./src/bin/utils/scenarios/index.ts","./src/browser-injected-scripts/annotate-elements.spec.ts","./src/constants/index.ts","./src/errors/index.ts","./src/evals/add-scenario-agent.evals.ts","./src/evals/append-create-test-agent.evals.ts","./src/evals/fetch-pom-skills-agent.evals.ts","./src/evals/infer-master-or-code-agent.evals.ts","./src/evals/master-agent.evals.ts","./src/evals/type.ts","./src/evals/update-scenario-agent.evals.ts","./src/file/client.ts","./src/file/server.ts","./src/human-in-the-loop/cli.ts","./src/human-in-the-loop/index.ts","./src/human-in-the-loop/ipc.ts","./src/page/index.ts","./src/prompts/lib/ts-transformer.ts","./src/recorder/display.ts","./src/recorder/index.ts","./src/recorder/request.ts","./src/recorder/temp-files.ts","./src/recorder/upload.ts","./src/recorder/validation.ts","./src/reporter/index.ts","./src/reporter/lib.ts","./src/session/index.ts","./src/test-build/index.ts","./src/tool-call-service/index.ts","./src/tool-call-service/utils.ts","./src/tools/commit-and-create-pr.ts","./src/tools/diagnosis-fetcher.ts","./src/tools/download-build.ts","./src/tools/list-environments.ts","./src/tools/str_replace_editor.ts","./src/tools/test-gen-browser.ts","./src/tools/test-run.ts","./src/tools/grep/index.ts","./src/tools/grep/ripgrep/index.ts","./src/tools/grep/ripgrep/types.ts","./src/tools/test-run-fetcher/index.ts","./src/tools/test-run-fetcher/types.ts","./src/tools/upgrade-packages/index.ts","./src/tools/upgrade-packages/utils.ts","./src/tools/utils/index.ts","./src/types/handlebars.d.ts","./src/types/index.ts","./src/uploader/index.ts","./src/uploader/utils.ts","./src/utils/checkpoint.ts","./src/utils/env.ts","./src/utils/exec.ts","./src/utils/file-tree.ts","./src/utils/file.ts","./src/utils/git.ts","./src/utils/html.ts","./src/utils/index.ts","./src/utils/json.ts","./src/utils/repo-tree.ts","./src/utils/slug.ts","./src/utils/string.ts","./src/utils/stripAnsi.ts"],"version":"5.8.3"}