@empiricalrun/test-gen 0.66.0 → 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 (69) hide show
  1. package/CHANGELOG.md +26 -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 +2 -0
  9. package/dist/agent/chat/{prompt.d.ts → prompt/index.d.ts} +2 -2
  10. package/dist/agent/chat/prompt/index.d.ts.map +1 -0
  11. package/dist/agent/chat/{prompt.js → prompt/index.js} +17 -64
  12. package/dist/agent/chat/prompt/pw-utils-docs.d.ts +2 -0
  13. package/dist/agent/chat/prompt/pw-utils-docs.d.ts.map +1 -0
  14. package/dist/agent/chat/prompt/pw-utils-docs.js +62 -0
  15. package/dist/agent/chat/{repo.d.ts → prompt/repo.d.ts} +1 -1
  16. package/dist/agent/chat/prompt/repo.d.ts.map +1 -0
  17. package/dist/agent/chat/{repo.js → prompt/repo.js} +1 -1
  18. package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.d.ts +14 -0
  19. package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.d.ts.map +1 -0
  20. package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.js +62 -0
  21. package/dist/agent/cua/pw-codegen/pw-pause/index.d.ts.map +1 -1
  22. package/dist/agent/cua/pw-codegen/pw-pause/index.js +19 -15
  23. package/dist/agent/cua/pw-codegen/pw-pause/patch.d.ts +7 -4
  24. package/dist/agent/cua/pw-codegen/pw-pause/patch.d.ts.map +1 -1
  25. package/dist/agent/cua/pw-codegen/pw-pause/patch.js +51 -24
  26. package/dist/agent/cua/pw-codegen/pw-pause/types.d.ts +14 -0
  27. package/dist/agent/cua/pw-codegen/pw-pause/types.d.ts.map +1 -0
  28. package/dist/agent/cua/pw-codegen/pw-pause/types.js +2 -0
  29. package/dist/artifacts/utils.d.ts +1 -1
  30. package/dist/artifacts/utils.d.ts.map +1 -1
  31. package/dist/artifacts/utils.js +19 -8
  32. package/dist/bin/index.js +7 -1
  33. package/dist/bin/utils/index.d.ts +2 -1
  34. package/dist/bin/utils/index.d.ts.map +1 -1
  35. package/dist/bin/utils/index.js +34 -5
  36. package/dist/browser-injected-scripts/annotate-elements.js +1 -4
  37. package/dist/file/client.d.ts +1 -0
  38. package/dist/file/client.d.ts.map +1 -1
  39. package/dist/file/client.js +3 -0
  40. package/dist/file/server.d.ts +2 -0
  41. package/dist/file/server.d.ts.map +1 -1
  42. package/dist/file/server.js +9 -0
  43. package/dist/index.d.ts +1 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +16 -0
  46. package/dist/recorder/display.d.ts +2 -0
  47. package/dist/recorder/display.d.ts.map +1 -0
  48. package/dist/recorder/display.js +50 -0
  49. package/dist/recorder/index.d.ts +4 -0
  50. package/dist/recorder/index.d.ts.map +1 -0
  51. package/dist/recorder/index.js +108 -0
  52. package/dist/recorder/request.d.ts +6 -0
  53. package/dist/recorder/request.d.ts.map +1 -0
  54. package/dist/recorder/request.js +55 -0
  55. package/dist/recorder/temp-files.d.ts +3 -0
  56. package/dist/recorder/temp-files.d.ts.map +1 -0
  57. package/dist/recorder/temp-files.js +39 -0
  58. package/dist/recorder/upload.d.ts +2 -0
  59. package/dist/recorder/upload.d.ts.map +1 -0
  60. package/dist/recorder/upload.js +85 -0
  61. package/dist/recorder/validation.d.ts +2 -0
  62. package/dist/recorder/validation.d.ts.map +1 -0
  63. package/dist/recorder/validation.js +24 -0
  64. package/dist/uploader/index.d.ts.map +1 -1
  65. package/dist/uploader/index.js +1 -0
  66. package/package.json +6 -6
  67. package/tsconfig.tsbuildinfo +1 -1
  68. package/dist/agent/chat/prompt.d.ts.map +0 -1
  69. package/dist/agent/chat/repo.d.ts.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
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
+
14
+ ## 0.66.1
15
+
16
+ ### Patch Changes
17
+
18
+ - 1a6bb19: chore: upgrade playwright in devDependencies
19
+ - d3639c0: fix: update browsing agent artifact path for 1.53
20
+ - 881e856: fix: update system prompt to get PRs as output
21
+ - cfb157a: fix: follow-ups for playwright reporter upgrade
22
+ - 4062787: feat: update page.pause codegen to work with playwright 1.53
23
+ - Updated dependencies [cfb157a]
24
+ - Updated dependencies [a533ee5]
25
+ - Updated dependencies [4062787]
26
+ - @empiricalrun/test-run@0.10.3
27
+ - @empiricalrun/llm@0.18.2
28
+
3
29
  ## 0.66.0
