@empiricalrun/test-gen 0.27.9 → 0.28.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/actions/fill.d.ts.map +1 -1
  3. package/dist/actions/fill.js +18 -3
  4. package/dist/actions/index.d.ts +3 -1
  5. package/dist/actions/index.d.ts.map +1 -1
  6. package/dist/actions/index.js +20 -10
  7. package/dist/actions/text-content.d.ts +4 -0
  8. package/dist/actions/text-content.d.ts.map +1 -0
  9. package/dist/actions/text-content.js +53 -0
  10. package/dist/agent/browsing/index.d.ts.map +1 -1
  11. package/dist/agent/browsing/index.js +2 -0
  12. package/dist/agent/browsing/utils.d.ts.map +1 -1
  13. package/dist/agent/browsing/utils.js +25 -54
  14. package/dist/agent/codegen/create-test-block.d.ts +9 -0
  15. package/dist/agent/codegen/create-test-block.d.ts.map +1 -0
  16. package/dist/agent/codegen/create-test-block.js +63 -0
  17. package/dist/agent/codegen/fix-ts-errors.d.ts +2 -3
  18. package/dist/agent/codegen/fix-ts-errors.d.ts.map +1 -1
  19. package/dist/agent/codegen/fix-ts-errors.js +12 -16
  20. package/dist/agent/codegen/run.d.ts.map +1 -1
  21. package/dist/agent/codegen/run.js +4 -2
  22. package/dist/agent/codegen/update-flow.d.ts +8 -0
  23. package/dist/agent/codegen/update-flow.d.ts.map +1 -1
  24. package/dist/agent/codegen/update-flow.js +139 -58
  25. package/dist/bin/index.js +3 -0
  26. package/dist/bin/utils/index.d.ts +1 -1
  27. package/dist/bin/utils/index.d.ts.map +1 -1
  28. package/dist/bin/utils/index.js +2 -2
  29. package/dist/bin/utils/platform/web/index.d.ts +5 -1
  30. package/dist/bin/utils/platform/web/index.d.ts.map +1 -1
  31. package/dist/bin/utils/platform/web/index.js +55 -7
  32. package/dist/bin/utils/scenarios/index.d.ts +1 -1
  33. package/dist/bin/utils/scenarios/index.d.ts.map +1 -1
  34. package/dist/bin/utils/scenarios/index.js +3 -3
  35. package/dist/reporter/index.d.ts +1 -0
  36. package/dist/reporter/index.d.ts.map +1 -1
  37. package/dist/test-build/index.d.ts +10 -0
  38. package/dist/test-build/index.d.ts.map +1 -0
  39. package/dist/test-build/index.js +30 -0
  40. package/dist/types/index.d.ts +7 -1
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/package.json +9 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @empiricalrun/test-gen
2
2
 
3
+ ## 0.28.1
4
+
5
+ ### Patch Changes
6
+
7
+ - a6fe36d: fix: incomplete llm response at the time of fixing ts errors
8
+
9
+ ## 0.28.0
10
+
11
+ ### Minor Changes
12
+
13
+ - a3761d0: feat: support test block extraction via suites param
14
+ - 7ab94df: feat: add support for downloading latest build during test gen workflow
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [cc12707]
19
+ - @empiricalrun/reporter@0.18.2
20
+
21
+ ## 0.27.10
22
+
23
+ ### Patch Changes
24
+
25
+ - 66aa7b6: fix: master agent gives up on blank page and coding agent hallucination
26
+ - Updated dependencies [aadad32]
27
+ - Updated dependencies [bac164e]
28
+ - @empiricalrun/reporter@0.18.1
29
+ - @empiricalrun/r2-uploader@0.3.0
30
+
3
31
  ## 0.27.9
4
32
 
