@empiricalrun/test-gen 0.17.6 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @empiricalrun/test-gen
2
2
 
3
+ ## 0.19.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [b7797f9]
8
+ - @empiricalrun/reporter@0.12.3
9
+
10
+ ## 0.19.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 2087461: feat: remove LLMTracing class and use native methods
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [2087461]
19
+ - @empiricalrun/llm@0.4.0
20
+
21
+ ## 0.18.0
22
+
23
+ ### Minor Changes
24
+
25
+ - 4390c31: fix: filter video based on test name
26
+
3
27
  ## 0.17.6
4
28
 
5
29
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAWlC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAInD,KAAK,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,GAAG;IAC1D,YAAY,CAAC,EAAE;QACb,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC9B,CAAC;CACH,CAAC;AAEF,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,oBAAoB,mBAgG9B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAWlC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAInD,KAAK,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,GAAG;IAC1D,YAAY,CAAC,EAAE;QACb,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC9B,CAAC;CACH,CAAC;AAEF,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,oBAAoB,mBAkG9B"}
@@ -10,9 +10,11 @@ const html_1 = require("../../utils/html");
10
10
  const utils_1 = require("./utils");
11
11
  async function browsingAgent(task, page, options) {
12
12
  const logger = new logger_1.CustomLogger();
13
- const trace = new llm_1.LLMTracing({
13
+ const session = (0, session_1.getSessionDetails)();
14
+ const trace = llm_1.langfuseInstance.trace({
14
15
  name: "browsing-agent",
15
- sessionDetails: (0, session_1.getSessionDetails)(),
16
+ id: session.id,
17
+ release: session.version,
16
18
  tags: [
17
19
  options.metadata?.projectName,
18
20
  options.metadata?.environment,
@@ -26,13 +28,13 @@ async function browsingAgent(task, page, options) {
26
28
  trace.update({ input: { task } });
27
29
  let lastActionExecTrace = "";
28
30
  while (!isTaskDone) {
29
- const pageContentSpan = trace.startSpan("page-content");
31
+ const pageContentSpan = trace.span({ name: "page-content" });
30
32
  const pageContent = await page.content();
31
33
  pageContentSpan.end({ output: { pageContent } });
32
- const sanitizationSpan = trace.startSpan("page-sanitization");
34
+ const sanitizationSpan = trace.span({ name: "page-sanitization" });
33
35
  const pageSnapshot = (0, html_1.sanitizeHtml)(pageContent, options.htmlSanitize);
34
36
  sanitizationSpan.end({ output: { pageSnapshot } });
35
- const promptSpan = trace.startSpan("page-prompt");
37
+ const promptSpan = trace.span({ name: "page-prompt" });
36
38
  // extract all successful actions
37
39
  const successfulActions = executedActions
38
40
  .filter((a) => !a.isError)
@@ -93,7 +95,7 @@ async function browsingAgent(task, page, options) {
93
95
  const code = actions.generateCode();
94
96
  trace.update({ input: { task }, output: { code } });
95
97
  logger.success("Successfully generated code for the given task");
96
- logger.log(`Trace: ${trace.url}`);
98
+ logger.log(`Trace: ${trace.getTraceUrl()}`);
97
99
  return code;
98
100
  }
99
101
  exports.browsingAgent = browsingAgent;
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/run.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAE7D,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,QAAQ,EAAE,CAAC,CA8IrB"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/run.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAE7D,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,QAAQ,EAAE,CAAC,CAmJrB"}
@@ -21,9 +21,11 @@ async function generateTest(testCase, file, options) {
21
21
  const { codePrompt, pomPrompt, testFileContent } = context;
22
22
  const generatedTestCases = [];
23
23
  logger.logEmptyLine();
24
- const trace = new llm_1.LLMTracing({
24
+ const session = (0, session_1.getSessionDetails)();
25
+ const trace = llm_1.langfuseInstance.trace({
25
26
  name: "generate-test",
26
- sessionDetails: (0, session_1.getSessionDetails)(),
27
+ id: session.id,
28
+ release: session.version,
27
29
  tags: [options.metadata.projectName, options.metadata.environment].filter((s) => !!s),
28
30
  });
29
31
  trace.event({
@@ -36,7 +38,9 @@ async function generateTest(testCase, file, options) {
36
38
  });
37
39
  trace.update({ input: { testCase } });
38
40
  const isUpdate = testFileContent.includes(`test("${testCase?.name}"`);
39
- const promptSpan = trace.startSpan(isUpdate ? "update-scenario-prompt" : "add-scenario-prompt");
41
+ const promptSpan = trace.span({
42
+ name: isUpdate ? "update-scenario-prompt" : "add-scenario-prompt",
43
+ });
40
44
  const promptName = isUpdate ? "update-scenario" : "add-scenario";
41
45
  const instruction = await (0, llm_1.getPrompt)(promptName, {
42
46
  testFiles: codePrompt,
@@ -59,7 +63,7 @@ async function generateTest(testCase, file, options) {
59
63
  });
60
64
  let response = firstShotMessage?.content || "";
61
65
  logger.success("Test generated successfully!");
62
- const readWriteFileSpan = trace.startSpan("write-to-file");
66
+ const readWriteFileSpan = trace.span({ name: "write-to-file" });
63
67
  let contents = fs_extra_1.default.readFileSync(file, "utf-8");
64
68
  const [prependContent, strippedContent] = await (0, web_1.stripAndPrependImports)(response);
65
69
  let updatedContent = prependContent + contents + `\n\n${strippedContent}`;
@@ -73,7 +77,7 @@ async function generateTest(testCase, file, options) {
73
77
  logger.log("Linting generated code...");
74
78
  trace.event({ name: "lint-file" });
75
79
  await (0, web_1.lintErrors)(file);
76
- const validateTypesSpan = trace.startSpan("detect-type-errors-in-file");
80
+ const validateTypesSpan = trace.span({ name: "detect-type-errors-in-file" });
77
81
  logger.log("Validating types...");
78
82
  let errors = (0, web_1.validateTypescript)(file);
79
83
  validateTypesSpan.end({ output: { errors } });
@@ -90,7 +94,7 @@ async function generateTest(testCase, file, options) {
90
94
  logger.error([
91
95
  `Unable to fix typescript errors. Please review ${file} manually and fix the typescript errors.`,
92
96
  `Run the test-gen command again, once errors are fixed.`,
93
- `Trace: ${trace.url}`,
97
+ `Trace: ${trace.getTraceUrl()}`,
94
98
  ].join("\n"));
95
99
  break;
96
100
  }
@@ -98,7 +102,7 @@ async function generateTest(testCase, file, options) {
98
102
  logger.warn("Found few errors while validating types:");
99
103
  errors.forEach((e) => logger.warn(e));
100
104
  logger.log("Trying to fix above errors...");
101
- const promptSpan = trace.startSpan("fix-type-errors-prompt");
105
+ const promptSpan = trace.span({ name: "fix-type-errors-prompt" });
102
106
  const instruction = await (0, llm_1.getPrompt)("fix-file-errors-ts", {
103
107
  testFiles: codePrompt || "",
104
108
  pageFiles: pomPrompt || "",
@@ -120,12 +124,14 @@ async function generateTest(testCase, file, options) {
120
124
  },
121
125
  });
122
126
  response = message?.content || "";
123
- const readWriteFileSpan = trace.startSpan("write-to-file");
127
+ const readWriteFileSpan = trace.span({ name: "write-to-file" });
124
128
  await fs_extra_1.default.writeFile(file, response, "utf-8");
125
129
  readWriteFileSpan.end({ output: { response } });
126
130
  trace.event({ name: "lint-file" });
127
131
  await (0, web_1.lintErrors)(file);
128
- const validateTypesSpan = trace.startSpan("detect-type-errors-in-file");
132
+ const validateTypesSpan = trace.span({
133
+ name: "detect-type-errors-in-file",
134
+ });
129
135
  errors = (0, web_1.validateTypescript)(file);
130
136
  validateTypesSpan.end({ output: { errors } });
131
137
  if (!errors.length) {
@@ -135,7 +141,7 @@ async function generateTest(testCase, file, options) {
135
141
  trace.event({ name: "format-file" });
136
142
  await (0, web_1.formatCode)(file);
137
143
  logger.success("File formatted successfully!");
138
- logger.log(`Trace: ${trace.url}`);
144
+ logger.log(`Trace: ${trace.getTraceUrl()}`);
139
145
  generatedTestCases.push(testCase);
140
146
  trace.update({ input: { testCase }, output: { response } });
141
147
  return generatedTestCases;
package/dist/bin/index.js CHANGED
@@ -29,6 +29,7 @@ async function runAgent(sourceFile, testGenConfig) {
29
29
  await (0, run_1.generateTestsUsingBrowsingAgent)(specPath);
30
30
  await (0, reporter_1.reportTestGenVideos)({
31
31
  projectRepoName: testGenConfig.options.metadata.projectRepoName,
32
+ testName: testCase.name,
32
33
  });
33
34
  }
34
35
  else {
@@ -10,8 +10,9 @@ export declare function getReporter(): Reporter | undefined;
10
10
  * }
11
11
  * @returns Promise<void> returns void
12
12
  */
13
- export declare function reportTestGenVideos({ projectRepoName, }: {
13
+ export declare function reportTestGenVideos({ projectRepoName, testName, }: {
14
14
  projectRepoName: string;
15
+ testName: string;
15
16
  }): Promise<void>;
16
17
  export declare function setReporterConfig(config: any): void;
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/reporter/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAW5E,wBAAgB,WAAW,IAAI,QAAQ,GAAG,SAAS,CAUlD;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,eAAe,GAChB,EAAE;IACD,eAAe,EAAE,MAAM,CAAC;CACzB,iBA4BA;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAGnD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/reporter/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAW5E,wBAAgB,WAAW,IAAI,QAAQ,GAAG,SAAS,CAUlD;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,eAAe,EACf,QAAQ,GACT,EAAE;IACD,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB,iBA6BA;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAGnD"}
@@ -28,7 +28,7 @@ exports.getReporter = getReporter;
28
28
  * }
29
29
  * @returns Promise<void> returns void
30
30
  */
31
- async function reportTestGenVideos({ projectRepoName, }) {
31
+ async function reportTestGenVideos({ projectRepoName, testName, }) {
32
32
  const logger = new logger_1.CustomLogger();
33
33
  try {
34
34
  if (!(0, uploader_1.checkIfResultsUploadAllowed)()) {
@@ -36,6 +36,7 @@ async function reportTestGenVideos({ projectRepoName, }) {
36
36
  }
37
37
  const { videoUrls } = await (0, uploader_1.uploadTestResultsUsingPrjtRepo)({
38
38
  projectRepoName,
39
+ testName,
39
40
  });
40
41
  const reporter = getReporter();
41
42
  const reporterMessage = `
@@ -44,7 +45,7 @@ async function reportTestGenVideos({ projectRepoName, }) {
44
45
 
45
46
  ${videoUrls
46
47
  .map((url) => `
47
- <video src="${url}" autoplay="true" muted="true" controls loop playsinline></video>`)
48
+ <video src="${url}" autoplay="true" muted="true" controls playsinline></video>`)
48
49
  .join("\n")}
49
50
  `;
50
51
  await reporter?.report(new reporter_1.ProcessLogMessageBuilder({ message: reporterMessage }));
@@ -1,11 +1,16 @@
1
1
  /**
2
- * function to upload test results to r2 using the project repo name
3
- * this only uploads json summary of test results
4
- * @param { projectName: string } projectRepoName - name of the project repo
5
- * @returns urls of videos and summary json
2
+ * Function to upload test results to R2 using the project repo name and test name.
3
+ * This function uploads both the JSON summary of test results and associated video files.
4
+ * @param {Object} params - The parameters for the function.
5
+ * @param {string} params.projectRepoName - Name of the project repository.
6
+ * @param {string} params.testName - Name of the testcase which called this test-gen.
7
+ * @returns {Promise<Object>} An object containing arrays of video URLs and the summary JSON URL.
8
+ * @returns {string[]} returns.videoUrls - URLs of the uploaded video files.
9
+ * @returns {string} returns.summaryUrl - URL of the uploaded summary JSON file.
6
10
  */
7
- export declare function uploadTestResultsUsingPrjtRepo({ projectRepoName, }: {
11
+ export declare function uploadTestResultsUsingPrjtRepo({ projectRepoName, testName, }: {
8
12
  projectRepoName: string;
13
+ testName: string;
9
14
  }): Promise<{
10
15
  videoUrls: string[];
11
16
  summaryUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/uploader/index.ts"],"names":[],"mappings":"AAiBA;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAAC,EACnD,eAAe,GAChB,EAAE;IACD,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC;IACV,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CAyBD;AAED,wBAAgB,2BAA2B,uBAQ1C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/uploader/index.ts"],"names":[],"mappings":"AAmBA;;;;;;;;;GASG;AACH,wBAAsB,8BAA8B,CAAC,EACnD,eAAe,EACf,QAAQ,GACT,EAAE;IACD,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC;IACV,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CAgDD;AAED,wBAAgB,2BAA2B,uBAQ1C"}
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.checkIfResultsUploadAllowed = exports.uploadTestResultsUsingPrjtRepo = void 0;
7
+ const reporter_1 = require("@empiricalrun/reporter");
7
8
  const path_1 = __importDefault(require("path"));
8
9
  const r2_1 = require("./r2");
9
10
  // json summary of test results
@@ -15,12 +16,16 @@ function getFullUploadPath(filePath, uploadDir) {
15
16
  return `${UPLOAD_DOMAIN}/${uploadDir}${relativeFilePath}`;
16
17
  }
17
18
  /**
18
- * function to upload test results to r2 using the project repo name
19
- * this only uploads json summary of test results
20
- * @param { projectName: string } projectRepoName - name of the project repo
21
- * @returns urls of videos and summary json
19
+ * Function to upload test results to R2 using the project repo name and test name.
20
+ * This function uploads both the JSON summary of test results and associated video files.
21
+ * @param {Object} params - The parameters for the function.
22
+ * @param {string} params.projectRepoName - Name of the project repository.
23
+ * @param {string} params.testName - Name of the testcase which called this test-gen.
24
+ * @returns {Promise<Object>} An object containing arrays of video URLs and the summary JSON URL.
25
+ * @returns {string[]} returns.videoUrls - URLs of the uploaded video files.
26
+ * @returns {string} returns.summaryUrl - URL of the uploaded summary JSON file.
22
27
  */
23
- async function uploadTestResultsUsingPrjtRepo({ projectRepoName, }) {
28
+ async function uploadTestResultsUsingPrjtRepo({ projectRepoName, testName, }) {
24
29
  const uploadUniqueId = crypto.randomUUID();
25
30
  // project repo name is the github repo name
26
31
  // the folder names in r2 are the github repo name without the `-tests` suffix
@@ -30,12 +35,29 @@ async function uploadTestResultsUsingPrjtRepo({ projectRepoName, }) {
30
35
  destinationDir: uploadDir,
31
36
  uploadBucket: UPLOAD_BUCKET,
32
37
  });
33
- const fileNames = Object.keys(files);
34
- // TODO: parse the json summary and then detect video attachments
38
+ const fileNames = Object.keys(files); // fileNames are absolute paths of the input files
39
+ const defaultLocation = path_1.default.join(process.cwd(), "test-results", "summary.json");
40
+ const results = (0, reporter_1.parseJsonReport)(defaultLocation);
41
+ const flatTestsList = (0, reporter_1.getFlattenedTestList)(results.suites);
42
+ const testVideos = [];
43
+ for (const test of flatTestsList) {
44
+ if (test.title === testName) {
45
+ if (test.tests[0]) {
46
+ if (test.tests[0].results[0]) {
47
+ // results array is basically made by retries
48
+ for (const attachments of test.tests[0].results[0].attachments) {
49
+ if (attachments.path) {
50
+ testVideos.push(attachments.path);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
35
57
  // current assumption
36
58
  // - test gen will only run on a single spec file
37
- // - the video files will be of format <spec-file-name>.webm
38
- const videoFiles = fileNames.filter((fileName) => fileName.endsWith(".webm"));
59
+ // - the video files are of the format - <some-directory>/video.webm
60
+ const videoFiles = fileNames.filter((fileName) => fileName.endsWith(".webm") && testVideos.includes(fileName));
39
61
  return {
40
62
  videoUrls: videoFiles.map((fileName) => getFullUploadPath(fileName, uploadDir)),
41
63
  summaryUrl: getFullUploadPath("/test-results/summary.json", uploadDir),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-gen",
3
- "version": "0.17.6",
3
+ "version": "0.19.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -18,6 +18,7 @@
18
18
  "@actions/core": "^1.10.1",
19
19
  "@aws-sdk/client-s3": "^3.614.0",
20
20
  "@aws-sdk/s3-request-presigner": "^3.614.0",
21
+ "@playwright/test": "^1.44.1",
21
22
  "@types/sanitize-html": "^2.11.0",
22
23
  "commander": "^12.1.0",
23
24
  "detect-port": "^1.6.1",
@@ -39,8 +40,8 @@
39
40
  "slugify": "^1.6.6",
40
41
  "tsx": "^4.16.2",
41
42
  "typescript": "^5.3.3",
42
- "@empiricalrun/llm": "^0.3.0",
43
- "@empiricalrun/reporter": "^0.12.2"
43
+ "@empiricalrun/llm": "^0.4.0",
44
+ "@empiricalrun/reporter": "^0.12.3"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@types/detect-port": "^1.3.5",