@empiricalrun/test-gen 0.46.5 → 0.46.7

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,18 @@
1
1
  # @empiricalrun/test-gen
2
2
 
3
+ ## 0.46.7
4
+
5
+ ### Patch Changes
6
+
7
+ - f2e9d28: fix: button in class name does not mean clickable
8
+
9
+ ## 0.46.6
10
+
11
+ ### Patch Changes
12
+
13
+ - 8065639: fix: check for svg in descendants of provided html element and not direct children
14
+ - f5f12f5: feat: Move icons knowledge to .empiricalrun directory
15
+
3
16
  ## 0.46.5
4
17
 
5
18
  ### Patch Changes
@@ -71,7 +71,7 @@ test("agent can click icons accurately", async ({ page, server }) => {
71
71
  (0, test_1.expect)(response.code).toContain("page.locator");
72
72
  (0, test_1.expect)(response.code).toContain("click()");
73
73
  // Validate icons registry
74
- const iconsRegistryFile = path_1.default.join(process.cwd(), "icons.json");
74
+ const iconsRegistryFile = path_1.default.join(process.cwd(), ".empiricalrun", "icons.json");
75
75
  const icons = JSON.parse(fs_1.default.readFileSync(iconsRegistryFile, "utf-8"));
76
76
  (0, test_1.expect)(icons.length).toBeGreaterThan(0);
77
77
  // Commenting out this check since with parallel test executions,
@@ -103,7 +103,7 @@ test("fill action with multiple pages", async ({ context }) => {
103
103
  const page1 = await context.newPage();
104
104
  const page2 = await context.newPage();
105
105
  const response = await (0, run_1.createTestUsingMasterAgent)({
106
- task: `goto empirical.run on page1 and goto google.com in page2. Enter text empirical on page2 and click on search.`,
106
+ task: `goto empirical.run on page1 and goto github.com/search in page2. Enter text empirical on page2 and click on search.`,
107
107
  page: page2,
108
108
  options: {},
109
109
  scopeVars: {
@@ -116,7 +116,6 @@ test("fill action with multiple pages", async ({ context }) => {
116
116
  console.log(response.code);
117
117
  const lines = response.code.split("\n");
118
118
  (0, test_1.expect)(lines.find((l) => l.match(/^await page1\.goto(.+)empirical\.run/))).toBeTruthy();
119
- (0, test_1.expect)(lines.find((l) => l.match(/^await page2\.goto(.+)google\.com/))).toBeTruthy();
119
+ (0, test_1.expect)(lines.find((l) => l.match(/^await page2\.goto(.+)github\.com\/search/))).toBeTruthy();
120
120
  (0, test_1.expect)(lines.find((l) => l.match(/^await page2(.+)fill(.+)empirical/))).toBeTruthy();
121
- (0, test_1.expect)(lines.find((l) => l.match(/^await page2(.+)click/))).toBeTruthy();
122
121
  });
@@ -11,7 +11,7 @@ export declare function generateKey(htmlString: string): string;
11
11
  export declare function reverseKey(hash: string): string;
12
12
  export declare function createNodeFromHTML(htmlString: string): {
13
13
  node: Element | null;
14
- children: Element[];
14
+ descendant: Element[];
15
15
  };
16
16
  export declare function getIconDescription({ htmlString, pageHtml, trace, }: {
17
17
  htmlString: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/agent/master/icon-descriptor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAWrD,KAAK,aAAa,GAAG;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;CACjB,CAAC;AAEF,wBAAgB,kBAAkB,IAAI,KAAK,CAAC,aAAa,CAAC,CAczD;AAED,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM;;;EAUpD;AAqKD,wBAAsB,kBAAkB,CAAC,EACvC,UAAU,EACV,QAAQ,EACR,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkC9B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/agent/master/icon-descriptor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAarD,KAAK,aAAa,GAAG;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;CACjB,CAAC;AAEF,wBAAgB,kBAAkB,IAAI,KAAK,CAAC,aAAa,CAAC,CAoDzD;AAED,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM;;;EAUpD;AAqKD,wBAAsB,kBAAkB,CAAC,EACvC,UAAU,EACV,QAAQ,EACR,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkC9B"}
@@ -10,18 +10,49 @@ const jsdom_1 = require("jsdom");
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const constants_1 = require("../../../constants");
12
12
  const normalize_svg_1 = require("./normalize-svg");
13
- const ICONS_KNOWLEDGE_PATH = path_1.default.join(process.cwd(), "icons.json");
13
+ const OLD_ICONS_KNOWLEDGE_PATH = path_1.default.join(process.cwd(), "icons.json");
14
+ const EMPIRICAL_RUN_DIR = path_1.default.join(process.cwd(), ".empiricalrun");
15
+ const ICONS_KNOWLEDGE_PATH = path_1.default.join(EMPIRICAL_RUN_DIR, "icons.json");
14
16
  function loadIconsKnowledge() {
15
- if (!fs_1.default.existsSync(ICONS_KNOWLEDGE_PATH)) {
17
+ // Check if file exists in the new location
18
+ if (fs_1.default.existsSync(ICONS_KNOWLEDGE_PATH)) {
19
+ const raw = fs_1.default.readFileSync(ICONS_KNOWLEDGE_PATH, "utf-8");
20
+ if (raw) {
21
+ try {
22
+ return JSON.parse(raw);
23
+ }
24
+ catch (err) {
25
+ console.log("error parsing iconsKnowledge json");
26
+ return [];
27
+ }
28
+ }
16
29
  return [];
17
30
  }
18
- const raw = fs_1.default.readFileSync(ICONS_KNOWLEDGE_PATH, "utf-8");
19
- if (raw) {
31
+ // Check if file exists in the old location and move it if it does
32
+ if (fs_1.default.existsSync(OLD_ICONS_KNOWLEDGE_PATH)) {
20
33
  try {
21
- return JSON.parse(raw);
34
+ const raw = fs_1.default.readFileSync(OLD_ICONS_KNOWLEDGE_PATH, "utf-8");
35
+ let iconData = [];
36
+ if (raw) {
37
+ try {
38
+ iconData = JSON.parse(raw);
39
+ }
40
+ catch (err) {
41
+ console.log("error parsing iconsKnowledge json from old location");
42
+ }
43
+ }
44
+ // Create the directory and move the file
45
+ if (!fs_1.default.existsSync(EMPIRICAL_RUN_DIR)) {
46
+ fs_1.default.mkdirSync(EMPIRICAL_RUN_DIR, { recursive: true });
47
+ }
48
+ // Save to new location
49
+ fs_1.default.writeFileSync(ICONS_KNOWLEDGE_PATH, JSON.stringify(iconData, null, 2), "utf-8");
50
+ // Remove the old file
51
+ fs_1.default.unlinkSync(OLD_ICONS_KNOWLEDGE_PATH);
52
+ return iconData;
22
53
  }
23
54
  catch (err) {
24
- console.log("error parsing iconsKnowledge json");
55
+ console.log("Error moving icons knowledge file:", err);
25
56
  return [];
26
57
  }
27
58
  }
@@ -29,8 +60,16 @@ function loadIconsKnowledge() {
29
60
  }
30
61
  exports.loadIconsKnowledge = loadIconsKnowledge;
31
62
  async function saveIconsKnowledge(iconsData) {
63
+ // Ensure the directory exists
64
+ if (!fs_1.default.existsSync(EMPIRICAL_RUN_DIR)) {
65
+ fs_1.default.mkdirSync(EMPIRICAL_RUN_DIR, { recursive: true });
66
+ }
32
67
  const content = JSON.stringify(iconsData, null, 2);
33
68
  fs_1.default.writeFileSync(ICONS_KNOWLEDGE_PATH, content, "utf-8");
69
+ // If old file exists, remove it
70
+ if (fs_1.default.existsSync(OLD_ICONS_KNOWLEDGE_PATH)) {
71
+ fs_1.default.unlinkSync(OLD_ICONS_KNOWLEDGE_PATH);
72
+ }
34
73
  }
35
74
  exports.saveIconsKnowledge = saveIconsKnowledge;
36
75
  function generateKey(htmlString) {
@@ -51,7 +90,7 @@ function createNodeFromHTML(htmlString) {
51
90
  const node = document.body.firstElementChild;
52
91
  return {
53
92
  node, // Return the first node
54
- children: node?.children ? Array.from(node.children) : [], // Convert HTMLCollection to array
93
+ descendant: node ? Array.from(node.querySelectorAll("*")) : [], // Convert HTMLCollection to array
55
94
  };
56
95
  }
57
96
  exports.createNodeFromHTML = createNodeFromHTML;
@@ -95,7 +134,7 @@ function processSvgWithUseElements(svgElement, document) {
95
134
  function getHtmlForDescription(elementHtml, pageHtml) {
96
135
  const dom = new jsdom_1.JSDOM(pageHtml);
97
136
  const page = dom.window.document;
98
- const { node, children } = createNodeFromHTML(elementHtml);
137
+ const { node, descendant } = createNodeFromHTML(elementHtml);
99
138
  if (!node) {
100
139
  return undefined;
101
140
  }
@@ -103,7 +142,7 @@ function getHtmlForDescription(elementHtml, pageHtml) {
103
142
  processSvgWithUseElements(node, page);
104
143
  return node.outerHTML;
105
144
  }
106
- const svgChildren = children?.filter((child) => child?.tagName?.toLowerCase() === "svg");
145
+ const svgChildren = descendant?.filter((child) => child?.tagName?.toLowerCase() === "svg");
107
146
  if (svgChildren && svgChildren.length > 0) {
108
147
  for (const svgChild of svgChildren) {
109
148
  processSvgWithUseElements(svgChild, page);
@@ -262,12 +262,9 @@ function annotateElementsWithPreference({
262
262
  }
263
263
  }
264
264
 
265
- // Check for class names containing 'button' as a possible click target
265
+ // Some special handling for Tailwind CSS and Vue
266
266
  if (!isClickable) {
267
- const className = element.getAttribute("class");
268
- if (className && className.toLowerCase().includes("button")) {
269
- isClickable = true;
270
- } else if (element.classList.contains("cursor-pointer")) {
267
+ if (element.classList.contains("cursor-pointer")) {
271
268
  isClickable = true;
272
269
  } else if (element.classList.contains("v-list-item--link")) {
273
270
  // vue specific click handling
@@ -183,7 +183,7 @@ const action_tool_calls_1 = require("../agent/master/action-tool-calls");
183
183
  test_1.test.expect(annotations.length).toBe(1);
184
184
  test_1.test.expect(annotations[0]?.tagName).toBe("DIV");
185
185
  });
186
- test_1.test.fail("should not annotate children that don't have onClick handler", async ({ page }) => {
186
+ (0, test_1.test)("should not annotate children that don't have onClick handler", async ({ page, }) => {
187
187
  await page.setContent(`<div class="button-outer" onclick="alert('clicked')">
188
188
  <div class="button-inner">Click me</div>
189
189
  </div>`);
@@ -300,34 +300,33 @@ test("should only annotate given text on quizziz page", async ({ page }) => {
300
300
  test.expect(annotations[0]?.tagName).toBe("DIV");
301
301
  });
302
302
 
303
- test.fail(
304
- "should not annotate children that don't have onClick handler",
305
- async ({ page }) => {
306
- await page.setContent(
307
- `<div class="button-outer" onclick="alert('clicked')">
303
+ test("should not annotate children that don't have onClick handler", async ({
304
+ page,
305
+ }) => {
306
+ await page.setContent(
307
+ `<div class="button-outer" onclick="alert('clicked')">
308
308
  <div class="button-inner">Click me</div>
309
309
  </div>`,
310
- );
310
+ );
311
311
 
312
- await page.addScriptTag({
313
- path: path.resolve(__dirname, "./annotate-elements.js"),
314
- });
312
+ await page.addScriptTag({
313
+ path: path.resolve(__dirname, "./annotate-elements.js"),
314
+ });
315
315
 
316
- const annotations = await page.evaluate(() => {
317
- // eslint-disable-next-line no-undef
318
- const { annotations } = annotateElementsWithPreference();
319
-
320
- return Object.entries(annotations).map(([hint, config]) => ({
321
- hint,
322
- innerText: config.node.innerText?.toLowerCase().trim(),
323
- tagName: config.node.tagName,
324
- testId: config.node.getAttribute("data-testid"),
325
- className: config.node.className,
326
- }));
327
- });
316
+ const annotations = await page.evaluate(() => {
317
+ // eslint-disable-next-line no-undef
318
+ const { annotations } = annotateElementsWithPreference();
328
319
 
329
- console.log(annotations);
330
- test.expect(annotations.length).toBe(1);
331
- test.expect(annotations[0].className).toBe("button-outer");
332
- },
333
- );
320
+ return Object.entries(annotations).map(([hint, config]) => ({
321
+ hint,
322
+ innerText: config.node.innerText?.toLowerCase().trim(),
323
+ tagName: config.node.tagName,
324
+ testId: config.node.getAttribute("data-testid"),
325
+ className: config.node.className,
326
+ }));
327
+ });
328
+
329
+ console.log(annotations);
330
+ test.expect(annotations.length).toBe(1);
331
+ test.expect(annotations[0].className).toBe("button-outer");
332
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-gen",
3
- "version": "0.46.5",
3
+ "version": "0.46.7",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -73,9 +73,9 @@
73
73
  "ts-morph": "^23.0.0",
74
74
  "tsx": "^4.16.2",
75
75
  "typescript": "^5.3.3",
76
- "@empiricalrun/reporter": "^0.23.1",
76
+ "@empiricalrun/llm": "^0.9.35",
77
77
  "@empiricalrun/r2-uploader": "^0.3.8",
78
- "@empiricalrun/llm": "^0.9.35"
78
+ "@empiricalrun/reporter": "^0.23.1"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@playwright/test": "1.47.1",
@@ -11,4 +11,14 @@ export default defineConfig({
11
11
  timeout: 240_000,
12
12
  fullyParallel: true,
13
13
  workers: "50%",
14
+ use: {
15
+ launchOptions: {
16
+ args: [
17
+ "--disable-web-security",
18
+ "--disable-site-isolation-trials",
19
+ "--disable-features=IsolateOrigins,site-per-process",
20
+ ],
21
+ },
22
+ bypassCSP: true,
23
+ },
14
24
  });