@midscene/web 0.1.4 → 0.2.0
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/README.md +7 -0
- package/dist/es/index.js +306 -170
- package/dist/es/playwright-report.js +22 -15
- package/dist/lib/index.js +320 -188
- package/dist/lib/playwright-report.js +31 -28
- package/dist/script/htmlElement.js +30 -18
- package/dist/script/types/htmlElement.d.ts +5 -5
- package/dist/types/index.d.ts +18 -12
- package/dist/types/playwright-report.d.ts +2 -2
- package/dist/visualizer-report/index.html +1 -1
- package/dist/visualizer-report/static/css/index.eccd04e1.css +1 -0
- package/dist/visualizer-report/static/css/index.eccd04e1.css.map +1 -0
- package/dist/visualizer-report/static/js/160.5ac2287a.js +6 -0
- package/dist/visualizer-report/static/js/160.5ac2287a.js.map +1 -0
- package/dist/visualizer-report/static/js/index.e71be3a2.js +1 -0
- package/dist/visualizer-report/static/js/index.e71be3a2.js.map +1 -0
- package/dist/visualizer-report/static/js/lib-antd.583c9200.js +141 -0
- package/dist/visualizer-report/static/js/lib-antd.583c9200.js.map +1 -0
- package/package.json +7 -6
- package/dist/visualizer-report/static/css/index.c7751597.css +0 -1
- package/dist/visualizer-report/static/css/index.c7751597.css.map +0 -1
- package/dist/visualizer-report/static/js/915.d3f73af1.js +0 -6
- package/dist/visualizer-report/static/js/915.d3f73af1.js.map +0 -1
- package/dist/visualizer-report/static/js/index.ae9a86c5.js +0 -1
- package/dist/visualizer-report/static/js/index.ae9a86c5.js.map +0 -1
- package/dist/visualizer-report/static/js/lib-antd.55d65804.js +0 -189
- package/dist/visualizer-report/static/js/lib-antd.55d65804.js.map +0 -1
- /package/dist/visualizer-report/static/js/{915.d3f73af1.js.LICENSE.txt → 160.5ac2287a.js.LICENSE.txt} +0 -0
package/dist/lib/index.js
CHANGED
|
@@ -53,17 +53,76 @@ __export(src_exports, {
|
|
|
53
53
|
module.exports = __toCommonJS(src_exports);
|
|
54
54
|
|
|
55
55
|
// src/playwright/index.ts
|
|
56
|
-
var
|
|
56
|
+
var import_node_crypto = require("crypto");
|
|
57
57
|
|
|
58
|
-
// src/
|
|
59
|
-
var
|
|
60
|
-
|
|
58
|
+
// src/common/agent.ts
|
|
59
|
+
var import_utils4 = require("@midscene/core/utils");
|
|
60
|
+
|
|
61
|
+
// src/common/tasks.ts
|
|
62
|
+
var import_node_assert2 = __toESM(require("assert"));
|
|
63
|
+
var import_core = __toESM(require("@midscene/core"));
|
|
64
|
+
var import_image2 = require("@midscene/core/image");
|
|
61
65
|
var import_utils2 = require("@midscene/core/utils");
|
|
62
66
|
|
|
67
|
+
// src/common/task-cache.ts
|
|
68
|
+
var TaskCache = class {
|
|
69
|
+
constructor(opts) {
|
|
70
|
+
this.cache = opts == null ? void 0 : opts.cache;
|
|
71
|
+
this.newCache = {
|
|
72
|
+
aiTasks: []
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
readCache(pageContext, type, userPrompt) {
|
|
76
|
+
var _a;
|
|
77
|
+
if (this.cache) {
|
|
78
|
+
const { aiTasks } = this.cache;
|
|
79
|
+
const index = aiTasks.findIndex((item) => item.prompt === userPrompt);
|
|
80
|
+
if (index === -1) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const taskRes = aiTasks.splice(index, 1)[0];
|
|
84
|
+
if ((taskRes == null ? void 0 : taskRes.type) === "locate" && !((_a = taskRes.response) == null ? void 0 : _a.elements.every((element) => {
|
|
85
|
+
const findIndex = pageContext.content.findIndex(
|
|
86
|
+
(contentElement) => contentElement.id === element.id
|
|
87
|
+
);
|
|
88
|
+
if (findIndex === -1) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}))) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (taskRes && taskRes.type === type && taskRes.prompt === userPrompt && this.pageContextEqual(taskRes.pageContext, pageContext)) {
|
|
96
|
+
return taskRes.response;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
saveCache(cache) {
|
|
102
|
+
var _a;
|
|
103
|
+
if (cache) {
|
|
104
|
+
(_a = this.newCache) == null ? void 0 : _a.aiTasks.push(cache);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
pageContextEqual(taskPageContext, pageContext) {
|
|
108
|
+
return taskPageContext.size.width === pageContext.size.width && taskPageContext.size.height === pageContext.size.height;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Generate task cache data.
|
|
112
|
+
* This method is mainly used to create or obtain some cached data for tasks, and it returns a new cache object.
|
|
113
|
+
* In the cache object, it may contain task-related information, states, or other necessary data.
|
|
114
|
+
* It is assumed that the `newCache` property already exists in the current class or object and is a data structure used to store task cache.
|
|
115
|
+
* @returns {Object} Returns a new cache object, which may include task cache data.
|
|
116
|
+
*/
|
|
117
|
+
generateTaskCache() {
|
|
118
|
+
return this.newCache;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
63
122
|
// src/common/utils.ts
|
|
64
|
-
var
|
|
65
|
-
var
|
|
66
|
-
var
|
|
123
|
+
var import_node_assert = __toESM(require("assert"));
|
|
124
|
+
var import_node_fs = __toESM(require("fs"));
|
|
125
|
+
var import_node_path = __toESM(require("path"));
|
|
67
126
|
var import_image = require("@midscene/core/image");
|
|
68
127
|
var import_utils = require("@midscene/core/utils");
|
|
69
128
|
|
|
@@ -79,7 +138,10 @@ var WebElementInfo = class {
|
|
|
79
138
|
}) {
|
|
80
139
|
this.content = content;
|
|
81
140
|
this.rect = rect;
|
|
82
|
-
this.center = [
|
|
141
|
+
this.center = [
|
|
142
|
+
Math.floor(rect.left + rect.width / 2),
|
|
143
|
+
Math.floor(rect.top + rect.height / 2)
|
|
144
|
+
];
|
|
83
145
|
this.page = page;
|
|
84
146
|
this.locator = locator;
|
|
85
147
|
this.id = id;
|
|
@@ -89,14 +151,18 @@ var WebElementInfo = class {
|
|
|
89
151
|
|
|
90
152
|
// src/common/utils.ts
|
|
91
153
|
async function parseContextFromWebPage(page, _opt) {
|
|
92
|
-
(0,
|
|
154
|
+
(0, import_node_assert.default)(page, "page is required");
|
|
93
155
|
const url = page.url();
|
|
94
156
|
const file = (0, import_utils.getTmpFile)("jpeg");
|
|
95
157
|
await page.screenshot({ path: file, type: "jpeg", quality: 75 });
|
|
96
|
-
const screenshotBuffer = (0,
|
|
158
|
+
const screenshotBuffer = (0, import_node_fs.readFileSync)(file);
|
|
97
159
|
const screenshotBase64 = (0, import_image.base64Encoded)(file);
|
|
98
160
|
const captureElementSnapshot = await getElementInfosFromPage(page);
|
|
99
|
-
const elementsInfo = await alignElements(
|
|
161
|
+
const elementsInfo = await alignElements(
|
|
162
|
+
screenshotBuffer,
|
|
163
|
+
captureElementSnapshot,
|
|
164
|
+
page
|
|
165
|
+
);
|
|
100
166
|
const size = await (0, import_image.imageInfoOfBase64)(screenshotBase64);
|
|
101
167
|
return {
|
|
102
168
|
content: elementsInfo,
|
|
@@ -107,9 +173,9 @@ async function parseContextFromWebPage(page, _opt) {
|
|
|
107
173
|
}
|
|
108
174
|
async function getElementInfosFromPage(page) {
|
|
109
175
|
const pathDir = findNearestPackageJson(__dirname);
|
|
110
|
-
(0,
|
|
111
|
-
const scriptPath =
|
|
112
|
-
const elementInfosScriptContent = (0,
|
|
176
|
+
(0, import_node_assert.default)(pathDir, `can't find pathDir, with ${__dirname}`);
|
|
177
|
+
const scriptPath = import_node_path.default.join(pathDir, "./dist/script/htmlElement.js");
|
|
178
|
+
const elementInfosScriptContent = (0, import_node_fs.readFileSync)(scriptPath, "utf-8");
|
|
113
179
|
const extraReturnLogic = `${elementInfosScriptContent}midscene_element_inspector.extractTextWithPositionDFS()`;
|
|
114
180
|
const captureElementSnapshot = await page.evaluate(extraReturnLogic);
|
|
115
181
|
return captureElementSnapshot;
|
|
@@ -133,135 +199,17 @@ async function alignElements(screenshotBuffer, elements, page) {
|
|
|
133
199
|
return textsAligned;
|
|
134
200
|
}
|
|
135
201
|
function findNearestPackageJson(dir) {
|
|
136
|
-
const packageJsonPath =
|
|
137
|
-
if (
|
|
202
|
+
const packageJsonPath = import_node_path.default.join(dir, "package.json");
|
|
203
|
+
if (import_node_fs.default.existsSync(packageJsonPath)) {
|
|
138
204
|
return dir;
|
|
139
205
|
}
|
|
140
|
-
const parentDir =
|
|
206
|
+
const parentDir = import_node_path.default.dirname(dir);
|
|
141
207
|
if (parentDir === dir) {
|
|
142
208
|
return null;
|
|
143
209
|
}
|
|
144
210
|
return findNearestPackageJson(parentDir);
|
|
145
211
|
}
|
|
146
212
|
|
|
147
|
-
// src/playwright/cache.ts
|
|
148
|
-
function writeTestCache(taskFile, taskTitle, taskCacheJson) {
|
|
149
|
-
const packageJson = getPkgInfo();
|
|
150
|
-
(0, import_utils2.writeDumpFile)({
|
|
151
|
-
fileName: `${taskFile}(${taskTitle})`,
|
|
152
|
-
fileExt: "json",
|
|
153
|
-
fileContent: JSON.stringify(
|
|
154
|
-
__spreadValues({
|
|
155
|
-
pkgName: packageJson.name,
|
|
156
|
-
pkgVersion: packageJson.version,
|
|
157
|
-
taskFile,
|
|
158
|
-
taskTitle
|
|
159
|
-
}, taskCacheJson),
|
|
160
|
-
null,
|
|
161
|
-
2
|
|
162
|
-
),
|
|
163
|
-
type: "cache"
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
function readTestCache(taskFile, taskTitle) {
|
|
167
|
-
const cacheFile = (0, import_path2.join)((0, import_utils2.getDumpDirPath)("cache"), `${taskFile}(${taskTitle}).json`);
|
|
168
|
-
const pkgInfo = getPkgInfo();
|
|
169
|
-
if (process.env.MIDSCENE_CACHE === "true" && import_fs2.default.existsSync(cacheFile)) {
|
|
170
|
-
try {
|
|
171
|
-
const data = import_fs2.default.readFileSync(cacheFile, "utf8");
|
|
172
|
-
const jsonData = JSON.parse(data);
|
|
173
|
-
if (jsonData.pkgName !== pkgInfo.name || jsonData.pkgVersion !== pkgInfo.version) {
|
|
174
|
-
return void 0;
|
|
175
|
-
}
|
|
176
|
-
return jsonData;
|
|
177
|
-
} catch (err) {
|
|
178
|
-
return void 0;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return void 0;
|
|
182
|
-
}
|
|
183
|
-
function getPkgInfo() {
|
|
184
|
-
const packageJsonDir = findNearestPackageJson(__dirname);
|
|
185
|
-
if (!packageJsonDir) {
|
|
186
|
-
console.error("Cannot find package.json directory: ", __dirname);
|
|
187
|
-
return {
|
|
188
|
-
name: "@midscene/web",
|
|
189
|
-
version: "0.0.0"
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
const packageJsonPath = import_path2.default.join(packageJsonDir, "package.json");
|
|
193
|
-
const data = import_fs2.default.readFileSync(packageJsonPath, "utf8");
|
|
194
|
-
const packageJson = JSON.parse(data);
|
|
195
|
-
return {
|
|
196
|
-
name: packageJson.name,
|
|
197
|
-
version: packageJson.version
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// src/common/agent.ts
|
|
202
|
-
var import_utils6 = require("@midscene/core/utils");
|
|
203
|
-
|
|
204
|
-
// src/common/tasks.ts
|
|
205
|
-
var import_assert2 = __toESM(require("assert"));
|
|
206
|
-
var import_core = __toESM(require("@midscene/core"));
|
|
207
|
-
var import_utils4 = require("@midscene/core/utils");
|
|
208
|
-
var import_image2 = require("@midscene/core/image");
|
|
209
|
-
|
|
210
|
-
// src/common/task-cache.ts
|
|
211
|
-
var TaskCache = class {
|
|
212
|
-
constructor(opts) {
|
|
213
|
-
this.cache = opts == null ? void 0 : opts.cache;
|
|
214
|
-
this.newCache = {
|
|
215
|
-
aiTasks: []
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
readCache(pageContext, type, userPrompt) {
|
|
219
|
-
var _a;
|
|
220
|
-
if (this.cache) {
|
|
221
|
-
const { aiTasks } = this.cache;
|
|
222
|
-
const index = aiTasks.findIndex((item) => item.prompt === userPrompt);
|
|
223
|
-
if (index === -1) {
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
const taskRes = aiTasks.splice(index, 1)[0];
|
|
227
|
-
if ((taskRes == null ? void 0 : taskRes.type) === "locate" && !((_a = taskRes.response) == null ? void 0 : _a.elements.every((element) => {
|
|
228
|
-
const findIndex = pageContext.content.findIndex(
|
|
229
|
-
(contentElement) => contentElement.id === element.id
|
|
230
|
-
);
|
|
231
|
-
if (findIndex === -1) {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
return true;
|
|
235
|
-
}))) {
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
if (taskRes && taskRes.type === type && taskRes.prompt === userPrompt && this.pageContextEqual(taskRes.pageContext, pageContext)) {
|
|
239
|
-
return taskRes.response;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
saveCache(cache) {
|
|
245
|
-
var _a;
|
|
246
|
-
if (cache) {
|
|
247
|
-
(_a = this.newCache) == null ? void 0 : _a.aiTasks.push(cache);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
pageContextEqual(taskPageContext, pageContext) {
|
|
251
|
-
return taskPageContext.size.width === pageContext.size.width && taskPageContext.size.height === pageContext.size.height;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Generate task cache data.
|
|
255
|
-
* This method is mainly used to create or obtain some cached data for tasks, and it returns a new cache object.
|
|
256
|
-
* In the cache object, it may contain task-related information, states, or other necessary data.
|
|
257
|
-
* It is assumed that the `newCache` property already exists in the current class or object and is a data structure used to store task cache.
|
|
258
|
-
* @returns {Object} Returns a new cache object, which may include task cache data.
|
|
259
|
-
*/
|
|
260
|
-
generateTaskCache() {
|
|
261
|
-
return this.newCache;
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
|
|
265
213
|
// src/common/tasks.ts
|
|
266
214
|
var PageTaskExecutor = class {
|
|
267
215
|
constructor(page, opts) {
|
|
@@ -272,8 +220,8 @@ var PageTaskExecutor = class {
|
|
|
272
220
|
this.taskCache = new TaskCache(opts);
|
|
273
221
|
}
|
|
274
222
|
async recordScreenshot(timing) {
|
|
275
|
-
const file = (0,
|
|
276
|
-
await this.page.screenshot(__spreadProps(__spreadValues({},
|
|
223
|
+
const file = (0, import_utils2.getTmpFile)("jpeg");
|
|
224
|
+
await this.page.screenshot(__spreadProps(__spreadValues({}, import_utils2.commonScreenshotParam), {
|
|
277
225
|
path: file
|
|
278
226
|
}));
|
|
279
227
|
const item = {
|
|
@@ -294,7 +242,7 @@ var PageTaskExecutor = class {
|
|
|
294
242
|
recorder.push(shot);
|
|
295
243
|
const result = await taskApply.executor(param, context, ...args);
|
|
296
244
|
if (taskApply.type === "Action") {
|
|
297
|
-
await (0,
|
|
245
|
+
await (0, import_utils2.sleep)(1e3);
|
|
298
246
|
const shot2 = await this.recordScreenshot("after Action");
|
|
299
247
|
recorder.push(shot2);
|
|
300
248
|
}
|
|
@@ -317,7 +265,11 @@ var PageTaskExecutor = class {
|
|
|
317
265
|
};
|
|
318
266
|
this.insight.onceDumpUpdatedFn = dumpCollector;
|
|
319
267
|
const pageContext = await this.insight.contextRetrieverFn();
|
|
320
|
-
const locateCache = this.taskCache.readCache(
|
|
268
|
+
const locateCache = this.taskCache.readCache(
|
|
269
|
+
pageContext,
|
|
270
|
+
"locate",
|
|
271
|
+
param.prompt
|
|
272
|
+
);
|
|
321
273
|
let locateResult;
|
|
322
274
|
const callAI = this.insight.aiVendorFn;
|
|
323
275
|
const element = await this.insight.locate(param.prompt, {
|
|
@@ -330,7 +282,7 @@ var PageTaskExecutor = class {
|
|
|
330
282
|
return locateResult;
|
|
331
283
|
}
|
|
332
284
|
});
|
|
333
|
-
(0,
|
|
285
|
+
(0, import_node_assert2.default)(element, `Element not found: ${param.prompt}`);
|
|
334
286
|
if (locateResult) {
|
|
335
287
|
this.taskCache.saveCache({
|
|
336
288
|
type: "locate",
|
|
@@ -356,59 +308,100 @@ var PageTaskExecutor = class {
|
|
|
356
308
|
}
|
|
357
309
|
};
|
|
358
310
|
return taskFind;
|
|
359
|
-
}
|
|
311
|
+
}
|
|
312
|
+
if (plan2.type === "Assert") {
|
|
313
|
+
const assertPlan = plan2;
|
|
314
|
+
const taskAssert = {
|
|
315
|
+
type: "Insight",
|
|
316
|
+
subType: "Assert",
|
|
317
|
+
param: assertPlan.param,
|
|
318
|
+
executor: async () => {
|
|
319
|
+
let insightDump;
|
|
320
|
+
const dumpCollector = (dump) => {
|
|
321
|
+
insightDump = dump;
|
|
322
|
+
};
|
|
323
|
+
this.insight.onceDumpUpdatedFn = dumpCollector;
|
|
324
|
+
const assertion = await this.insight.assert(
|
|
325
|
+
assertPlan.param.assertion
|
|
326
|
+
);
|
|
327
|
+
return {
|
|
328
|
+
output: assertion,
|
|
329
|
+
log: {
|
|
330
|
+
dump: insightDump
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
return taskAssert;
|
|
336
|
+
}
|
|
337
|
+
if (plan2.type === "Input") {
|
|
360
338
|
const taskActionInput = {
|
|
361
339
|
type: "Action",
|
|
362
340
|
subType: "Input",
|
|
363
341
|
param: plan2.param,
|
|
364
342
|
executor: async (taskParam, { element }) => {
|
|
365
343
|
if (element) {
|
|
366
|
-
await this.page.mouse.click(
|
|
344
|
+
await this.page.mouse.click(
|
|
345
|
+
element.center[0],
|
|
346
|
+
element.center[1]
|
|
347
|
+
);
|
|
367
348
|
}
|
|
368
|
-
(0,
|
|
349
|
+
(0, import_node_assert2.default)(taskParam.value, "No value to input");
|
|
369
350
|
await this.page.keyboard.type(taskParam.value);
|
|
370
351
|
}
|
|
371
352
|
};
|
|
372
353
|
return taskActionInput;
|
|
373
|
-
}
|
|
354
|
+
}
|
|
355
|
+
if (plan2.type === "KeyboardPress") {
|
|
374
356
|
const taskActionKeyboardPress = {
|
|
375
357
|
type: "Action",
|
|
376
358
|
subType: "KeyboardPress",
|
|
377
359
|
param: plan2.param,
|
|
378
360
|
executor: async (taskParam) => {
|
|
379
|
-
(0,
|
|
361
|
+
(0, import_node_assert2.default)(taskParam.value, "No key to press");
|
|
380
362
|
await this.page.keyboard.press(taskParam.value);
|
|
381
363
|
}
|
|
382
364
|
};
|
|
383
365
|
return taskActionKeyboardPress;
|
|
384
|
-
}
|
|
366
|
+
}
|
|
367
|
+
if (plan2.type === "Tap") {
|
|
385
368
|
const taskActionTap = {
|
|
386
369
|
type: "Action",
|
|
387
370
|
subType: "Tap",
|
|
388
371
|
executor: async (param, { element }) => {
|
|
389
|
-
(0,
|
|
390
|
-
await this.page.mouse.click(
|
|
372
|
+
(0, import_node_assert2.default)(element, "Element not found, cannot tap");
|
|
373
|
+
await this.page.mouse.click(
|
|
374
|
+
element.center[0],
|
|
375
|
+
element.center[1]
|
|
376
|
+
);
|
|
391
377
|
}
|
|
392
378
|
};
|
|
393
379
|
return taskActionTap;
|
|
394
|
-
}
|
|
380
|
+
}
|
|
381
|
+
if (plan2.type === "Hover") {
|
|
395
382
|
const taskActionHover = {
|
|
396
383
|
type: "Action",
|
|
397
384
|
subType: "Hover",
|
|
398
385
|
executor: async (param, { element }) => {
|
|
399
|
-
(0,
|
|
400
|
-
await this.page.mouse.move(
|
|
386
|
+
(0, import_node_assert2.default)(element, "Element not found, cannot hover");
|
|
387
|
+
await this.page.mouse.move(
|
|
388
|
+
element.center[0],
|
|
389
|
+
element.center[1]
|
|
390
|
+
);
|
|
401
391
|
}
|
|
402
392
|
};
|
|
403
393
|
return taskActionHover;
|
|
404
|
-
}
|
|
394
|
+
}
|
|
395
|
+
if (plan2.type === "Scroll") {
|
|
405
396
|
const taskActionScroll = {
|
|
406
397
|
type: "Action",
|
|
407
398
|
subType: "Scroll",
|
|
408
399
|
param: plan2.param,
|
|
409
400
|
executor: async (taskParam) => {
|
|
410
401
|
const scrollToEventName = taskParam.scrollType;
|
|
411
|
-
const innerHeight = await this.page.evaluate(
|
|
402
|
+
const innerHeight = await this.page.evaluate(
|
|
403
|
+
() => window.innerHeight
|
|
404
|
+
);
|
|
412
405
|
switch (scrollToEventName) {
|
|
413
406
|
case "ScrollUntilTop":
|
|
414
407
|
await this.page.mouse.wheel(0, -9999999);
|
|
@@ -423,16 +416,19 @@ var PageTaskExecutor = class {
|
|
|
423
416
|
await this.page.mouse.wheel(0, innerHeight);
|
|
424
417
|
break;
|
|
425
418
|
default:
|
|
426
|
-
console.error(
|
|
419
|
+
console.error(
|
|
420
|
+
"Unknown scroll event type:",
|
|
421
|
+
scrollToEventName
|
|
422
|
+
);
|
|
427
423
|
}
|
|
428
424
|
}
|
|
429
425
|
};
|
|
430
426
|
return taskActionScroll;
|
|
431
|
-
}
|
|
427
|
+
}
|
|
428
|
+
if (plan2.type === "Error") {
|
|
432
429
|
throw new Error(`Got a task plan with type Error: ${plan2.thought}`);
|
|
433
|
-
} else {
|
|
434
|
-
throw new Error(`Unknown or Unsupported task type: ${plan2.type}`);
|
|
435
430
|
}
|
|
431
|
+
throw new Error(`Unknown or Unsupported task type: ${plan2.type}`);
|
|
436
432
|
}).map((task) => {
|
|
437
433
|
return this.wrapExecutorWithScreenshot(task);
|
|
438
434
|
});
|
|
@@ -450,7 +446,11 @@ var PageTaskExecutor = class {
|
|
|
450
446
|
executor: async (param) => {
|
|
451
447
|
const pageContext = await this.insight.contextRetrieverFn();
|
|
452
448
|
let planResult;
|
|
453
|
-
const planCache = this.taskCache.readCache(
|
|
449
|
+
const planCache = this.taskCache.readCache(
|
|
450
|
+
pageContext,
|
|
451
|
+
"plan",
|
|
452
|
+
userPrompt
|
|
453
|
+
);
|
|
454
454
|
if (planCache) {
|
|
455
455
|
planResult = planCache;
|
|
456
456
|
} else {
|
|
@@ -458,7 +458,7 @@ var PageTaskExecutor = class {
|
|
|
458
458
|
context: pageContext
|
|
459
459
|
});
|
|
460
460
|
}
|
|
461
|
-
(0,
|
|
461
|
+
(0, import_node_assert2.default)(planResult.plans.length > 0, "No plans found");
|
|
462
462
|
plans = planResult.plans;
|
|
463
463
|
this.taskCache.saveCache({
|
|
464
464
|
type: "plan",
|
|
@@ -472,7 +472,7 @@ var PageTaskExecutor = class {
|
|
|
472
472
|
return {
|
|
473
473
|
output: planResult,
|
|
474
474
|
cache: {
|
|
475
|
-
|
|
475
|
+
hit: Boolean(planCache)
|
|
476
476
|
}
|
|
477
477
|
};
|
|
478
478
|
}
|
|
@@ -485,7 +485,7 @@ var PageTaskExecutor = class {
|
|
|
485
485
|
await taskExecutor.append(executables);
|
|
486
486
|
await taskExecutor.flush();
|
|
487
487
|
this.executionDump = taskExecutor.dump();
|
|
488
|
-
(0,
|
|
488
|
+
(0, import_node_assert2.default)(
|
|
489
489
|
taskExecutor.status !== "error",
|
|
490
490
|
`failed to execute tasks: ${taskExecutor.status}, msg: ${taskExecutor.errorMsg || ""}`
|
|
491
491
|
);
|
|
@@ -530,6 +530,22 @@ var PageTaskExecutor = class {
|
|
|
530
530
|
}
|
|
531
531
|
return data;
|
|
532
532
|
}
|
|
533
|
+
async assert(assertion) {
|
|
534
|
+
const description = assertion;
|
|
535
|
+
const taskExecutor = new import_core.Executor(description);
|
|
536
|
+
taskExecutor.description = description;
|
|
537
|
+
const assertionPlan = {
|
|
538
|
+
type: "Assert",
|
|
539
|
+
param: {
|
|
540
|
+
assertion
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
const assertTask = await this.convertPlanToExecutable([assertionPlan]);
|
|
544
|
+
await taskExecutor.append(this.wrapExecutorWithScreenshot(assertTask[0]));
|
|
545
|
+
const assertionResult = await taskExecutor.flush();
|
|
546
|
+
this.executionDump = taskExecutor.dump();
|
|
547
|
+
return assertionResult;
|
|
548
|
+
}
|
|
533
549
|
};
|
|
534
550
|
|
|
535
551
|
// src/common/agent.ts
|
|
@@ -543,7 +559,7 @@ var PageAgent = class {
|
|
|
543
559
|
}
|
|
544
560
|
];
|
|
545
561
|
this.testId = (opts == null ? void 0 : opts.testId) || String(process.pid);
|
|
546
|
-
this.
|
|
562
|
+
this.taskExecutor = new PageTaskExecutor(this.page, {
|
|
547
563
|
cache: (opts == null ? void 0 : opts.cache) || { aiTasks: [] }
|
|
548
564
|
});
|
|
549
565
|
}
|
|
@@ -552,21 +568,21 @@ var PageAgent = class {
|
|
|
552
568
|
currentDump.executions.push(execution);
|
|
553
569
|
}
|
|
554
570
|
writeOutActionDumps() {
|
|
555
|
-
this.dumpFile = (0,
|
|
571
|
+
this.dumpFile = (0, import_utils4.writeDumpFile)({
|
|
556
572
|
fileName: `playwright-${this.testId}`,
|
|
557
|
-
fileExt:
|
|
573
|
+
fileExt: import_utils4.groupedActionDumpFileExt,
|
|
558
574
|
fileContent: JSON.stringify(this.dumps)
|
|
559
575
|
});
|
|
560
576
|
}
|
|
561
577
|
async aiAction(taskPrompt) {
|
|
562
578
|
let error;
|
|
563
579
|
try {
|
|
564
|
-
await this.
|
|
580
|
+
await this.taskExecutor.action(taskPrompt);
|
|
565
581
|
} catch (e) {
|
|
566
582
|
error = e;
|
|
567
583
|
}
|
|
568
|
-
if (this.
|
|
569
|
-
this.appendDump(this.
|
|
584
|
+
if (this.taskExecutor.executionDump) {
|
|
585
|
+
this.appendDump(this.taskExecutor.executionDump);
|
|
570
586
|
this.writeOutActionDumps();
|
|
571
587
|
}
|
|
572
588
|
if (error) {
|
|
@@ -578,12 +594,12 @@ var PageAgent = class {
|
|
|
578
594
|
let error;
|
|
579
595
|
let result;
|
|
580
596
|
try {
|
|
581
|
-
result = await this.
|
|
597
|
+
result = await this.taskExecutor.query(demand);
|
|
582
598
|
} catch (e) {
|
|
583
599
|
error = e;
|
|
584
600
|
}
|
|
585
|
-
if (this.
|
|
586
|
-
this.appendDump(this.
|
|
601
|
+
if (this.taskExecutor.executionDump) {
|
|
602
|
+
this.appendDump(this.taskExecutor.executionDump);
|
|
587
603
|
this.writeOutActionDumps();
|
|
588
604
|
}
|
|
589
605
|
if (error) {
|
|
@@ -592,23 +608,102 @@ var PageAgent = class {
|
|
|
592
608
|
}
|
|
593
609
|
return result;
|
|
594
610
|
}
|
|
611
|
+
async aiAssert(assertion, msg) {
|
|
612
|
+
const assertionResult = await this.taskExecutor.assert(assertion);
|
|
613
|
+
if (this.taskExecutor.executionDump) {
|
|
614
|
+
this.appendDump(this.taskExecutor.executionDump);
|
|
615
|
+
this.writeOutActionDumps();
|
|
616
|
+
}
|
|
617
|
+
if (!assertionResult.pass) {
|
|
618
|
+
const errMsg = msg || `Assertion failed: ${assertion}`;
|
|
619
|
+
const reasonMsg = `Reason: ${assertionResult.thought}`;
|
|
620
|
+
throw new Error(`${errMsg}
|
|
621
|
+
${reasonMsg}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
595
624
|
async ai(taskPrompt, type = "action") {
|
|
596
625
|
if (type === "action") {
|
|
597
626
|
return this.aiAction(taskPrompt);
|
|
598
|
-
}
|
|
627
|
+
}
|
|
628
|
+
if (type === "query") {
|
|
599
629
|
return this.aiQuery(taskPrompt);
|
|
600
630
|
}
|
|
601
|
-
|
|
631
|
+
if (type === "assert") {
|
|
632
|
+
return this.aiAssert(taskPrompt);
|
|
633
|
+
}
|
|
634
|
+
throw new Error(
|
|
635
|
+
`Unknown type: ${type}, only support 'action', 'query', 'assert'`
|
|
636
|
+
);
|
|
602
637
|
}
|
|
603
638
|
};
|
|
604
639
|
|
|
640
|
+
// src/playwright/cache.ts
|
|
641
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
642
|
+
var import_node_path2 = __toESM(require("path"));
|
|
643
|
+
var import_utils6 = require("@midscene/core/utils");
|
|
644
|
+
function writeTestCache(taskFile, taskTitle, taskCacheJson) {
|
|
645
|
+
const packageJson = getPkgInfo();
|
|
646
|
+
(0, import_utils6.writeDumpFile)({
|
|
647
|
+
fileName: `${taskFile}(${taskTitle})`,
|
|
648
|
+
fileExt: "json",
|
|
649
|
+
fileContent: JSON.stringify(
|
|
650
|
+
__spreadValues({
|
|
651
|
+
pkgName: packageJson.name,
|
|
652
|
+
pkgVersion: packageJson.version,
|
|
653
|
+
taskFile,
|
|
654
|
+
taskTitle
|
|
655
|
+
}, taskCacheJson),
|
|
656
|
+
null,
|
|
657
|
+
2
|
|
658
|
+
),
|
|
659
|
+
type: "cache"
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
function readTestCache(taskFile, taskTitle) {
|
|
663
|
+
const cacheFile = (0, import_node_path2.join)(
|
|
664
|
+
(0, import_utils6.getDumpDirPath)("cache"),
|
|
665
|
+
`${taskFile}(${taskTitle}).json`
|
|
666
|
+
);
|
|
667
|
+
const pkgInfo = getPkgInfo();
|
|
668
|
+
if (process.env.MIDSCENE_CACHE === "true" && import_node_fs2.default.existsSync(cacheFile)) {
|
|
669
|
+
try {
|
|
670
|
+
const data = import_node_fs2.default.readFileSync(cacheFile, "utf8");
|
|
671
|
+
const jsonData = JSON.parse(data);
|
|
672
|
+
if (jsonData.pkgName !== pkgInfo.name || jsonData.pkgVersion !== pkgInfo.version) {
|
|
673
|
+
return void 0;
|
|
674
|
+
}
|
|
675
|
+
return jsonData;
|
|
676
|
+
} catch (err) {
|
|
677
|
+
return void 0;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return void 0;
|
|
681
|
+
}
|
|
682
|
+
function getPkgInfo() {
|
|
683
|
+
const packageJsonDir = findNearestPackageJson(__dirname);
|
|
684
|
+
if (!packageJsonDir) {
|
|
685
|
+
console.error("Cannot find package.json directory: ", __dirname);
|
|
686
|
+
return {
|
|
687
|
+
name: "@midscene/web",
|
|
688
|
+
version: "0.0.0"
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
const packageJsonPath = import_node_path2.default.join(packageJsonDir, "package.json");
|
|
692
|
+
const data = import_node_fs2.default.readFileSync(packageJsonPath, "utf8");
|
|
693
|
+
const packageJson = JSON.parse(data);
|
|
694
|
+
return {
|
|
695
|
+
name: packageJson.name,
|
|
696
|
+
version: packageJson.version
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
605
700
|
// src/playwright/index.ts
|
|
606
701
|
var groupAndCaseForTest = (testInfo) => {
|
|
607
702
|
let taskFile;
|
|
608
703
|
let taskTitle;
|
|
609
704
|
const titlePath = [...testInfo.titlePath];
|
|
610
705
|
if (titlePath.length > 1) {
|
|
611
|
-
taskTitle = titlePath.pop();
|
|
706
|
+
taskTitle = titlePath.pop() || "unnamed";
|
|
612
707
|
taskFile = `${titlePath.join(" > ")}:${testInfo.line}`;
|
|
613
708
|
} else if (titlePath.length === 1) {
|
|
614
709
|
taskTitle = titlePath[0];
|
|
@@ -619,15 +714,17 @@ var groupAndCaseForTest = (testInfo) => {
|
|
|
619
714
|
}
|
|
620
715
|
return { taskFile, taskTitle };
|
|
621
716
|
};
|
|
622
|
-
var
|
|
717
|
+
var midsceneAgentKeyId = "_midsceneAgentId";
|
|
623
718
|
var PlaywrightAiFixture = () => {
|
|
624
719
|
const pageAgentMap = {};
|
|
625
720
|
const agentForPage = (page, opts) => {
|
|
626
|
-
let idForPage = page[
|
|
721
|
+
let idForPage = page[midsceneAgentKeyId];
|
|
627
722
|
if (!idForPage) {
|
|
628
|
-
idForPage = (0,
|
|
629
|
-
page[
|
|
630
|
-
const testCase = readTestCache(opts.taskFile, opts.taskTitle) || {
|
|
723
|
+
idForPage = (0, import_node_crypto.randomUUID)();
|
|
724
|
+
page[midsceneAgentKeyId] = idForPage;
|
|
725
|
+
const testCase = readTestCache(opts.taskFile, opts.taskTitle) || {
|
|
726
|
+
aiTasks: []
|
|
727
|
+
};
|
|
631
728
|
pageAgentMap[idForPage] = new PageAgent(page, {
|
|
632
729
|
testId: `${opts.testId}-${idForPage}`,
|
|
633
730
|
taskFile: opts.taskFile,
|
|
@@ -639,14 +736,20 @@ var PlaywrightAiFixture = () => {
|
|
|
639
736
|
return {
|
|
640
737
|
ai: async ({ page }, use, testInfo) => {
|
|
641
738
|
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
642
|
-
const agent = agentForPage(page, {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const result = await agent.ai(taskPrompt, actionType);
|
|
647
|
-
return result;
|
|
739
|
+
const agent = agentForPage(page, {
|
|
740
|
+
testId: testInfo.testId,
|
|
741
|
+
taskFile,
|
|
742
|
+
taskTitle
|
|
648
743
|
});
|
|
649
|
-
|
|
744
|
+
await use(
|
|
745
|
+
async (taskPrompt, opts) => {
|
|
746
|
+
await page.waitForLoadState("networkidle");
|
|
747
|
+
const actionType = (opts == null ? void 0 : opts.type) || "action";
|
|
748
|
+
const result = await agent.ai(taskPrompt, actionType);
|
|
749
|
+
return result;
|
|
750
|
+
}
|
|
751
|
+
);
|
|
752
|
+
const taskCacheJson = agent.taskExecutor.taskCache.generateTaskCache();
|
|
650
753
|
writeTestCache(taskFile, taskTitle, taskCacheJson);
|
|
651
754
|
if (agent.dumpFile) {
|
|
652
755
|
testInfo.annotations.push({
|
|
@@ -660,7 +763,11 @@ var PlaywrightAiFixture = () => {
|
|
|
660
763
|
},
|
|
661
764
|
aiAction: async ({ page }, use, testInfo) => {
|
|
662
765
|
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
663
|
-
const agent = agentForPage(page, {
|
|
766
|
+
const agent = agentForPage(page, {
|
|
767
|
+
testId: testInfo.testId,
|
|
768
|
+
taskFile,
|
|
769
|
+
taskTitle
|
|
770
|
+
});
|
|
664
771
|
await use(async (taskPrompt) => {
|
|
665
772
|
await page.waitForLoadState("networkidle");
|
|
666
773
|
await agent.aiAction(taskPrompt);
|
|
@@ -677,8 +784,12 @@ var PlaywrightAiFixture = () => {
|
|
|
677
784
|
},
|
|
678
785
|
aiQuery: async ({ page }, use, testInfo) => {
|
|
679
786
|
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
680
|
-
const agent = agentForPage(page, {
|
|
681
|
-
|
|
787
|
+
const agent = agentForPage(page, {
|
|
788
|
+
testId: testInfo.testId,
|
|
789
|
+
taskFile,
|
|
790
|
+
taskTitle
|
|
791
|
+
});
|
|
792
|
+
await use(async (demand) => {
|
|
682
793
|
await page.waitForLoadState("networkidle");
|
|
683
794
|
const result = await agent.aiQuery(demand);
|
|
684
795
|
return result;
|
|
@@ -692,6 +803,27 @@ var PlaywrightAiFixture = () => {
|
|
|
692
803
|
})
|
|
693
804
|
});
|
|
694
805
|
}
|
|
806
|
+
},
|
|
807
|
+
aiAssert: async ({ page }, use, testInfo) => {
|
|
808
|
+
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
809
|
+
const agent = agentForPage(page, {
|
|
810
|
+
testId: testInfo.testId,
|
|
811
|
+
taskFile,
|
|
812
|
+
taskTitle
|
|
813
|
+
});
|
|
814
|
+
await use(async (assertion, errorMsg) => {
|
|
815
|
+
await page.waitForLoadState("networkidle");
|
|
816
|
+
await agent.aiAssert(assertion, errorMsg);
|
|
817
|
+
});
|
|
818
|
+
if (agent.dumpFile) {
|
|
819
|
+
testInfo.annotations.push({
|
|
820
|
+
type: "MIDSCENE_AI_ACTION",
|
|
821
|
+
description: JSON.stringify({
|
|
822
|
+
testId: testInfo.testId,
|
|
823
|
+
dumpPath: agent.dumpFile
|
|
824
|
+
})
|
|
825
|
+
});
|
|
826
|
+
}
|
|
695
827
|
}
|
|
696
828
|
};
|
|
697
829
|
};
|