5
33
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"fill.d.ts","sourceRoot":"","sources":["../../src/actions/fill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAIrD,eAAO,MAAM,2BAA2B,uBAAuB,CAAC;AAEhE,eAAO,MAAM,mBAAmB,EAAE,yBA8CjC,CAAC"}
1
+ {"version":3,"file":"fill.d.ts","sourceRoot":"","sources":["../../src/actions/fill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAIrD,eAAO,MAAM,2BAA2B,uBAAuB,CAAC;AAGhE,eAAO,MAAM,mBAAmB,EAAE,yBAgEjC,CAAC"}
@@ -4,12 +4,16 @@ exports.fillActionGenerator = exports.PLAYWRIGHT_FILL_ACTION_NAME = void 0;
4
4
  const constants_1 = require("./constants");
5
5
  const utils_1 = require("./utils");
6
6
  exports.PLAYWRIGHT_FILL_ACTION_NAME = "fill_input_element";
7
- const fillActionGenerator = (page) => {
7
+ const NO_STATE_VARIABLE = "NA";
8
+ const fillActionGenerator = (page, options) => {
9
+ const stateVariableNames = Object.keys(options?.stateVariables || {});
10
+ stateVariableNames.push(NO_STATE_VARIABLE);
8
11
  return {
9
12
  execute: async (args) => {
10
13
  const css = args.css_selector;
11
14
  const locator = await (0, utils_1.getPlaywrightLocatorUsingCssSelector)(css, page);
12
- const exec = new Function("page", `return page.${locator}.fill("${args.text}", { timeout: 3000 })`);
15
+ const textToFill = options?.stateVariables[args.variable_name] || args.text;
16
+ const exec = new Function("page", `return page.${locator}.fill("${textToFill}", { timeout: 3000 })`);
13
17
  await exec(page);
14
18
  return {
15
19
  locator,
@@ -32,6 +36,17 @@ const fillActionGenerator = (page) => {
32
36
  type: "string",
33
37
  description: "The text to fill the input element with",
34
38
  },
39
+ variable_name: {
40
+ type: "string",
41
+ enum: stateVariableNames,
42
+ description: `variable name whose value needs to be filled in the input element.
43
+ The variable name and values are:
44
+
45
+ ${JSON.stringify(stateVariableNames, null, 2)}
46
+
47
+ - Choose the variable name based on the value above.
48
+ - Set variable_name as 'NA' if there is no variable to be used.`,
49
+ },
35
50
  css_selector: {
36
51
  type: "string",
37
52
  description: "CSS selector to identify the element uniquely.When creating CSS selectors, ensure they are unique to the page and specific enough to select only one element.",
@@ -41,7 +56,7 @@ const fillActionGenerator = (page) => {
41
56
  description: constants_1.DEFAULT_ACTION_REASON_PROMPT,
42
57
  },
43
58
  },
44
- required: ["text", "css_selector", "reason"],
59
+ required: ["variable_name", "text", "css_selector", "reason"],
45
60
  },
46
61
  },
47
62
  },
@@ -1,8 +1,10 @@
1
1
  import { Page } from "playwright";
2
2
  import { ActionSchema } from "../types";
3
3
  export declare class PlaywrightActions {
4
- private actions;
4
+ private page;
5
+ private actionGenerators;
5
6
  private recordedActions;
7
+ private stateVariables;
6
8
  constructor(page: Page);
7
9
  executeAction(name: string | undefined, args: Record<string, any>): Promise<void>;
8
10
  getActionSchemas(): ActionSchema[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIlC,OAAO,EAAU,YAAY,EAAE,MAAM,UAAU,CAAC;AAQhD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAW;IAC1B,OAAO,CAAC,eAAe,CAAmC;gBAC9C,IAAI,EAAE,IAAI;IAYhB,aAAa,CAAC,IAAI,oBAAa,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAuBhE,gBAAgB,IAAI,YAAY,EAAE;IAIlC,YAAY;IAIZ,gBAAgB,CAAC,KAAK,EAAE,MAAM;IAO9B,UAAU;CAUX"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIlC,OAAO,EAAE,YAAY,EAA6B,MAAM,UAAU,CAAC;AASnE,qBAAa,iBAAiB;IAMhB,OAAO,CAAC,IAAI;IAJxB,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,cAAc,CAA2B;gBAE7B,IAAI,EAAE,IAAI;IAaxB,aAAa,CAAC,IAAI,oBAAa,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAyBhE,gBAAgB,IAAI,YAAY,EAAE;IAMlC,YAAY;IAIZ,gBAAgB,CAAC,KAAK,EAAE,MAAM;IAO9B,UAAU;CAUX"}
@@ -9,22 +9,30 @@ const done_1 = require("./done");
9
9
  const fill_1 = require("./fill");
10
10
  const goto_1 = require("./goto");
11
11
  const reload_page_1 = require("./reload-page");
12
+ const text_content_1 = require("./text-content");
12
13
  class PlaywrightActions {
13
- actions;
14
+ page;
15
+ // private actions: Action[];
16
+ actionGenerators;
14
17
  recordedActions;
18
+ stateVariables = {};
15
19
  constructor(page) {
16
- this.actions = [
17
- (0, fill_1.fillActionGenerator)(page),
18
- (0, goto_1.gotoActionGenerator)(page),
19
- (0, click_1.clickActionGenerator)(page),
20
- (0, done_1.doneActionGenerator)(page),
21
- (0, assert_1.assertTextVisibilityActionGenerator)(page),
22
- (0, reload_page_1.reloadActionGenerator)(page),
20
+ this.page = page;
21
+ this.actionGenerators = [
22
+ fill_1.fillActionGenerator,
23
+ goto_1.gotoActionGenerator,
24
+ click_1.clickActionGenerator,
25
+ done_1.doneActionGenerator,
26
+ assert_1.assertTextVisibilityActionGenerator,
27
+ reload_page_1.reloadActionGenerator,
28
+ text_content_1.textContentActionGenerator,
23
29
  ];
24
30
  this.recordedActions = [];
25
31
  }
26
32
  async executeAction(name = "", args) {
27
- const [action] = this.actions.filter((a) => a.name === name);
33
+ const [action] = this.actionGenerators
34
+ .map((a) => a(this.page, { stateVariables: this.stateVariables }))
35
+ .filter((a) => a.name === name);
28
36
  if (!action) {
29
37
  throw Error(`No action registered for action: ${name}`);
30
38
  }
@@ -47,7 +55,9 @@ class PlaywrightActions {
47
55
  }
48
56
  }
49
57
  getActionSchemas() {
50
- return this.actions.map((a) => a.schema);
58
+ return this.actionGenerators
59
+ .map((a) => a(this.page, { stateVariables: this.stateVariables }))
60
+ .map((a) => a.schema);
51
61
  }
52
62
  generateCode() {
53
63
  return this.recordedActions.map((a) => a.code).join("\n");
@@ -0,0 +1,4 @@
1
+ import { PlaywrightActionGenerator } from "../types";
2
+ export declare const PLAYWRIGHT_GOTO_ACTION_NAME = "text_content";
3
+ export declare const textContentActionGenerator: PlaywrightActionGenerator;
4
+ //# sourceMappingURL=text-content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-content.d.ts","sourceRoot":"","sources":["../../src/actions/text-content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAIrD,eAAO,MAAM,2BAA2B,iBAAiB,CAAC;AAE1D,eAAO,MAAM,0BAA0B,EAAE,yBAkDxC,CAAC"}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.textContentActionGenerator = exports.PLAYWRIGHT_GOTO_ACTION_NAME = void 0;
4
+ const constants_1 = require("./constants");
5
+ const utils_1 = require("./utils");
6
+ exports.PLAYWRIGHT_GOTO_ACTION_NAME = "text_content";
7
+ const textContentActionGenerator = (page, options) => {
8
+ return {
9
+ execute: async (args) => {
10
+ const css = args.css_selector;
11
+ const locator = await (0, utils_1.getPlaywrightLocatorUsingCssSelector)(css, page);
12
+ const exec = new Function("page", `return page.${locator}.textContent()`);
13
+ const value = (await exec(page));
14
+ if (options) {
15
+ options.stateVariables[args.variable_name] = value;
16
+ }
17
+ return {
18
+ locator,
19
+ };
20
+ },
21
+ template: (args, options) => {
22
+ const templ = `const ${args.variable_name} = await ${(0, utils_1.getPageVarName)()}.${options.locator}.textContent();`;
23
+ return templ;
24
+ },
25
+ name: exports.PLAYWRIGHT_GOTO_ACTION_NAME,
26
+ schema: {
27
+ type: "function",
28
+ function: {
29
+ name: exports.PLAYWRIGHT_GOTO_ACTION_NAME,
30
+ description: "extract text content of the element",
31
+ parameters: {
32
+ type: "object",
33
+ properties: {
34
+ variable_name: {
35
+ type: "string",
36
+ description: "name of the variable to store the text content. The variable name should describe what the text content is about.",
37
+ },
38
+ css_selector: {
39
+ type: "string",
40
+ description: "CSS selector to identify the element uniquely.When creating CSS selectors, ensure they are unique to the page and specific enough to select only one element.",
41
+ },
42
+ reason: {
43
+ type: "string",
44
+ description: constants_1.DEFAULT_ACTION_REASON_PROMPT,
45
+ },
46
+ },
47
+ required: ["variable_name", "css_selector", "reason"],
48
+ },
49
+ },
50
+ },
51
+ };
52
+ };
53
+ exports.textContentActionGenerator = textContentActionGenerator;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAYlC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAMnD,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,6BAA6B,CACjD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,oBAAoB,mBAqO9B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAYlC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAMnD,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,6BAA6B,CACjD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,oBAAoB,mBAuO9B"}
@@ -36,6 +36,8 @@ async function browsingAgentUsingMasterAgent(task, page, options) {
36
36
  provider: options.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER,
37
37
  defaultModel: options.model || constants_1.DEFAULT_MODEL,
38
38
  providerApiKey: constants_1.MODEL_API_KEYS[options.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER],
39
+ // we will be using google model for larger context window, in such cases 1 million tokens is not enough
40
+ maxTokens: options.modelProvider === "google" ? 3000000 : 1000000,
39
41
  });
40
42
  const actions = new actions_1.PlaywrightActions(page);
41
43
  const tools = actions.getActionSchemas();
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAWvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,MAAM,CAKhD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,UAIvD;AAqFD;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,aAAa,GACvB,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAwCD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,iBAiBxD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,QA6BjD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAM1E;AAWD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,oBAAoB,GACrC,OAAO,CAAC,MAAM,CAAC,CA8CjB;AAED,wBAAsB,sBAAsB,CAAC,EAC3C,YAAiB,EACjB,IAAS,EACT,eAAoB,EACpB,gBAAqB,EACrB,UAAyC,GAC1C,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,8EASA"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAWvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAK5C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,MAAM,CAKhD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,UAIvD;AAqDD;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,aAAa,GACvB,OAAO,CAAC,MAAM,CAAC,CAkCjB;AAyBD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,iBAiBxD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,QA6BjD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAM1E;AAWD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,oBAAoB,GACrC,OAAO,CAAC,MAAM,CAAC,CA8CjB;AAED,wBAAsB,sBAAsB,CAAC,EAC3C,YAAiB,EACjB,IAAS,EACT,eAAoB,EACpB,gBAAqB,EACrB,UAAyC,GAC1C,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,8EASA"}
@@ -12,6 +12,7 @@ const api_1 = __importDefault(require("tsx/cjs/api"));
12
12
  const logger_1 = require("../../bin/logger");
13
13
  const context_1 = require("../../bin/utils/context");
14
14
  const web_1 = require("../../bin/utils/platform/web");
15
+ const create_test_block_1 = require("../codegen/create-test-block");
15
16
  const fix_ts_errors_1 = require("../codegen/fix-ts-errors");
16
17
  const update_flow_1 = require("../codegen/update-flow");
17
18
  function isRegExp(obj) {
@@ -31,25 +32,16 @@ exports.prepareBrowsingAgentTask = prepareBrowsingAgentTask;
31
32
  */
32
33
  async function prepareFileForUpdateScenario(genConfig) {
33
34
  const { specPath, testCase } = genConfig;
34
- const { name, steps } = testCase;
35
+ const { name } = testCase;
35
36
  // update the test case with appropriate location for createTest
36
37
  // TODO: reduce the payload for this LLM call. Only provide test file and page files which are used in the test block
37
38
  // this will help with faster response
38
- const [suggestion] = await (0, update_flow_1.updateTest)({
39
- ...testCase,
40
- steps: [
41
- ...steps,
42
- `
43
- - You are given a method with interface await createTest(<task>, <playwright_page_instance>)
44
- - This method will help you execute given task.
45
- - Given the task you need to decide the right file path and code block to place this method.
46
- - The task should contain hints as in where to place this method.
47
- - If there is no hint provided in the task, you can assume the method needs to be placed at the end of the test block.
48
- - YOU NEED TO MANDATORILY USE "createTest" method to execute the task.
49
- - Once the methods is placed in the right location with a task and playwright page provided, assume the task is done.
50
- - You need to respond with file path, code block and updated code block where this method can be placed in order to accomplish the task`,
51
- ],
52
- }, specPath, genConfig.options, false, false);
39
+ const [suggestion] = await (0, update_flow_1.appendCreateTestBlock)({
40
+ testCase,
41
+ file: specPath,
42
+ validateTypes: false,
43
+ options: genConfig.options,
44
+ });
53
45
  const createTestFilePath = suggestion?.updatedFiles[0] || "";
54
46
  console.log("appending to existing test block");
55
47
  console.log("updated test file path", createTestFilePath);
@@ -60,30 +52,17 @@ async function prepareFileForUpdateScenario(genConfig) {
60
52
  testCode: codePrompt,
61
53
  pomCode: pomPrompt,
62
54
  testCase: testCase,
63
- options: genConfig.options,
64
55
  });
65
56
  const testFileContent = await fs_extra_1.default.readFile(specPath, "utf-8");
66
- const { testBlock, testNode } = (0, web_1.getTypescriptTestBlock)(name, testFileContent);
57
+ const { testBlock, testNode } = (0, web_1.getTypescriptTestBlock)({
58
+ scenarioName: name,
59
+ content: testFileContent,
60
+ });
67
61
  const parentDescribe = (0, web_1.findFirstSerialDescribeBlock)(testNode);
68
62
  // add test.only / describe.only to the spec file so that only that block is executed
69
63
  const updatedTestFileContent = newContentsWithTestOnly(testFileContent, testBlock, testBlock, parentDescribe?.getText() || "");
70
64
  await fs_extra_1.default.writeFile(specPath, updatedTestFileContent);
71
65
  }
72
- /**
73
- * Function to prepare test file for new scenarios for master agent to run
74
- * @param {TestGenConfig} genConfig
75
- */
76
- async function prepareFileForNewScenario(genConfig) {
77
- const { specPath, testCase } = genConfig;
78
- const { name, steps } = testCase;
79
- console.log("creating new test block");
80
- const mergedSteps = prepareBrowsingAgentTask(steps);
81
- // TODO: this assumes that test code repo has `page` as the main entrypoint fixture
82
- const testGenCodeBlock = createTestBlockForCreateTest(name, mergedSteps);
83
- const existingContents = await fs_extra_1.default.readFile(specPath, "utf-8");
84
- const newContents = `${existingContents}\n\n${testGenCodeBlock}`;
85
- await fs_extra_1.default.writeFile(specPath, (0, web_1.addNewImport)(newContents, ["createTest"], "@empiricalrun/test-gen"));
86
- }
87
66
  /**
88
67
  * Function to prepare test file for master agent to run
89
68
  * @param {TestGenConfig} genConfig
@@ -99,16 +78,21 @@ async function prepareFileForMasterAgent(genConfig) {
99
78
  const fileContentWithImports = (0, web_1.addNewImport)("", ["test", "expect"], (0, web_1.getFixtureImportPath)(specPath));
100
79
  await fs_extra_1.default.writeFile(specPath, fileContentWithImports, "utf-8");
101
80
  }
102
- let createTestFilePath = specPath;
103
81
  const existingContents = await fs_extra_1.default.readFile(specPath, "utf-8");
104
- const { testBlock } = (0, web_1.getTypescriptTestBlock)(name, existingContents);
105
- if (testBlock) {
106
- await prepareFileForUpdateScenario(genConfig);
107
- }
108
- else {
109
- await prepareFileForNewScenario(genConfig);
82
+ const { testBlock } = (0, web_1.getTypescriptTestBlock)({
83
+ scenarioName: name,
84
+ content: existingContents,
85
+ });
86
+ if (!testBlock) {
87
+ const newTestBlock = await (0, create_test_block_1.createEmptyTestCaseBlock)({
88
+ testCase: genConfig.testCase,
89
+ file: specPath,
90
+ options: genConfig.options,
91
+ });
92
+ await fs_extra_1.default.writeFile(specPath, `${existingContents} \n\n ${newTestBlock}`, "utf-8");
110
93
  }
111
- return createTestFilePath;
94
+ await prepareFileForUpdateScenario(genConfig);
95
+ return specPath;
112
96
  }
113
97
  exports.prepareFileForMasterAgent = prepareFileForMasterAgent;
114
98
  function newContentsWithTestOnly(existingContents, originalTestBlock, updatedTestBlock, parentDescribeBlock) {
@@ -123,19 +107,6 @@ function newContentsWithTestOnly(existingContents, originalTestBlock, updatedTes
123
107
  return existingContents.replace(parentDescribeBlock, describeMarkedAsOnly);
124
108
  }
125
109
  }
126
- function createTestBlockForCreateTest(name, steps) {
127
- return `
128
- test.only("${name}", async ({page}) => {
129
- ${createTestGenBlock(steps)}
130
- });
131
- `;
132
- }
133
- function createTestGenBlock(steps) {
134
- return `
135
- await createTest(\`
136
- ${steps}\`, page);
137
- `;
138
- }
139
110
  async function injectPwLocatorGenerator(page) {
140
111
  const scriptResps = await Promise.all([
141
112
  "https://assets-test.empirical.run/pw-selector.js",
@@ -0,0 +1,9 @@
1
+ import { TraceClient } from "@empiricalrun/llm";
2
+ import { TestCase, TestGenConfigOptions } from "../../types";
3
+ export declare function createEmptyTestCaseBlock({ testCase, file, options, trace, }: {
4
+ testCase: TestCase;
5
+ file: string;
6
+ options?: TestGenConfigOptions;
7
+ trace?: TraceClient;
8
+ }): Promise<string | undefined>;
9
+ //# sourceMappingURL=create-test-block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-test-block.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/create-test-block.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAY3B,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAE7D,wBAAsB,wBAAwB,CAAC,EAC7C,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,+BAsDA"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createEmptyTestCaseBlock = void 0;
4
+ const llm_1 = require("@empiricalrun/llm");
5
+ const logger_1 = require("../../bin/logger");
6
+ const context_1 = require("../../bin/utils/context");
7
+ const web_1 = require("../../bin/utils/platform/web");
8
+ const constants_1 = require("../../constants");
9
+ const session_1 = require("../../session");
10
+ async function createEmptyTestCaseBlock({ testCase, file, options, trace, }) {
11
+ const logger = new logger_1.CustomLogger({ useReporter: false });
12
+ logger.log("Creating new test block");
13
+ const context = await (0, context_1.contextForGeneration)(file);
14
+ // TODO: move this to a common place
15
+ const session = (0, session_1.getSessionDetails)();
16
+ trace =
17
+ trace ||
18
+ llm_1.langfuseInstance.trace({
19
+ name: "update-test",
20
+ id: crypto.randomUUID(),
21
+ release: session.version,
22
+ tags: [
23
+ options?.metadata.projectName || "",
24
+ options?.metadata.environment || "",
25
+ ].filter((s) => !!s),
26
+ });
27
+ const promptSpan = trace?.span({
28
+ name: "build-create-empty-test-case-prompt",
29
+ });
30
+ const prompt = await (0, llm_1.getPrompt)("create-empty-test-block", {
31
+ testFiles: context.codePrompt,
32
+ pageFiles: context.pomPrompt,
33
+ scenarioName: testCase.name,
34
+ scenario: testCase.steps.join("\n"),
35
+ scenarioFile: file,
36
+ });
37
+ promptSpan?.end({ output: { prompt } });
38
+ const llm = new llm_1.LLM({
39
+ trace,
40
+ provider: options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER,
41
+ defaultModel: options?.model || constants_1.DEFAULT_MODEL,
42
+ providerApiKey: constants_1.MODEL_API_KEYS[options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER],
43
+ });
44
+ const firstShotMessage = await llm.createChatCompletion({
45
+ messages: prompt,
46
+ modelParameters: {
47
+ ...constants_1.DEFAULT_MODEL_PARAMETERS,
48
+ ...options?.modelParameters,
49
+ },
50
+ });
51
+ const markdownRemoverSpan = trace?.span({
52
+ name: "remove-markdown-span",
53
+ });
54
+ let response = firstShotMessage?.content || "";
55
+ markdownRemoverSpan?.end({ output: { response } });
56
+ const { testBlock } = (0, web_1.getTypescriptTestBlock)({
57
+ scenarioName: testCase.name,
58
+ content: response,
59
+ });
60
+ console.log(`trace: ${trace?.getTraceUrl()}`);
61
+ return testBlock;
62
+ }
63
+ exports.createEmptyTestCaseBlock = createEmptyTestCaseBlock;
@@ -1,13 +1,12 @@
1
1
  import { TraceClient } from "@empiricalrun/llm";
2
2
  import { CustomLogger } from "../../bin/logger";
3
- import { TestCase, TestGenConfigOptions } from "../../types";
4
- export declare function validateAndFixTypescriptErrors({ trace, logger, file, testCode, pomCode, testCase, options, }: {
3
+ import { TestCase } from "../../types";
4
+ export declare function validateAndFixTypescriptErrors({ trace, logger, file, testCode, pomCode, testCase, }: {
5
5
  trace?: TraceClient;
6
6
  logger?: CustomLogger;
7
7
  file: string;
8
8
  testCode: string;
9
9
  pomCode: string;
10
10
  testCase: TestCase;
11
- options?: TestGenConfigOptions;
12
11
  }): Promise<void>;
13
12
  //# sourceMappingURL=fix-ts-errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fix-ts-errors.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/fix-ts-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAQhD,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAE7D,wBAAsB,8BAA8B,CAAC,EACnD,KAAK,EACL,MAA2B,EAC3B,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,GACR,EAAE;IACD,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC,iBAoEA"}
1
+ {"version":3,"file":"fix-ts-errors.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/fix-ts-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAK3D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,wBAAsB,8BAA8B,CAAC,EACnD,KAAK,EACL,MAA2B,EAC3B,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,QAAQ,GACT,EAAE;IACD,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;CACpB,iBA8DA"}
@@ -5,11 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.validateAndFixTypescriptErrors = void 0;
7
7
  const llm_1 = require("@empiricalrun/llm");
8
+ const generative_ai_1 = require("@google/generative-ai");
8
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const remove_markdown_1 = __importDefault(require("remove-markdown"));
9
11
  const logger_1 = require("../../bin/logger");
10
12
  const web_1 = require("../../bin/utils/platform/web");
11
- const constants_1 = require("../../constants");
12
- async function validateAndFixTypescriptErrors({ trace, logger = new logger_1.CustomLogger(), file, testCode, pomCode, testCase, options, }) {
13
+ async function validateAndFixTypescriptErrors({ trace, logger = new logger_1.CustomLogger(), file, testCode, pomCode, testCase, }) {
13
14
  const validateTypesSpan = trace?.span({ name: "detect-type-errors-in-file" });
14
15
  logger.log("Validating types...");
15
16
  let errors = (0, web_1.validateTypescript)(file);
@@ -43,20 +44,15 @@ async function validateAndFixTypescriptErrors({ trace, logger = new logger_1.Cus
43
44
  scenaioName: testCase.name,
44
45
  });
45
46
  promptSpan?.end({ output: { instruction } });
46
- const llm = new llm_1.LLM({
47
- trace,
48
- provider: options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER,
49
- defaultModel: options?.model || constants_1.DEFAULT_MODEL,
50
- providerApiKey: constants_1.MODEL_API_KEYS[options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER],
51
- });
52
- const message = await llm.createChatCompletion({
53
- messages: instruction,
54
- modelParameters: {
55
- ...constants_1.DEFAULT_MODEL_PARAMETERS,
56
- ...options?.modelParameters,
57
- },
58
- });
59
- const response = message?.content || "";
47
+ const genAI = new generative_ai_1.GoogleGenerativeAI(process.env.GOOGLE_API_KEY);
48
+ const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro-latest" });
49
+ const prompt = instruction.map((p) => p.content);
50
+ const llmOutputTrace = trace?.span({ name: "llm-output" });
51
+ const message = await model.generateContent(prompt);
52
+ llmOutputTrace?.end({ output: { message: message.response.text() } });
53
+ const removeMarkdownSpan = trace?.span({ name: "remove-markdown" });
54
+ let response = (0, remove_markdown_1.default)(message.response.text() || "");
55
+ removeMarkdownSpan?.end({ output: { response } });
60
56
  const readWriteFileSpan = trace?.span({ name: "write-to-file" });
61
57
  await fs_extra_1.default.writeFile(file, response, "utf-8");
62
58
  readWriteFileSpan?.end({ output: { response } });
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/run.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAI7D,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,QAAQ,EAAE,CAAC,CA+FrB"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/run.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAI7D,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,QAAQ,EAAE,CAAC,CA8FrB"}
@@ -22,7 +22,10 @@ async function generateTest(testCase, file, options) {
22
22
  }
23
23
  const context = await (0, context_1.contextForGeneration)(file);
24
24
  const { codePrompt, pomPrompt, testFileContent } = context;
25
- const { testBlock } = (0, web_1.getTypescriptTestBlock)(testCase?.name, testFileContent);
25
+ const { testBlock } = (0, web_1.getTypescriptTestBlock)({
26
+ scenarioName: testCase?.name,
27
+ content: testFileContent,
28
+ });
26
29
  const isUpdate = !!testBlock;
27
30
  if (isUpdate) {
28
31
  return await (0, update_flow_1.updateTest)(testCase, file, options);
@@ -87,7 +90,6 @@ async function generateTest(testCase, file, options) {
87
90
  testCode: codePrompt,
88
91
  pomCode: pomPrompt,
89
92
  testCase: testCase,
90
- options,
91
93
  });
92
94
  trace.event({ name: "format-file" });
93
95
  await (0, web_1.formatCode)(file);
@@ -1,7 +1,15 @@
1
+ import { TraceClient } from "@empiricalrun/llm";
1
2
  import { TestCase, TestGenConfigOptions } from "../../types";
2
3
  type UpdatedTestCase = TestCase & {
3
4
  updatedFiles: string[];
4
5
  };
5
6
  export declare function updateTest(testCase: TestCase, file: string, options: TestGenConfigOptions | undefined, logging?: boolean, validate?: boolean): Promise<UpdatedTestCase[]>;
7
+ export declare function appendCreateTestBlock({ testCase, file, options, trace, validateTypes, }: {
8
+ testCase: TestCase;
9
+ file: string;
10
+ options?: TestGenConfigOptions;
11
+ trace?: TraceClient;
12
+ validateTypes?: boolean;
13
+ }): Promise<UpdatedTestCase[]>;
6
14
  export {};
7
15
  //# sourceMappingURL=update-flow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"update-flow.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/update-flow.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAI7D,KAAK,eAAe,GAAG,QAAQ,GAAG;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAEF,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,GAAG,SAAS,EACzC,OAAO,GAAE,OAAc,EACvB,QAAQ,GAAE,OAAc,GACvB,OAAO,CAAC,eAAe,EAAE,CAAC,CAoJ5B"}
1
+ {"version":3,"file":"update-flow.d.ts","sourceRoot":"","sources":["../../../src/agent/codegen/update-flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAmB3B,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAI7D,KAAK,eAAe,GAAG,QAAQ,GAAG;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AA6GF,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,GAAG,SAAS,EACzC,OAAO,GAAE,OAAc,EACvB,QAAQ,GAAE,OAAc,GACvB,OAAO,CAAC,eAAe,EAAE,CAAC,CA4E5B;AAED,wBAAsB,qBAAqB,CAAC,EAC1C,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,KAAK,EACL,aAAoB,GACrB,EAAE;IACD,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAgE7B"}
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.updateTest = void 0;
6
+ exports.appendCreateTestBlock = exports.updateTest = void 0;
7
7
  const llm_1 = require("@empiricalrun/llm");
8
8
  const crypto_1 = __importDefault(require("crypto"));
9
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
@@ -15,64 +15,15 @@ const constants_1 = require("../../constants");
15
15
  const session_1 = require("../../session");
16
16
  const fix_ts_errors_1 = require("./fix-ts-errors");
17
17
  const utils_1 = require("./utils");
18
- async function updateTest(testCase, file, options, logging = true, validate = true) {
19
- const logger = new logger_1.CustomLogger({ useReporter: logging });
20
- const context = await (0, context_1.contextForGeneration)(file);
21
- const { codePrompt, pomPrompt, testFileContent } = context;
22
- const generatedTestCases = [];
23
- logger.logEmptyLine();
24
- const session = (0, session_1.getSessionDetails)();
25
- const trace = llm_1.langfuseInstance.trace({
26
- name: "update-test",
27
- id: crypto_1.default.randomUUID(),
28
- release: session.version,
29
- tags: [
30
- options?.metadata.projectName || "",
31
- options?.metadata.environment || "",
32
- ].filter((s) => !!s),
33
- });
34
- trace.event({
35
- name: "collate-files-as-text",
36
- output: {
37
- codePrompt,
38
- pomPrompt,
39
- testFileContent,
40
- },
41
- });
42
- trace.update({ input: { testCase } });
43
- const promptSpan = trace.span({
44
- name: "update-scenario-prompt",
45
- });
46
- const promptName = "update-scenario";
47
- const instruction = await (0, llm_1.getPrompt)(promptName, {
48
- testFiles: codePrompt,
49
- pageFiles: pomPrompt,
50
- scenarioName: testCase.name,
51
- scenarioSteps: testCase.steps.join("\n"),
52
- scenarioFile: file,
53
- }, 14);
54
- promptSpan.end({ output: { instruction } });
55
- const llm = new llm_1.LLM({
56
- trace,
57
- provider: options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER,
58
- defaultModel: options?.model || constants_1.DEFAULT_MODEL,
59
- providerApiKey: constants_1.MODEL_API_KEYS[options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER],
60
- });
61
- const firstShotMessage = await llm.createChatCompletion({
62
- messages: instruction,
63
- modelParameters: {
64
- ...constants_1.DEFAULT_MODEL_PARAMETERS,
65
- ...options?.modelParameters,
66
- },
67
- });
68
- let response = firstShotMessage?.content || "";
69
- logger.success("Test generated successfully!");
70
- const fileChanges = (0, utils_1.extractTestUpdates)(response);
18
+ async function applyFileChanges({ validateTypes = true, trace, testCase, fileChanges, logger, pomPrompt, codePrompt, }) {
71
19
  await Promise.allSettled(fileChanges.map(async (fileChange) => {
72
20
  if (!fileChange.filePath) {
73
21
  return;
74
22
  }
75
- const { testBlock: testBlockUpdate } = (0, web_1.getTypescriptTestBlock)(testCase?.name || "", fileChange.newCode || "");
23
+ const { testBlock: testBlockUpdate } = (0, web_1.getTypescriptTestBlock)({
24
+ scenarioName: testCase?.name || "",
25
+ content: fileChange.newCode || "",
26
+ });
76
27
  if (testBlockUpdate) {
77
28
  // assuming the test case getting updated
78
29
  // maintaining the previous accuracy of the test case update
@@ -80,7 +31,10 @@ async function updateTest(testCase, file, options, logging = true, validate = tr
80
31
  let contents = await fs_extra_1.default.readFile(fileChange.filePath, "utf-8");
81
32
  const [prependContent, strippedContent] = await (0, web_1.stripAndPrependImports)(fileChange.newCode, testCase?.name);
82
33
  let updatedContent = prependContent + contents + `\n\n${strippedContent}`;
83
- const { testBlock } = (0, web_1.getTypescriptTestBlock)(testCase?.name, contents);
34
+ const { testBlock } = (0, web_1.getTypescriptTestBlock)({
35
+ scenarioName: testCase?.name,
36
+ content: contents,
37
+ });
84
38
  contents = contents.replace(testBlock, `\n\n${strippedContent}`);
85
39
  updatedContent = prependContent + contents;
86
40
  await fs_extra_1.default.writeFile(fileChange.filePath, updatedContent, "utf-8");
@@ -113,7 +67,7 @@ async function updateTest(testCase, file, options, logging = true, validate = tr
113
67
  readWriteFileSpan.end({ output: { contents } });
114
68
  }
115
69
  // format and validate file change
116
- if (validate) {
70
+ if (validateTypes) {
117
71
  await (0, fix_ts_errors_1.validateAndFixTypescriptErrors)({
118
72
  trace,
119
73
  logger,
@@ -121,13 +75,75 @@ async function updateTest(testCase, file, options, logging = true, validate = tr
121
75
  testCode: codePrompt,
122
76
  pomCode: pomPrompt,
123
77
  testCase: testCase,
124
- options,
125
78
  });
126
79
  }
127
80
  trace.event({ name: "format-file" });
128
81
  await (0, web_1.formatCode)(fileChange.filePath);
129
82
  logger.success(`${fileChange.filePath} file formatted successfully!`);
130
83
  }));
84
+ }
85
+ async function updateTest(testCase, file, options, logging = true, validate = true) {
86
+ const logger = new logger_1.CustomLogger({ useReporter: logging });
87
+ const context = await (0, context_1.contextForGeneration)(file);
88
+ const { codePrompt, pomPrompt, testFileContent } = context;
89
+ const generatedTestCases = [];
90
+ logger.logEmptyLine();
91
+ const session = (0, session_1.getSessionDetails)();
92
+ const trace = llm_1.langfuseInstance.trace({
93
+ name: "update-test",
94
+ id: crypto_1.default.randomUUID(),
95
+ release: session.version,
96
+ tags: [
97
+ options?.metadata.projectName || "",
98
+ options?.metadata.environment || "",
99
+ ].filter((s) => !!s),
100
+ });
101
+ trace.event({
102
+ name: "collate-files-as-text",
103
+ output: {
104
+ codePrompt,
105
+ pomPrompt,
106
+ testFileContent,
107
+ },
108
+ });
109
+ trace.update({ input: { testCase } });
110
+ const promptSpan = trace.span({
111
+ name: "update-scenario-prompt",
112
+ });
113
+ const promptName = "update-scenario";
114
+ const instruction = await (0, llm_1.getPrompt)(promptName, {
115
+ testFiles: codePrompt,
116
+ pageFiles: pomPrompt,
117
+ scenarioName: testCase.name,
118
+ scenarioSteps: testCase.steps.join("\n"),
119
+ scenarioFile: file,
120
+ }, 14);
121
+ promptSpan.end({ output: { instruction } });
122
+ const llm = new llm_1.LLM({
123
+ trace,
124
+ provider: options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER,
125
+ defaultModel: options?.model || constants_1.DEFAULT_MODEL,
126
+ providerApiKey: constants_1.MODEL_API_KEYS[options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER],
127
+ });
128
+ const firstShotMessage = await llm.createChatCompletion({
129
+ messages: instruction,
130
+ modelParameters: {
131
+ ...constants_1.DEFAULT_MODEL_PARAMETERS,
132
+ ...options?.modelParameters,
133
+ },
134
+ });
135
+ let response = firstShotMessage?.content || "";
136
+ logger.success("Test generated successfully!");
137
+ const fileChanges = (0, utils_1.extractTestUpdates)(response);
138
+ await applyFileChanges({
139
+ validateTypes: validate,
140
+ trace,
141
+ testCase,
142
+ fileChanges,
143
+ logger,
144
+ pomPrompt: pomPrompt,
145
+ codePrompt: codePrompt,
146
+ });
131
147
  logger.log(`Trace: ${trace.getTraceUrl()}`);
132
148
  generatedTestCases.push({
133
149
  ...testCase,
@@ -138,3 +154,68 @@ async function updateTest(testCase, file, options, logging = true, validate = tr
138
154
  return generatedTestCases;
139
155
  }
140
156
  exports.updateTest = updateTest;
157
+ async function appendCreateTestBlock({ testCase, file, options, trace, validateTypes = true, }) {
158
+ const logger = new logger_1.CustomLogger({ useReporter: false });
159
+ logger.log("Appending create test block");
160
+ const context = await (0, context_1.contextForGeneration)(file);
161
+ const { codePrompt, pomPrompt } = context;
162
+ const generatedTestCases = [];
163
+ // TODO: move this to a common place
164
+ const session = (0, session_1.getSessionDetails)();
165
+ trace =
166
+ trace ||
167
+ llm_1.langfuseInstance.trace({
168
+ name: "append-create-test-block",
169
+ id: crypto_1.default.randomUUID(),
170
+ release: session.version,
171
+ tags: [
172
+ options?.metadata.projectName || "",
173
+ options?.metadata.environment || "",
174
+ ].filter((s) => !!s),
175
+ });
176
+ const promptName = "append-create-test-block";
177
+ const promptSpan = trace.span({
178
+ name: "append-create-test-block-prompt",
179
+ });
180
+ const instruction = await (0, llm_1.getPrompt)(promptName, {
181
+ testFiles: codePrompt,
182
+ pageFiles: pomPrompt,
183
+ scenarioName: testCase.name,
184
+ scenarioSteps: testCase.steps.join("\n"),
185
+ scenarioFile: file,
186
+ });
187
+ promptSpan.end({ output: { instruction } });
188
+ const llm = new llm_1.LLM({
189
+ trace,
190
+ provider: options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER,
191
+ defaultModel: options?.model || constants_1.DEFAULT_MODEL,
192
+ providerApiKey: constants_1.MODEL_API_KEYS[options?.modelProvider || constants_1.DEFAULT_MODEL_PROVIDER],
193
+ });
194
+ const firstShotMessage = await llm.createChatCompletion({
195
+ messages: instruction,
196
+ modelParameters: {
197
+ ...constants_1.DEFAULT_MODEL_PARAMETERS,
198
+ ...options?.modelParameters,
199
+ },
200
+ });
201
+ let response = firstShotMessage?.content || "";
202
+ const fileChanges = (0, utils_1.extractTestUpdates)(response);
203
+ await applyFileChanges({
204
+ trace,
205
+ testCase,
206
+ fileChanges,
207
+ logger,
208
+ pomPrompt: pomPrompt,
209
+ codePrompt: codePrompt,
210
+ validateTypes,
211
+ });
212
+ logger.log(`Trace: ${trace.getTraceUrl()}`);
213
+ generatedTestCases.push({
214
+ ...testCase,
215
+ updatedFiles: fileChanges.map((f) => f.filePath),
216
+ });
217
+ trace.update({ input: { testCase }, output: { response } });
218
+ await (0, llm_1.flushAllTraces)();
219
+ return generatedTestCases;
220
+ }
221
+ exports.appendCreateTestBlock = appendCreateTestBlock;
package/dist/bin/index.js CHANGED
@@ -12,6 +12,7 @@ const run_2 = require("../agent/codegen/run");
12
12
  const reporter_1 = require("../reporter");
13
13
  const ci_1 = require("../reporter/ci");
14
14
  const session_1 = require("../session");
15
+ const test_build_1 = require("../test-build");
15
16
  const logger_1 = require("./logger");
16
17
  const utils_2 = require("./utils");
17
18
  dotenv_1.default.config({
@@ -62,6 +63,8 @@ async function runAgent(testGenConfig) {
62
63
  generationId: testGenConfig.options?.metadata.generationId,
63
64
  });
64
65
  try {
66
+ // download the build if it exists
67
+ await (0, test_build_1.downloadBuild)(testGenConfig.build || {});
65
68
  await runAgent(testGenConfig);
66
69
  }
67
70
  catch (e) {
@@ -1,5 +1,5 @@
1
1
  import { TestGenConfig } from "../../types";
2
- export declare function parseCliArgs(scenarioOrScenariosPath?: string): Promise<{
2
+ export declare function parseCliArgs(testGenToken?: string): Promise<{
3
3
  testGenConfig: TestGenConfig;
4
4
  }>;
5
5
  export declare function getTestConfigCliArg(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,wBAAsB,YAAY,CAChC,uBAAuB,GAAE,MAA8B;;GAQxD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/bin/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,wBAAsB,YAAY,CAChC,YAAY,GAAE,MAA8B;;GAM7C;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"}
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getTestConfigCliArg = exports.parseCliArgs = void 0;
4
4
  const scenarios_1 = require("./scenarios");
5
- async function parseCliArgs(scenarioOrScenariosPath = getTestConfigCliArg()) {
6
- const testGenConfig = await (0, scenarios_1.loadTestConfigs)(scenarioOrScenariosPath);
5
+ async function parseCliArgs(testGenToken = getTestConfigCliArg()) {
6
+ const testGenConfig = await (0, scenarios_1.loadTestConfigs)(testGenToken);
7
7
  return {
8
8
  testGenConfig,
9
9
  };
@@ -6,7 +6,11 @@ import { Node } from "ts-morph";
6
6
  * @param {string} content
7
7
  * @return { testBlock: string; parentDescribe: string; } testBlock - the test block content, testNode - the test function node
8
8
  */
9
- export declare function getTypescriptTestBlock(scenarioName: string, content: string): {
9
+ export declare function getTypescriptTestBlock({ scenarioName, suites, content, }: {
10
+ scenarioName: string;
11
+ suites?: string[];
12
+ content: string;
13
+ }): {
10
14
  testBlock: string | undefined;
11
15
  testNode: Node | undefined;
12
16
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/bin/utils/platform/web/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAqB,IAAI,EAAuB,MAAM,UAAU,CAAC;AAGxE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,GACd;IACD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,EAAE,IAAI,GAAG,SAAS,CAAC;CAC5B,CAeA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,IAAI,GAAG,SAAS,GACrB,IAAI,GAAG,SAAS,CA4BlB;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAG5E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA8C7D;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,mCAUjB;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAShD;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAQhD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,UAE5E;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,iBAMpD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,UAcpD;AAED,wBAAsB,iCAAiC,CAAC,QAAQ,EAAE,MAAM,+BAmBvE;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,UA0CtB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/bin/utils/platform/web/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,IAAI,EAAuB,MAAM,UAAU,CAAC;AAGxE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,YAAY,EACZ,MAAM,EACN,OAAO,GACR,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG;IACF,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,EAAE,IAAI,GAAG,SAAS,CAAC;CAC5B,CAuCA;AAwBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,IAAI,GAAG,SAAS,GACrB,IAAI,GAAG,SAAS,CA4BlB;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAG5E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA8C7D;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,mCAUjB;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAShD;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,iBAQhD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,UAE5E;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,iBAMpD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,UAcpD;AAED,wBAAsB,iCAAiC,CAAC,QAAQ,EAAE,MAAM,+BAmBvE;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,UA0CtB"}
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.replaceCreateTestWithNewCode = exports.getPageVariableNameFromCreateTest = exports.getFixtureImportPath = exports.removeTestOnly = exports.addNewImport = exports.formatCode = exports.lintErrors = exports.stripAndPrependImports = exports.validateTypescript = exports.appendToTestBlock = exports.findFirstSerialDescribeBlock = exports.getTypescriptTestBlock = void 0;
7
7
  const eslint_1 = require("eslint");
8
8
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const lodash_isequal_1 = __importDefault(require("lodash.isequal"));
9
10
  const prettier_1 = __importDefault(require("prettier"));
10
11
  const ts_morph_1 = require("ts-morph");
11
12
  const typescript_1 = __importDefault(require("typescript"));
@@ -16,18 +17,62 @@ const typescript_1 = __importDefault(require("typescript"));
16
17
  * @param {string} content
17
18
  * @return { testBlock: string; parentDescribe: string; } testBlock - the test block content, testNode - the test function node
18
19
  */
19
- function getTypescriptTestBlock(scenarioName, content) {
20
+ function getTypescriptTestBlock({ scenarioName, suites, content, }) {
20
21
  const project = new ts_morph_1.Project();
21
22
  const sourceFile = project.createSourceFile("test.ts", content);
22
- const testFunctionNode = sourceFile.getFirstDescendant((node) => !!(node.isKind(ts_morph_1.SyntaxKind.CallExpression) &&
23
- node.getExpression().getText() === "test" &&
24
- node.getArguments()[0]?.getText().includes(scenarioName)));
23
+ // Get all test function nodes that match the scenario name
24
+ const matchingTestFunctionNodes = sourceFile
25
+ .getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
26
+ .filter((node) => {
27
+ return Boolean(node.getExpression().getText() === "test" &&
28
+ node.getArguments()[0]?.getText().includes(scenarioName));
29
+ });
30
+ if (!suites?.length) {
31
+ const firstNode = matchingTestFunctionNodes?.[0];
32
+ return {
33
+ testBlock: firstNode?.getText(),
34
+ testNode: firstNode,
35
+ };
36
+ }
37
+ // Iterate over each test function node and check if the suites match
38
+ for (const testNode of matchingTestFunctionNodes) {
39
+ const parentDescribes = getParentDescribeNames(testNode);
40
+ if ((0, lodash_isequal_1.default)(parentDescribes, suites)) {
41
+ // Found the matching test block
42
+ return {
43
+ testBlock: testNode.getText(),
44
+ testNode,
45
+ };
46
+ }
47
+ }
48
+ // No matching test block found
25
49
  return {
26
- testBlock: testFunctionNode?.getText(),
27
- testNode: testFunctionNode,
50
+ testBlock: undefined,
51
+ testNode: undefined,
28
52
  };
29
53
  }
30
54
  exports.getTypescriptTestBlock = getTypescriptTestBlock;
55
+ // get the names of parent describe blocks
56
+ function getParentDescribeNames(node) {
57
+ const names = [];
58
+ let current = node.getParent();
59
+ while (current) {
60
+ if (ts_morph_1.Node.isCallExpression(current)) {
61
+ const expr = current.getExpression();
62
+ if (expr.getText() === "test.describe") {
63
+ const describeBlockArguments = current.getArguments();
64
+ if (describeBlockArguments.length > 0) {
65
+ const describeBlockName = describeBlockArguments[0];
66
+ if (ts_morph_1.Node.isStringLiteral(describeBlockName)) {
67
+ names.push(describeBlockName.getLiteralText());
68
+ }
69
+ }
70
+ }
71
+ }
72
+ current = current.getParent();
73
+ }
74
+ return names.reverse(); // Reverse to get from outermost to innermost
75
+ }
31
76
  /**
32
77
  * Function to find the first 'describe' block configured with 'serial: true'
33
78
  *
@@ -124,7 +169,10 @@ exports.validateTypescript = validateTypescript;
124
169
  async function stripAndPrependImports(content, testName) {
125
170
  const importRegexp = /import\s+\{[^}]*\}\s+from\s+["'][^"']+["'];?/g;
126
171
  const imports = content.match(importRegexp);
127
- const { testBlock: strippedContent } = getTypescriptTestBlock(testName, content);
172
+ const { testBlock: strippedContent } = getTypescriptTestBlock({
173
+ scenarioName: testName,
174
+ content,
175
+ });
128
176
  const prependContent = (imports?.join("\n") || "") + "\n\n";
129
177
  return [prependContent, strippedContent];
130
178
  }
@@ -1,4 +1,4 @@
1
1
  import { TestGenConfig } from "../../../types";
2
- declare function loadTestConfigs(scenariosPath: string): Promise<TestGenConfig>;
2
+ declare function loadTestConfigs(testGenToken: string): Promise<TestGenConfig>;
3
3
  export { loadTestConfigs };
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bin/utils/scenarios/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAwB,MAAM,gBAAgB,CAAC;AASrE,iBAAe,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAc5E;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bin/utils/scenarios/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,aAAa,EAAwB,MAAM,gBAAgB,CAAC;AAU5E,iBAAe,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAc3E;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loadTestConfigs = void 0;
4
- async function loadTestConfigs(scenariosPath) {
5
- // scenariosPath is the testGenToken passed from ci
6
- const str = decodeURIComponent(atob(scenariosPath));
4
+ async function loadTestConfigs(testGenToken) {
5
+ const str = decodeURIComponent(atob(testGenToken));
7
6
  const config = JSON.parse(str);
8
7
  const specPath = `./tests/${config.group || "index"}.spec.ts`;
9
8
  return {
@@ -13,6 +12,7 @@ async function loadTestConfigs(scenariosPath) {
13
12
  steps: config.steps.filter((s) => !!s),
14
13
  group: config.group,
15
14
  },
15
+ build: config.build,
16
16
  options: config.options,
17
17
  };
18
18
  }
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { Reporter } from "@empiricalrun/reporter";
2
3
  type ReporterConfigType = {
3
4
  testSessionId: number;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/reporter/index.ts"],"names":[],"mappings":"AACA,OAAO,EAA4B,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAa5E,KAAK,kBAAkB,GAAG;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAKF,wBAAgB,WAAW,IAAI,QAAQ,GAAG,SAAS,CAUlD;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAGlE;AAED,qBAAa,sBAAsB;;IAE3B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,eAAe,CAAC,EACpB,eAAe,EACf,QAAQ,GACT,EAAE;QACD,eAAe,EAAE,MAAM,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;KAClB;IA4CK,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B9C,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAUlD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/reporter/index.ts"],"names":[],"mappings":";AACA,OAAO,EAA4B,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAa5E,KAAK,kBAAkB,GAAG;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAKF,wBAAgB,WAAW,IAAI,QAAQ,GAAG,SAAS,CAUlD;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAGlE;AAED,qBAAa,sBAAsB;;IAE3B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,eAAe,CAAC,EACpB,eAAe,EACf,QAAQ,GACT,EAAE;QACD,eAAe,EAAE,MAAM,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;KAClB;IA4CK,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B9C,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAUlD"}
@@ -0,0 +1,10 @@
1
+ import { Build } from "../types";
2
+ /**
3
+ * method to download the build from the URL provided in the build object
4
+ * this is only used in cases like chrome extension as of yet.
5
+ * @export
6
+ * @param {Build} build
7
+ * @return {*} {Promise<void>}
8
+ */
9
+ export declare function downloadBuild(build: Build): Promise<void>;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test-build/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAOjC;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAY/D"}
@@ -0,0 +1,30 @@
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.downloadBuild = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const logger_1 = require("../bin/logger");
9
+ const exec_1 = require("../utils/exec");
10
+ /**
11
+ * method to download the build from the URL provided in the build object
12
+ * this is only used in cases like chrome extension as of yet.
13
+ * @export
14
+ * @param {Build} build
15
+ * @return {*} {Promise<void>}
16
+ */
17
+ async function downloadBuild(build) {
18
+ const logger = new logger_1.CustomLogger({ useReporter: false });
19
+ const packageJSONPath = "package.json";
20
+ const packageJsonStr = await fs_extra_1.default.readFile(packageJSONPath, "utf-8");
21
+ const packageJSONData = JSON.parse(packageJsonStr);
22
+ const buildDownloadScript = packageJSONData.scripts["download"];
23
+ if (buildDownloadScript && build?.url) {
24
+ logger.log(`Downloading build from ${build.url}`);
25
+ await (0, exec_1.cmd)(`npm run download ${build.url}`.split(" "), {
26
+ env: { ...Object(process.env) },
27
+ });
28
+ }
29
+ }
30
+ exports.downloadBuild = downloadBuild;
@@ -19,9 +19,13 @@ export type TestGenConfigOptions = {
19
19
  environment: "development" | "production";
20
20
  };
21
21
  };
22
+ export type Build = {
23
+ url?: string;
24
+ };
22
25
  export type TestGenConfig = {
23
26
  specPath: string;
24
27
  testCase: TestCase;
28
+ build?: Build;
25
29
  options?: TestGenConfigOptions;
26
30
  };
27
31
  export type TestCase = {
@@ -29,7 +33,9 @@ export type TestCase = {
29
33
  steps: string[];
30
34
  group: string;
31
35
  };
32
- export type PlaywrightActionGenerator = (page: Page) => Action;
36
+ export type PlaywrightActionGenerator = (page: Page, options?: {
37
+ stateVariables: Record<string, any>;
38
+ }) => Action;
33
39
  export type ActionSchema = OpenAI.Chat.Completions.ChatCompletionTool;
34
40
  export type Action = {
35
41
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,KAAK,EAAE,QAAQ,CAAC;IAChB,aAAa,EAAE,WAAW,CAAC;IAC3B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,EAAE;QACR,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,aAAa,GAAG,YAAY,CAAC;KAC3C,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC;AAE/D,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC;AAEtE,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC5E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;CAC/E,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,KAAK,EAAE,QAAQ,CAAC;IAChB,aAAa,EAAE,WAAW,CAAC;IAC3B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,EAAE;QACR,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,aAAa,GAAG,YAAY,CAAC;KAC3C,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,CACtC,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE;IACR,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACrC,KACE,MAAM,CAAC;AAEZ,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC;AAEtE,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC5E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;CAC/E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-gen",
3
- "version": "0.27.9",
3
+ "version": "0.28.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
+ "@google/generative-ai": "^0.15.0",
21
22
  "@playwright/test": "^1.44.1",
22
23
  "@types/sanitize-html": "^2.11.0",
23
24
  "commander": "^12.1.0",
@@ -29,6 +30,7 @@
29
30
  "google-auth-library": "^9.10.0",
30
31
  "google-spreadsheet": "^4.1.2",
31
32
  "ignore": "^5.3.1",
33
+ "lodash.isequal": "^4.5.0",
32
34
  "md5": "^2.3.0",
33
35
  "mime": "^4.0.4",
34
36
  "minimatch": "^10.0.1",
@@ -36,19 +38,21 @@
36
38
  "picocolors": "^1.0.1",
37
39
  "playwright": "^1.44.1",
38
40
  "prettier": "^3.2.5",
41
+ "remove-markdown": "^0.5.5",
39
42
  "sanitize-html": "^2.13.0",
40
43
  "slugify": "^1.6.6",
41
44
  "ts-morph": "^23.0.0",
42
45
  "tsx": "^4.16.2",
43
46
  "typescript": "^5.3.3",
44
47
  "@empiricalrun/llm": "^0.9.2",
45
- "@empiricalrun/r2-uploader": "^0.2.0",
46
- "@empiricalrun/reporter": "^0.18.0"
48
+ "@empiricalrun/r2-uploader": "^0.3.0",
49
+ "@empiricalrun/reporter": "^0.18.2"
47
50
  },
48
51
  "devDependencies": {
49
52
  "@types/detect-port": "^1.3.5",
50
53
  "@types/express": "^4.17.21",
51
54
  "@types/fs-extra": "^11.0.4",
55
+ "@types/lodash.isequal": "^4.5.8",
52
56
  "@types/md5": "^2.3.5"
53
57
  },
54
58
  "scripts": {
@@ -57,6 +61,7 @@
57
61
  "clean": "tsc --build --clean",
58
62
  "lint": "eslint .",
59
63
  "test": "vitest run",
60
- "test:watch": "vitest"
64
+ "test:watch": "vitest",
65
+ "test:watch-files": "vitest $0 --watch"
61
66
  }
62
67
  }