4
30
 
5
31
  ### Minor 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,iBA4HA;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");
@@ -51,6 +52,7 @@ async function runChatAgentForCLI({ useDiskForChatState, selectedModel, initialP
51
52
  await (0, git_1.checkoutBranch)(branchName, process.cwd());
52
53
  let messagesLoadedFromDisk = chatState?.messages || [];
53
54
  let chatModel = (0, chat_1.createChatModel)(messagesLoadedFromDisk, selectedModel);
55
+ chatModel.validateEnvVarsForAuth();
54
56
  if (initialPromptContent && chatModel.messages.length === 0) {
55
57
  chatModel.pushUserMessage(initialPromptContent);
56
58
  chatModel.askUserForInput = false;
@@ -1,3 +1,3 @@
1
- import { FileInfo } from "../../types";
1
+ import { FileInfo } from "../../../types";
2
2
  export declare function buildSystemPrompt(fileInfo: FileInfo): Promise<string>;
3
- //# sourceMappingURL=prompt.d.ts.map
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,mBA4GzD"}
@@ -1,70 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildSystemPrompt = buildSystemPrompt;
4
+ const pw_utils_docs_1 = require("./pw-utils-docs");
4
5
  const repo_1 = require("./repo");
5
- const emailRecipe = `
6
- # Email automation
7
-
8
- ## Example usage
9
-
10
- ### Dynamic email
11
-
12
- This dynamically generates a random email address that can
13
- be used for the test (e.g. invite a new user).
14
-
15
- \`\`\`ts
16
- import { EmailClient } from "@empiricalrun/playwright-utils";
17
- import { expect } from "@playwright/test";
18
-
19
- const client = new EmailClient();
20
- const address = client.getAddress();
21
-
22
- // Input the \`address\` in the application
23
- // that sends the email.
24
-
25
- // Get email received on the \`address\`
26
- const email = await client.waitForEmail();
27
- expect(
28
- email.links.find((l) => l.text === "Join your team")
29
- ).toBeTruthy();
30
- \`\`\`
31
-
32
- ### Static email
33
-
34
- This uses a known (static) email that can be used to login
35
- into an application.
36
-
37
- This needs an email id (e.g. \`test-login-user\`). The email id
38
- is appended with the domain (managed internally) to get the full
39
- email address.
40
-
41
- \`\`\`ts
42
- import { EmailClient } from "@empiricalrun/playwright-utils";
43
-
44
- const emailId = \`test-login-user\`;
45
-
46
- const client = new EmailClient({ emailId });
47
- const address = client.getAddress(); // Returns full address with domain
48
-
49
- // Get email received on the \`address\`
50
- const email = await client.waitForEmail();
51
-
52
- // Get login OTP
53
- const loginCode = email.codes[0];
54
- \`\`\`
55
- `;
56
6
  async function buildSystemPrompt(fileInfo) {
57
- const repoContext = await (0, repo_1.getRepoInfoPrompt)(fileInfo);
58
- return `
59
- You are a helpful assistant that can answer questions and help with tasks related to writing
60
- and maintaining Playwright tests.
7
+ const preamble = `
8
+ You are a helpful assistant that can answer questions and help with tasks related to writing and maintaining Playwright tests.
9
+
10
+ You are working on a test code repository that contains Playwright tests and other related files. If you have modified
11
+ any files, your end output should be a pull request that will be reviewed and merged by a human.
61
12
 
62
- # Supported capabilities
13
+ # Your capabilities
63
14
 
64
15
  - Adding new Playwright tests or helper methods
65
16
  - Going through test reports and identifying app issues versus test issues
66
- - Modifying existing tests
67
- - Modifying repo configuration (e.g. in playwright.config.ts and other)
17
+ - Modifying existing tests to adapt to changes in the application
18
+ - Modifying repo configuration (e.g. in playwright.config.ts) and dependencies (e.g. in package.json)
68
19
 
69
20
  # Going through test reports
70
21
 
@@ -116,7 +67,9 @@ ONLY when explicitly asked for by the user.
116
67
 
117
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)
118
69
  2. Do not add any conditional logic or try catch blocks in a test. A good test deterministically tests a user scenario
119
- 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()
120
73
  4. Do not add waitForTimeout or waitForLoadState in a test. Playwright will automatically wait for the page to load.
121
74
  5. Try/catch blocks are a code smell for tests: you should not use them.
122
75
  6. Do not use then() or catch() syntax in a test. Use async/await only
@@ -147,12 +100,12 @@ if (await saveButton.isVisible()) {
147
100
  }
148
101
  \`\`\`
149
102
 
150
- # Recipes
151
- You can refer to the following recipes to learn how to write tests for different scenarios.
103
+ `;
104
+ const repoContext = await (0, repo_1.getRepoInfoPrompt)(fileInfo);
105
+ return `${preamble}
152
106
 
153
- <email-automation>
154
- ${emailRecipe}
155
- </email-automation>
107
+ # Recipes
108
+ ${pw_utils_docs_1.playwrightUtilsDocs}
156
109
 
157
110
  # Repo context
158
111
  ${repoContext}
@@ -0,0 +1,2 @@
1
+ export declare const playwrightUtilsDocs = "\nYou can refer to the following recipes to learn how to write tests for different scenarios.\n\n<email-automation>\n\n# Email automation\n\n## Example usage\n\n### Dynamic email\n\nThis dynamically generates a random email address that can \nbe used for the test (e.g. invite a new user).\n\n```ts\nimport { EmailClient } from \"@empiricalrun/playwright-utils\";\nimport { expect } from \"@playwright/test\";\n\nconst client = new EmailClient();\nconst address = client.getAddress();\n\n// Input the `address` in the application\n// that sends the email.\n\n// Get email received on the `address`\nconst email = await client.waitForEmail();\nexpect(\n email.links.find((l) => l.text === \"Join your team\")\n).toBeTruthy();\n```\n\n### Static email\n\nThis uses a known (static) email that can be used to login\ninto an application.\n\nThis needs an email id (e.g. `test-login-user`). The email id\nis appended with the domain (managed internally) to get the full\nemail address.\n\n```ts\nimport { EmailClient } from \"@empiricalrun/playwright-utils\";\n\nconst emailId = `test-login-user`;\n\nconst client = new EmailClient({ emailId });\nconst address = client.getAddress(); // Returns full address with domain\n\n// Get email received on the `address`\nconst email = await client.waitForEmail();\n\n// Get login OTP\nconst loginCode = email.codes[0];\n```\n\n</email-automation>\n\n";
2
+ //# sourceMappingURL=pw-utils-docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pw-utils-docs.d.ts","sourceRoot":"","sources":["../../../../src/agent/chat/prompt/pw-utils-docs.ts"],"names":[],"mappings":"AAoDA,eAAO,MAAM,mBAAmB,i3CAO/B,CAAC"}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.playwrightUtilsDocs = void 0;
4
+ const emailRecipe = `
5
+ # Email automation
6
+
7
+ ## Example usage
8
+
9
+ ### Dynamic email
10
+
11
+ This dynamically generates a random email address that can
12
+ be used for the test (e.g. invite a new user).
13
+
14
+ \`\`\`ts
15
+ import { EmailClient } from "@empiricalrun/playwright-utils";
16
+ import { expect } from "@playwright/test";
17
+
18
+ const client = new EmailClient();
19
+ const address = client.getAddress();
20
+
21
+ // Input the \`address\` in the application
22
+ // that sends the email.
23
+
24
+ // Get email received on the \`address\`
25
+ const email = await client.waitForEmail();
26
+ expect(
27
+ email.links.find((l) => l.text === "Join your team")
28
+ ).toBeTruthy();
29
+ \`\`\`
30
+
31
+ ### Static email
32
+
33
+ This uses a known (static) email that can be used to login
34
+ into an application.
35
+
36
+ This needs an email id (e.g. \`test-login-user\`). The email id
37
+ is appended with the domain (managed internally) to get the full
38
+ email address.
39
+
40
+ \`\`\`ts
41
+ import { EmailClient } from "@empiricalrun/playwright-utils";
42
+
43
+ const emailId = \`test-login-user\`;
44
+
45
+ const client = new EmailClient({ emailId });
46
+ const address = client.getAddress(); // Returns full address with domain
47
+
48
+ // Get email received on the \`address\`
49
+ const email = await client.waitForEmail();
50
+
51
+ // Get login OTP
52
+ const loginCode = email.codes[0];
53
+ \`\`\`
54
+ `;
55
+ exports.playwrightUtilsDocs = `
56
+ You can refer to the following recipes to learn how to write tests for different scenarios.
57
+
58
+ <email-automation>
59
+ ${emailRecipe}
60
+ </email-automation>
61
+
62
+ `;
@@ -1,3 +1,3 @@
1
- import { FileInfo } from "../../types";
1
+ import { FileInfo } from "../../../types";
2
2
  export declare function getRepoInfoPrompt(directory: FileInfo): Promise<string>;
3
3
  //# sourceMappingURL=repo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../../src/agent/chat/prompt/repo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAwC1C,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,QAAQ,mBAyC1D"}
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getRepoInfoPrompt = getRepoInfoPrompt;
7
7
  const path_1 = __importDefault(require("path"));
8
- const repo_tree_1 = require("../../utils/repo-tree");
8
+ const repo_tree_1 = require("../../../utils/repo-tree");
9
9
  async function getAllMarkdownFiles(directory) {
10
10
  if (!directory.isDirectory) {
11
11
  return [];
@@ -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
@@ -1,9 +1,12 @@
1
1
  export declare function preparePlaywrightForCodegen(repoDir: string): Promise<void>;
2
- export declare function getPathToRecorderApp(repoDir: string): Promise<string | undefined>;
2
+ export declare function getPathToRecorderApp(pwCorePath: string): Promise<string>;
3
3
  export declare function runNpmList(repoDir: string): Promise<string | undefined>;
4
- export declare function getPlaywrightCoreFromNpmList(output: string): any;
5
- export declare function hasPatchedRecorderApp(repoDir: string): Promise<boolean>;
4
+ export declare function getPlaywrightCoreFromNpmList(output: string): {
5
+ path: string;
6
+ version: "1.47" | "1.53";
7
+ };
8
+ export declare function hasPatchedRecorderApp(repoDir: string): Promise<boolean | undefined>;
6
9
  export declare function revertToOriginalPwCode(repoDir: string): Promise<void>;
7
10
  export declare function createFileBackup(pathToFile: string): Promise<void>;
8
- export declare function patchPwCode(pathToRecorderApp: string, port: number): Promise<void>;
11
+ export declare function patchPwCode(pwVersion: "1.47" | "1.53", pathToRecorderApp: string, port: number): Promise<void>;
9
12
  //# sourceMappingURL=patch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../../../../../src/agent/cua/pw-codegen/pw-pause/patch.ts"],"names":[],"mappings":"AAmBA,wBAAsB,2BAA2B,CAAC,OAAO,EAAE,MAAM,iBAgBhE;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,+BAczD;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,+BAiB/C;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,OAuB1D;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,oBAY1D;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,iBAoB3D;AAED,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,iBAGxD;AAED,wBAAsB,WAAW,CAAC,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,iBAkBxE"}
1
+ {"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../../../../../src/agent/cua/pw-codegen/pw-pause/patch.ts"],"names":[],"mappings":"AAmBA,wBAAsB,2BAA2B,CAAC,OAAO,EAAE,MAAM,iBAehE;AAED,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,mBAgB5D;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,+BAiB/C;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B,CA8BA;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,gCAkB1D;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,iBAoB3D;AAED,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,iBAGxD;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,iBAAiB,EAAE,MAAM,EACzB,IAAI,EAAE,MAAM,iBA2Bb"}
@@ -28,29 +28,30 @@ const ipc_1 = require("./ipc");
28
28
  const CUSTOM_PATCH_COMMENT = `// Custom patch code set by @empiricalrun/test-gen`;
29
29
  async function preparePlaywrightForCodegen(repoDir) {
30
30
  try {
31
- const pathToRecorderApp = await getPathToRecorderApp(repoDir);
32
- if (!pathToRecorderApp) {
33
- throw new Error("Cannot find path to recorder app");
34
- }
35
- if (!fs_1.default.existsSync(pathToRecorderApp)) {
36
- const errMsg = `Cannot patch Playwright: ${pathToRecorderApp} does not exist`;
37
- throw new Error(errMsg);
31
+ const npmListOutput = await runNpmList(repoDir);
32
+ if (!npmListOutput) {
33
+ return;
38
34
  }
35
+ const pwCore = getPlaywrightCoreFromNpmList(npmListOutput);
36
+ const { path: pwCorePath, version: pwVersion } = pwCore;
37
+ const pathToRecorderApp = await getPathToRecorderApp(pwCorePath);
39
38
  await createFileBackup(pathToRecorderApp);
40
- await patchPwCode(pathToRecorderApp, ipc_1.PW_PAUSE_IPC_PORT);
39
+ await patchPwCode(pwVersion, pathToRecorderApp, ipc_1.PW_PAUSE_IPC_PORT);
41
40
  }
42
41
  catch (error) {
43
42
  console.error("Error patching Playwright", error);
44
43
  throw error;
45
44
  }
46
45
  }
47
- async function getPathToRecorderApp(repoDir) {
48
- const npmListOutput = await runNpmList(repoDir);
49
- if (!npmListOutput) {
50
- return;
46
+ async function getPathToRecorderApp(pwCorePath) {
47
+ // Both supported versions have the same path structure
48
+ const pathToRecorderApp = path_1.default.join(pwCorePath, "lib", "server", "recorder", "recorderApp.js");
49
+ if (!pathToRecorderApp) {
50
+ throw new Error("Cannot find path to recorder app");
51
+ }
52
+ if (!fs_1.default.existsSync(pathToRecorderApp)) {
53
+ throw new Error(`${pathToRecorderApp} does not exist`);
51
54
  }
52
- const playwrightCorePath = getPlaywrightCoreFromNpmList(npmListOutput);
53
- const pathToRecorderApp = path_1.default.join(playwrightCorePath, "lib", "server", "recorder", "recorderApp.js");
54
55
  return pathToRecorderApp;
55
56
  }
56
57
  async function runNpmList(repoDir) {
@@ -78,20 +79,37 @@ function getPlaywrightCoreFromNpmList(output) {
78
79
  if (!playwrightTest) {
79
80
  throw new Error("Could not find @playwright/test in npm dependencies");
80
81
  }
81
- const playwrightTestVersion = playwrightTest.version;
82
- if (playwrightTestVersion !== "1.47.1") {
83
- throw new Error("Unsupported playwright-test version: ${playwrightTestVersion}");
84
- }
85
82
  const playwrightDeps = playwrightTest.dependencies["playwright"];
86
83
  const playwrightCoreDep = playwrightDeps.dependencies["playwright-core"];
87
84
  if (!playwrightCoreDep) {
88
85
  throw new Error("Could not find playwright-core in @playwright/test dependencies");
89
86
  }
90
- return playwrightCoreDep.path;
87
+ function extractMinorVersion(version) {
88
+ const minorVersion = version.split(".").slice(0, 2).join(".");
89
+ if (minorVersion === "1.47") {
90
+ return "1.47";
91
+ }
92
+ else if (minorVersion === "1.53") {
93
+ return "1.53";
94
+ }
95
+ else {
96
+ throw new Error(`Unsupported Playwright version: ${minorVersion}`);
97
+ }
98
+ }
99
+ return {
100
+ path: playwrightCoreDep.path,
101
+ version: extractMinorVersion(playwrightCoreDep.version),
102
+ };
91
103
  }
92
104
  async function hasPatchedRecorderApp(repoDir) {
93
105
  try {
94
- const pathToRecorderApp = await getPathToRecorderApp(repoDir);
106
+ const npmListOutput = await runNpmList(repoDir);
107
+ if (!npmListOutput) {
108
+ return;
109
+ }
110
+ const pwCore = getPlaywrightCoreFromNpmList(npmListOutput);
111
+ const { path: pwCorePath } = pwCore;
112
+ const pathToRecorderApp = await getPathToRecorderApp(pwCorePath);
95
113
  if (!pathToRecorderApp) {
96
114
  return false;
97
115
  }
@@ -109,8 +127,8 @@ async function revertToOriginalPwCode(repoDir) {
109
127
  if (!npmListOutput) {
110
128
  return;
111
129
  }
112
- const playwrightCorePath = getPlaywrightCoreFromNpmList(npmListOutput);
113
- const pathToRecorderApp = path_1.default.join(playwrightCorePath, "lib", "server", "recorder", "recorderApp.js");
130
+ const { path: pwCorePath } = getPlaywrightCoreFromNpmList(npmListOutput);
131
+ const pathToRecorderApp = path_1.default.join(pwCorePath, "lib", "server", "recorder", "recorderApp.js");
114
132
  const backupPath = `${pathToRecorderApp}.backup`;
115
133
  fs_1.default.copyFileSync(backupPath, pathToRecorderApp);
116
134
  fs_1.default.unlinkSync(backupPath);
@@ -123,9 +141,18 @@ async function createFileBackup(pathToFile) {
123
141
  const backupPath = `${pathToFile}.backup`;
124
142
  fs_1.default.copyFileSync(pathToFile, backupPath);
125
143
  }
126
- async function patchPwCode(pathToRecorderApp, port) {
144
+ async function patchPwCode(pwVersion, pathToRecorderApp, port) {
127
145
  const currentFileContents = fs_1.default.readFileSync(pathToRecorderApp, "utf-8");
128
- const original = `async setSources(sources) {}`;
146
+ let original;
147
+ if (pwVersion === "1.47") {
148
+ original = `async setSources(sources) {}`;
149
+ }
150
+ else if (pwVersion === "1.53") {
151
+ original = `async setSources(sources, primaryPageURL) {\n }`;
152
+ }
153
+ else {
154
+ throw new Error(`Unsupported Playwright version: ${pwVersion}`);
155
+ }
129
156
  const replacement = `async setSources(sources) {
130
157
  ${CUSTOM_PATCH_COMMENT}
131
158
  const payload = JSON.stringify(sources.filter(source => source.isRecorded).filter(source => source.id === "javascript"));