@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.
- package/CHANGELOG.md +11 -0
- package/dist/agent/browsing/run.js +4 -4
- package/dist/agent/browsing/utils.d.ts +1 -0
- package/dist/agent/browsing/utils.d.ts.map +1 -1
- package/dist/agent/browsing/utils.js +5 -4
- package/dist/agent/chat/index.d.ts +1 -0
- package/dist/agent/chat/index.d.ts.map +1 -1
- package/dist/agent/chat/index.js +1 -0
- package/dist/agent/chat/prompt/index.d.ts.map +1 -1
- package/dist/agent/chat/prompt/index.js +3 -1
- package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.d.ts +14 -0
- package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.d.ts.map +1 -0
- package/dist/agent/cua/pw-codegen/pw-pause/for-recorder.js +62 -0
- package/dist/agent/cua/pw-codegen/pw-pause/index.d.ts.map +1 -1
- package/dist/agent/cua/pw-codegen/pw-pause/index.js +19 -15
- package/dist/agent/cua/pw-codegen/pw-pause/types.d.ts +14 -0
- package/dist/agent/cua/pw-codegen/pw-pause/types.d.ts.map +1 -0
- package/dist/agent/cua/pw-codegen/pw-pause/types.js +2 -0
- package/dist/artifacts/utils.d.ts +1 -1
- package/dist/artifacts/utils.d.ts.map +1 -1
- package/dist/artifacts/utils.js +5 -5
- package/dist/bin/index.js +7 -1
- package/dist/bin/utils/index.d.ts +2 -1
- package/dist/bin/utils/index.d.ts.map +1 -1
- package/dist/bin/utils/index.js +34 -5
- package/dist/browser-injected-scripts/annotate-elements.js +1 -4
- package/dist/file/client.d.ts +1 -0
- package/dist/file/client.d.ts.map +1 -1
- package/dist/file/client.js +3 -0
- package/dist/file/server.d.ts +2 -0
- package/dist/file/server.d.ts.map +1 -1
- package/dist/file/server.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/recorder/display.d.ts +2 -0
- package/dist/recorder/display.d.ts.map +1 -0
- package/dist/recorder/display.js +50 -0
- package/dist/recorder/index.d.ts +4 -0
- package/dist/recorder/index.d.ts.map +1 -0
- package/dist/recorder/index.js +108 -0
- package/dist/recorder/request.d.ts +6 -0
- package/dist/recorder/request.d.ts.map +1 -0
- package/dist/recorder/request.js +55 -0
- package/dist/recorder/temp-files.d.ts +3 -0
- package/dist/recorder/temp-files.d.ts.map +1 -0
- package/dist/recorder/temp-files.js +39 -0
- package/dist/recorder/upload.d.ts +2 -0
- package/dist/recorder/upload.d.ts.map +1 -0
- package/dist/recorder/upload.js +85 -0
- package/dist/recorder/validation.d.ts +2 -0
- package/dist/recorder/validation.d.ts.map +1 -0
- package/dist/recorder/validation.js +24 -0
- package/package.json +1 -1
- 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(
|
|
87
|
-
const updatedContent = (0, web_1.replaceCreateTestWithNewCode)(
|
|
88
|
-
fs_1.default.writeFileSync(
|
|
89
|
-
await (0, web_1.lintErrors)(
|
|
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;
|
|
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
|
|
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"), [
|
|
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
|
|
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
|
|
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;
|
|
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"}
|
package/dist/agent/chat/index.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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"}
|
|
@@ -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,
|
|
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"}
|
package/dist/artifacts/utils.js
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/bin/utils/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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;
|
package/dist/file/client.d.ts
CHANGED
|
@@ -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"}
|
package/dist/file/client.js
CHANGED
|
@@ -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",
|
package/dist/file/server.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/file/server.js
CHANGED
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -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"}
|