@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 +13 -0
- package/dist/agent/master/browser-tests/index.spec.js +3 -4
- package/dist/agent/master/icon-descriptor/index.d.ts +1 -1
- package/dist/agent/master/icon-descriptor/index.d.ts.map +1 -1
- package/dist/agent/master/icon-descriptor/index.js +48 -9
- package/dist/browser-injected-scripts/annotate-elements.js +2 -5
- package/dist/browser-injected-scripts/annotate-elements.spec.js +1 -1
- package/dist/browser-injected-scripts/annotate-elements.spec.ts +25 -26
- package/package.json +3 -3
- package/playwright.config.ts +10 -0
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
|
|
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(.+)
|
|
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
|
-
|
|
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;
|
|
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
|
|
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
|
|
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
|
-
|
|
19
|
-
if (
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
//
|
|
265
|
+
// Some special handling for Tailwind CSS and Vue
|
|
266
266
|
if (!isClickable) {
|
|
267
|
-
|
|
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
|
|
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
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
await page.addScriptTag({
|
|
313
|
+
path: path.resolve(__dirname, "./annotate-elements.js"),
|
|
314
|
+
});
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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.
|
|
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/
|
|
76
|
+
"@empiricalrun/llm": "^0.9.35",
|
|
77
77
|
"@empiricalrun/r2-uploader": "^0.3.8",
|
|
78
|
-
"@empiricalrun/
|
|
78
|
+
"@empiricalrun/reporter": "^0.23.1"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@playwright/test": "1.47.1",
|
package/playwright.config.ts
CHANGED
|
@@ -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
|
});
|