@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/README.md
ADDED
package/dist/es/index.js
CHANGED
|
@@ -21,16 +21,82 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
|
21
21
|
// src/playwright/index.ts
|
|
22
22
|
import { randomUUID } from "crypto";
|
|
23
23
|
|
|
24
|
-
// src/
|
|
25
|
-
import
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
// src/common/agent.ts
|
|
25
|
+
import { groupedActionDumpFileExt, writeDumpFile } from "@midscene/core/utils";
|
|
26
|
+
|
|
27
|
+
// src/common/tasks.ts
|
|
28
|
+
import assert2 from "assert";
|
|
29
|
+
import Insight, {
|
|
30
|
+
Executor,
|
|
31
|
+
plan
|
|
32
|
+
} from "@midscene/core";
|
|
33
|
+
import { base64Encoded as base64Encoded2 } from "@midscene/core/image";
|
|
34
|
+
import { commonScreenshotParam, getTmpFile as getTmpFile2, sleep } from "@midscene/core/utils";
|
|
35
|
+
|
|
36
|
+
// src/common/task-cache.ts
|
|
37
|
+
var TaskCache = class {
|
|
38
|
+
constructor(opts) {
|
|
39
|
+
this.cache = opts == null ? void 0 : opts.cache;
|
|
40
|
+
this.newCache = {
|
|
41
|
+
aiTasks: []
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
readCache(pageContext, type, userPrompt) {
|
|
45
|
+
var _a;
|
|
46
|
+
if (this.cache) {
|
|
47
|
+
const { aiTasks } = this.cache;
|
|
48
|
+
const index = aiTasks.findIndex((item) => item.prompt === userPrompt);
|
|
49
|
+
if (index === -1) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const taskRes = aiTasks.splice(index, 1)[0];
|
|
53
|
+
if ((taskRes == null ? void 0 : taskRes.type) === "locate" && !((_a = taskRes.response) == null ? void 0 : _a.elements.every((element) => {
|
|
54
|
+
const findIndex = pageContext.content.findIndex(
|
|
55
|
+
(contentElement) => contentElement.id === element.id
|
|
56
|
+
);
|
|
57
|
+
if (findIndex === -1) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}))) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (taskRes && taskRes.type === type && taskRes.prompt === userPrompt && this.pageContextEqual(taskRes.pageContext, pageContext)) {
|
|
65
|
+
return taskRes.response;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
saveCache(cache) {
|
|
71
|
+
var _a;
|
|
72
|
+
if (cache) {
|
|
73
|
+
(_a = this.newCache) == null ? void 0 : _a.aiTasks.push(cache);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
pageContextEqual(taskPageContext, pageContext) {
|
|
77
|
+
return taskPageContext.size.width === pageContext.size.width && taskPageContext.size.height === pageContext.size.height;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Generate task cache data.
|
|
81
|
+
* This method is mainly used to create or obtain some cached data for tasks, and it returns a new cache object.
|
|
82
|
+
* In the cache object, it may contain task-related information, states, or other necessary data.
|
|
83
|
+
* 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.
|
|
84
|
+
* @returns {Object} Returns a new cache object, which may include task cache data.
|
|
85
|
+
*/
|
|
86
|
+
generateTaskCache() {
|
|
87
|
+
return this.newCache;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
28
90
|
|
|
29
91
|
// src/common/utils.ts
|
|
30
|
-
import fs, { readFileSync } from "fs";
|
|
31
92
|
import assert from "assert";
|
|
93
|
+
import fs, { readFileSync } from "fs";
|
|
32
94
|
import path from "path";
|
|
33
|
-
import {
|
|
95
|
+
import {
|
|
96
|
+
alignCoordByTrim,
|
|
97
|
+
base64Encoded,
|
|
98
|
+
imageInfoOfBase64
|
|
99
|
+
} from "@midscene/core/image";
|
|
34
100
|
import { getTmpFile } from "@midscene/core/utils";
|
|
35
101
|
|
|
36
102
|
// src/web-element.ts
|
|
@@ -45,7 +111,10 @@ var WebElementInfo = class {
|
|
|
45
111
|
}) {
|
|
46
112
|
this.content = content;
|
|
47
113
|
this.rect = rect;
|
|
48
|
-
this.center = [
|
|
114
|
+
this.center = [
|
|
115
|
+
Math.floor(rect.left + rect.width / 2),
|
|
116
|
+
Math.floor(rect.top + rect.height / 2)
|
|
117
|
+
];
|
|
49
118
|
this.page = page;
|
|
50
119
|
this.locator = locator;
|
|
51
120
|
this.id = id;
|
|
@@ -62,7 +131,11 @@ async function parseContextFromWebPage(page, _opt) {
|
|
|
62
131
|
const screenshotBuffer = readFileSync(file);
|
|
63
132
|
const screenshotBase64 = base64Encoded(file);
|
|
64
133
|
const captureElementSnapshot = await getElementInfosFromPage(page);
|
|
65
|
-
const elementsInfo = await alignElements(
|
|
134
|
+
const elementsInfo = await alignElements(
|
|
135
|
+
screenshotBuffer,
|
|
136
|
+
captureElementSnapshot,
|
|
137
|
+
page
|
|
138
|
+
);
|
|
66
139
|
const size = await imageInfoOfBase64(screenshotBase64);
|
|
67
140
|
return {
|
|
68
141
|
content: elementsInfo,
|
|
@@ -110,127 +183,6 @@ function findNearestPackageJson(dir) {
|
|
|
110
183
|
return findNearestPackageJson(parentDir);
|
|
111
184
|
}
|
|
112
185
|
|
|
113
|
-
// src/playwright/cache.ts
|
|
114
|
-
function writeTestCache(taskFile, taskTitle, taskCacheJson) {
|
|
115
|
-
const packageJson = getPkgInfo();
|
|
116
|
-
writeDumpFile({
|
|
117
|
-
fileName: `${taskFile}(${taskTitle})`,
|
|
118
|
-
fileExt: "json",
|
|
119
|
-
fileContent: JSON.stringify(
|
|
120
|
-
__spreadValues({
|
|
121
|
-
pkgName: packageJson.name,
|
|
122
|
-
pkgVersion: packageJson.version,
|
|
123
|
-
taskFile,
|
|
124
|
-
taskTitle
|
|
125
|
-
}, taskCacheJson),
|
|
126
|
-
null,
|
|
127
|
-
2
|
|
128
|
-
),
|
|
129
|
-
type: "cache"
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
function readTestCache(taskFile, taskTitle) {
|
|
133
|
-
const cacheFile = join(getDumpDirPath("cache"), `${taskFile}(${taskTitle}).json`);
|
|
134
|
-
const pkgInfo = getPkgInfo();
|
|
135
|
-
if (process.env.MIDSCENE_CACHE === "true" && fs2.existsSync(cacheFile)) {
|
|
136
|
-
try {
|
|
137
|
-
const data = fs2.readFileSync(cacheFile, "utf8");
|
|
138
|
-
const jsonData = JSON.parse(data);
|
|
139
|
-
if (jsonData.pkgName !== pkgInfo.name || jsonData.pkgVersion !== pkgInfo.version) {
|
|
140
|
-
return void 0;
|
|
141
|
-
}
|
|
142
|
-
return jsonData;
|
|
143
|
-
} catch (err) {
|
|
144
|
-
return void 0;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return void 0;
|
|
148
|
-
}
|
|
149
|
-
function getPkgInfo() {
|
|
150
|
-
const packageJsonDir = findNearestPackageJson(__dirname);
|
|
151
|
-
if (!packageJsonDir) {
|
|
152
|
-
console.error("Cannot find package.json directory: ", __dirname);
|
|
153
|
-
return {
|
|
154
|
-
name: "@midscene/web",
|
|
155
|
-
version: "0.0.0"
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
const packageJsonPath = path2.join(packageJsonDir, "package.json");
|
|
159
|
-
const data = fs2.readFileSync(packageJsonPath, "utf8");
|
|
160
|
-
const packageJson = JSON.parse(data);
|
|
161
|
-
return {
|
|
162
|
-
name: packageJson.name,
|
|
163
|
-
version: packageJson.version
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// src/common/agent.ts
|
|
168
|
-
import { groupedActionDumpFileExt, writeDumpFile as writeDumpFile2 } from "@midscene/core/utils";
|
|
169
|
-
|
|
170
|
-
// src/common/tasks.ts
|
|
171
|
-
import assert2 from "assert";
|
|
172
|
-
import Insight, {
|
|
173
|
-
Executor,
|
|
174
|
-
plan
|
|
175
|
-
} from "@midscene/core";
|
|
176
|
-
import { commonScreenshotParam, getTmpFile as getTmpFile2, sleep } from "@midscene/core/utils";
|
|
177
|
-
import { base64Encoded as base64Encoded2 } from "@midscene/core/image";
|
|
178
|
-
|
|
179
|
-
// src/common/task-cache.ts
|
|
180
|
-
var TaskCache = class {
|
|
181
|
-
constructor(opts) {
|
|
182
|
-
this.cache = opts == null ? void 0 : opts.cache;
|
|
183
|
-
this.newCache = {
|
|
184
|
-
aiTasks: []
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
readCache(pageContext, type, userPrompt) {
|
|
188
|
-
var _a;
|
|
189
|
-
if (this.cache) {
|
|
190
|
-
const { aiTasks } = this.cache;
|
|
191
|
-
const index = aiTasks.findIndex((item) => item.prompt === userPrompt);
|
|
192
|
-
if (index === -1) {
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
const taskRes = aiTasks.splice(index, 1)[0];
|
|
196
|
-
if ((taskRes == null ? void 0 : taskRes.type) === "locate" && !((_a = taskRes.response) == null ? void 0 : _a.elements.every((element) => {
|
|
197
|
-
const findIndex = pageContext.content.findIndex(
|
|
198
|
-
(contentElement) => contentElement.id === element.id
|
|
199
|
-
);
|
|
200
|
-
if (findIndex === -1) {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
return true;
|
|
204
|
-
}))) {
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
if (taskRes && taskRes.type === type && taskRes.prompt === userPrompt && this.pageContextEqual(taskRes.pageContext, pageContext)) {
|
|
208
|
-
return taskRes.response;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
saveCache(cache) {
|
|
214
|
-
var _a;
|
|
215
|
-
if (cache) {
|
|
216
|
-
(_a = this.newCache) == null ? void 0 : _a.aiTasks.push(cache);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
pageContextEqual(taskPageContext, pageContext) {
|
|
220
|
-
return taskPageContext.size.width === pageContext.size.width && taskPageContext.size.height === pageContext.size.height;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Generate task cache data.
|
|
224
|
-
* This method is mainly used to create or obtain some cached data for tasks, and it returns a new cache object.
|
|
225
|
-
* In the cache object, it may contain task-related information, states, or other necessary data.
|
|
226
|
-
* 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.
|
|
227
|
-
* @returns {Object} Returns a new cache object, which may include task cache data.
|
|
228
|
-
*/
|
|
229
|
-
generateTaskCache() {
|
|
230
|
-
return this.newCache;
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
|
|
234
186
|
// src/common/tasks.ts
|
|
235
187
|
var PageTaskExecutor = class {
|
|
236
188
|
constructor(page, opts) {
|
|
@@ -286,7 +238,11 @@ var PageTaskExecutor = class {
|
|
|
286
238
|
};
|
|
287
239
|
this.insight.onceDumpUpdatedFn = dumpCollector;
|
|
288
240
|
const pageContext = await this.insight.contextRetrieverFn();
|
|
289
|
-
const locateCache = this.taskCache.readCache(
|
|
241
|
+
const locateCache = this.taskCache.readCache(
|
|
242
|
+
pageContext,
|
|
243
|
+
"locate",
|
|
244
|
+
param.prompt
|
|
245
|
+
);
|
|
290
246
|
let locateResult;
|
|
291
247
|
const callAI = this.insight.aiVendorFn;
|
|
292
248
|
const element = await this.insight.locate(param.prompt, {
|
|
@@ -325,21 +281,51 @@ var PageTaskExecutor = class {
|
|
|
325
281
|
}
|
|
326
282
|
};
|
|
327
283
|
return taskFind;
|
|
328
|
-
}
|
|
284
|
+
}
|
|
285
|
+
if (plan2.type === "Assert") {
|
|
286
|
+
const assertPlan = plan2;
|
|
287
|
+
const taskAssert = {
|
|
288
|
+
type: "Insight",
|
|
289
|
+
subType: "Assert",
|
|
290
|
+
param: assertPlan.param,
|
|
291
|
+
executor: async () => {
|
|
292
|
+
let insightDump;
|
|
293
|
+
const dumpCollector = (dump) => {
|
|
294
|
+
insightDump = dump;
|
|
295
|
+
};
|
|
296
|
+
this.insight.onceDumpUpdatedFn = dumpCollector;
|
|
297
|
+
const assertion = await this.insight.assert(
|
|
298
|
+
assertPlan.param.assertion
|
|
299
|
+
);
|
|
300
|
+
return {
|
|
301
|
+
output: assertion,
|
|
302
|
+
log: {
|
|
303
|
+
dump: insightDump
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
return taskAssert;
|
|
309
|
+
}
|
|
310
|
+
if (plan2.type === "Input") {
|
|
329
311
|
const taskActionInput = {
|
|
330
312
|
type: "Action",
|
|
331
313
|
subType: "Input",
|
|
332
314
|
param: plan2.param,
|
|
333
315
|
executor: async (taskParam, { element }) => {
|
|
334
316
|
if (element) {
|
|
335
|
-
await this.page.mouse.click(
|
|
317
|
+
await this.page.mouse.click(
|
|
318
|
+
element.center[0],
|
|
319
|
+
element.center[1]
|
|
320
|
+
);
|
|
336
321
|
}
|
|
337
322
|
assert2(taskParam.value, "No value to input");
|
|
338
323
|
await this.page.keyboard.type(taskParam.value);
|
|
339
324
|
}
|
|
340
325
|
};
|
|
341
326
|
return taskActionInput;
|
|
342
|
-
}
|
|
327
|
+
}
|
|
328
|
+
if (plan2.type === "KeyboardPress") {
|
|
343
329
|
const taskActionKeyboardPress = {
|
|
344
330
|
type: "Action",
|
|
345
331
|
subType: "KeyboardPress",
|
|
@@ -350,34 +336,45 @@ var PageTaskExecutor = class {
|
|
|
350
336
|
}
|
|
351
337
|
};
|
|
352
338
|
return taskActionKeyboardPress;
|
|
353
|
-
}
|
|
339
|
+
}
|
|
340
|
+
if (plan2.type === "Tap") {
|
|
354
341
|
const taskActionTap = {
|
|
355
342
|
type: "Action",
|
|
356
343
|
subType: "Tap",
|
|
357
344
|
executor: async (param, { element }) => {
|
|
358
345
|
assert2(element, "Element not found, cannot tap");
|
|
359
|
-
await this.page.mouse.click(
|
|
346
|
+
await this.page.mouse.click(
|
|
347
|
+
element.center[0],
|
|
348
|
+
element.center[1]
|
|
349
|
+
);
|
|
360
350
|
}
|
|
361
351
|
};
|
|
362
352
|
return taskActionTap;
|
|
363
|
-
}
|
|
353
|
+
}
|
|
354
|
+
if (plan2.type === "Hover") {
|
|
364
355
|
const taskActionHover = {
|
|
365
356
|
type: "Action",
|
|
366
357
|
subType: "Hover",
|
|
367
358
|
executor: async (param, { element }) => {
|
|
368
359
|
assert2(element, "Element not found, cannot hover");
|
|
369
|
-
await this.page.mouse.move(
|
|
360
|
+
await this.page.mouse.move(
|
|
361
|
+
element.center[0],
|
|
362
|
+
element.center[1]
|
|
363
|
+
);
|
|
370
364
|
}
|
|
371
365
|
};
|
|
372
366
|
return taskActionHover;
|
|
373
|
-
}
|
|
367
|
+
}
|
|
368
|
+
if (plan2.type === "Scroll") {
|
|
374
369
|
const taskActionScroll = {
|
|
375
370
|
type: "Action",
|
|
376
371
|
subType: "Scroll",
|
|
377
372
|
param: plan2.param,
|
|
378
373
|
executor: async (taskParam) => {
|
|
379
374
|
const scrollToEventName = taskParam.scrollType;
|
|
380
|
-
const innerHeight = await this.page.evaluate(
|
|
375
|
+
const innerHeight = await this.page.evaluate(
|
|
376
|
+
() => window.innerHeight
|
|
377
|
+
);
|
|
381
378
|
switch (scrollToEventName) {
|
|
382
379
|
case "ScrollUntilTop":
|
|
383
380
|
await this.page.mouse.wheel(0, -9999999);
|
|
@@ -392,16 +389,19 @@ var PageTaskExecutor = class {
|
|
|
392
389
|
await this.page.mouse.wheel(0, innerHeight);
|
|
393
390
|
break;
|
|
394
391
|
default:
|
|
395
|
-
console.error(
|
|
392
|
+
console.error(
|
|
393
|
+
"Unknown scroll event type:",
|
|
394
|
+
scrollToEventName
|
|
395
|
+
);
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
398
|
};
|
|
399
399
|
return taskActionScroll;
|
|
400
|
-
}
|
|
400
|
+
}
|
|
401
|
+
if (plan2.type === "Error") {
|
|
401
402
|
throw new Error(`Got a task plan with type Error: ${plan2.thought}`);
|
|
402
|
-
} else {
|
|
403
|
-
throw new Error(`Unknown or Unsupported task type: ${plan2.type}`);
|
|
404
403
|
}
|
|
404
|
+
throw new Error(`Unknown or Unsupported task type: ${plan2.type}`);
|
|
405
405
|
}).map((task) => {
|
|
406
406
|
return this.wrapExecutorWithScreenshot(task);
|
|
407
407
|
});
|
|
@@ -419,7 +419,11 @@ var PageTaskExecutor = class {
|
|
|
419
419
|
executor: async (param) => {
|
|
420
420
|
const pageContext = await this.insight.contextRetrieverFn();
|
|
421
421
|
let planResult;
|
|
422
|
-
const planCache = this.taskCache.readCache(
|
|
422
|
+
const planCache = this.taskCache.readCache(
|
|
423
|
+
pageContext,
|
|
424
|
+
"plan",
|
|
425
|
+
userPrompt
|
|
426
|
+
);
|
|
423
427
|
if (planCache) {
|
|
424
428
|
planResult = planCache;
|
|
425
429
|
} else {
|
|
@@ -441,7 +445,7 @@ var PageTaskExecutor = class {
|
|
|
441
445
|
return {
|
|
442
446
|
output: planResult,
|
|
443
447
|
cache: {
|
|
444
|
-
|
|
448
|
+
hit: Boolean(planCache)
|
|
445
449
|
}
|
|
446
450
|
};
|
|
447
451
|
}
|
|
@@ -499,6 +503,22 @@ var PageTaskExecutor = class {
|
|
|
499
503
|
}
|
|
500
504
|
return data;
|
|
501
505
|
}
|
|
506
|
+
async assert(assertion) {
|
|
507
|
+
const description = assertion;
|
|
508
|
+
const taskExecutor = new Executor(description);
|
|
509
|
+
taskExecutor.description = description;
|
|
510
|
+
const assertionPlan = {
|
|
511
|
+
type: "Assert",
|
|
512
|
+
param: {
|
|
513
|
+
assertion
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
const assertTask = await this.convertPlanToExecutable([assertionPlan]);
|
|
517
|
+
await taskExecutor.append(this.wrapExecutorWithScreenshot(assertTask[0]));
|
|
518
|
+
const assertionResult = await taskExecutor.flush();
|
|
519
|
+
this.executionDump = taskExecutor.dump();
|
|
520
|
+
return assertionResult;
|
|
521
|
+
}
|
|
502
522
|
};
|
|
503
523
|
|
|
504
524
|
// src/common/agent.ts
|
|
@@ -512,7 +532,7 @@ var PageAgent = class {
|
|
|
512
532
|
}
|
|
513
533
|
];
|
|
514
534
|
this.testId = (opts == null ? void 0 : opts.testId) || String(process.pid);
|
|
515
|
-
this.
|
|
535
|
+
this.taskExecutor = new PageTaskExecutor(this.page, {
|
|
516
536
|
cache: (opts == null ? void 0 : opts.cache) || { aiTasks: [] }
|
|
517
537
|
});
|
|
518
538
|
}
|
|
@@ -521,7 +541,7 @@ var PageAgent = class {
|
|
|
521
541
|
currentDump.executions.push(execution);
|
|
522
542
|
}
|
|
523
543
|
writeOutActionDumps() {
|
|
524
|
-
this.dumpFile =
|
|
544
|
+
this.dumpFile = writeDumpFile({
|
|
525
545
|
fileName: `playwright-${this.testId}`,
|
|
526
546
|
fileExt: groupedActionDumpFileExt,
|
|
527
547
|
fileContent: JSON.stringify(this.dumps)
|
|
@@ -530,12 +550,12 @@ var PageAgent = class {
|
|
|
530
550
|
async aiAction(taskPrompt) {
|
|
531
551
|
let error;
|
|
532
552
|
try {
|
|
533
|
-
await this.
|
|
553
|
+
await this.taskExecutor.action(taskPrompt);
|
|
534
554
|
} catch (e) {
|
|
535
555
|
error = e;
|
|
536
556
|
}
|
|
537
|
-
if (this.
|
|
538
|
-
this.appendDump(this.
|
|
557
|
+
if (this.taskExecutor.executionDump) {
|
|
558
|
+
this.appendDump(this.taskExecutor.executionDump);
|
|
539
559
|
this.writeOutActionDumps();
|
|
540
560
|
}
|
|
541
561
|
if (error) {
|
|
@@ -547,12 +567,12 @@ var PageAgent = class {
|
|
|
547
567
|
let error;
|
|
548
568
|
let result;
|
|
549
569
|
try {
|
|
550
|
-
result = await this.
|
|
570
|
+
result = await this.taskExecutor.query(demand);
|
|
551
571
|
} catch (e) {
|
|
552
572
|
error = e;
|
|
553
573
|
}
|
|
554
|
-
if (this.
|
|
555
|
-
this.appendDump(this.
|
|
574
|
+
if (this.taskExecutor.executionDump) {
|
|
575
|
+
this.appendDump(this.taskExecutor.executionDump);
|
|
556
576
|
this.writeOutActionDumps();
|
|
557
577
|
}
|
|
558
578
|
if (error) {
|
|
@@ -561,23 +581,102 @@ var PageAgent = class {
|
|
|
561
581
|
}
|
|
562
582
|
return result;
|
|
563
583
|
}
|
|
584
|
+
async aiAssert(assertion, msg) {
|
|
585
|
+
const assertionResult = await this.taskExecutor.assert(assertion);
|
|
586
|
+
if (this.taskExecutor.executionDump) {
|
|
587
|
+
this.appendDump(this.taskExecutor.executionDump);
|
|
588
|
+
this.writeOutActionDumps();
|
|
589
|
+
}
|
|
590
|
+
if (!assertionResult.pass) {
|
|
591
|
+
const errMsg = msg || `Assertion failed: ${assertion}`;
|
|
592
|
+
const reasonMsg = `Reason: ${assertionResult.thought}`;
|
|
593
|
+
throw new Error(`${errMsg}
|
|
594
|
+
${reasonMsg}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
564
597
|
async ai(taskPrompt, type = "action") {
|
|
565
598
|
if (type === "action") {
|
|
566
599
|
return this.aiAction(taskPrompt);
|
|
567
|
-
}
|
|
600
|
+
}
|
|
601
|
+
if (type === "query") {
|
|
568
602
|
return this.aiQuery(taskPrompt);
|
|
569
603
|
}
|
|
570
|
-
|
|
604
|
+
if (type === "assert") {
|
|
605
|
+
return this.aiAssert(taskPrompt);
|
|
606
|
+
}
|
|
607
|
+
throw new Error(
|
|
608
|
+
`Unknown type: ${type}, only support 'action', 'query', 'assert'`
|
|
609
|
+
);
|
|
571
610
|
}
|
|
572
611
|
};
|
|
573
612
|
|
|
613
|
+
// src/playwright/cache.ts
|
|
614
|
+
import fs2 from "fs";
|
|
615
|
+
import path2, { join } from "path";
|
|
616
|
+
import { getDumpDirPath, writeDumpFile as writeDumpFile2 } from "@midscene/core/utils";
|
|
617
|
+
function writeTestCache(taskFile, taskTitle, taskCacheJson) {
|
|
618
|
+
const packageJson = getPkgInfo();
|
|
619
|
+
writeDumpFile2({
|
|
620
|
+
fileName: `${taskFile}(${taskTitle})`,
|
|
621
|
+
fileExt: "json",
|
|
622
|
+
fileContent: JSON.stringify(
|
|
623
|
+
__spreadValues({
|
|
624
|
+
pkgName: packageJson.name,
|
|
625
|
+
pkgVersion: packageJson.version,
|
|
626
|
+
taskFile,
|
|
627
|
+
taskTitle
|
|
628
|
+
}, taskCacheJson),
|
|
629
|
+
null,
|
|
630
|
+
2
|
|
631
|
+
),
|
|
632
|
+
type: "cache"
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
function readTestCache(taskFile, taskTitle) {
|
|
636
|
+
const cacheFile = join(
|
|
637
|
+
getDumpDirPath("cache"),
|
|
638
|
+
`${taskFile}(${taskTitle}).json`
|
|
639
|
+
);
|
|
640
|
+
const pkgInfo = getPkgInfo();
|
|
641
|
+
if (process.env.MIDSCENE_CACHE === "true" && fs2.existsSync(cacheFile)) {
|
|
642
|
+
try {
|
|
643
|
+
const data = fs2.readFileSync(cacheFile, "utf8");
|
|
644
|
+
const jsonData = JSON.parse(data);
|
|
645
|
+
if (jsonData.pkgName !== pkgInfo.name || jsonData.pkgVersion !== pkgInfo.version) {
|
|
646
|
+
return void 0;
|
|
647
|
+
}
|
|
648
|
+
return jsonData;
|
|
649
|
+
} catch (err) {
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return void 0;
|
|
654
|
+
}
|
|
655
|
+
function getPkgInfo() {
|
|
656
|
+
const packageJsonDir = findNearestPackageJson(__dirname);
|
|
657
|
+
if (!packageJsonDir) {
|
|
658
|
+
console.error("Cannot find package.json directory: ", __dirname);
|
|
659
|
+
return {
|
|
660
|
+
name: "@midscene/web",
|
|
661
|
+
version: "0.0.0"
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
const packageJsonPath = path2.join(packageJsonDir, "package.json");
|
|
665
|
+
const data = fs2.readFileSync(packageJsonPath, "utf8");
|
|
666
|
+
const packageJson = JSON.parse(data);
|
|
667
|
+
return {
|
|
668
|
+
name: packageJson.name,
|
|
669
|
+
version: packageJson.version
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
574
673
|
// src/playwright/index.ts
|
|
575
674
|
var groupAndCaseForTest = (testInfo) => {
|
|
576
675
|
let taskFile;
|
|
577
676
|
let taskTitle;
|
|
578
677
|
const titlePath = [...testInfo.titlePath];
|
|
579
678
|
if (titlePath.length > 1) {
|
|
580
|
-
taskTitle = titlePath.pop();
|
|
679
|
+
taskTitle = titlePath.pop() || "unnamed";
|
|
581
680
|
taskFile = `${titlePath.join(" > ")}:${testInfo.line}`;
|
|
582
681
|
} else if (titlePath.length === 1) {
|
|
583
682
|
taskTitle = titlePath[0];
|
|
@@ -588,15 +687,17 @@ var groupAndCaseForTest = (testInfo) => {
|
|
|
588
687
|
}
|
|
589
688
|
return { taskFile, taskTitle };
|
|
590
689
|
};
|
|
591
|
-
var
|
|
690
|
+
var midsceneAgentKeyId = "_midsceneAgentId";
|
|
592
691
|
var PlaywrightAiFixture = () => {
|
|
593
692
|
const pageAgentMap = {};
|
|
594
693
|
const agentForPage = (page, opts) => {
|
|
595
|
-
let idForPage = page[
|
|
694
|
+
let idForPage = page[midsceneAgentKeyId];
|
|
596
695
|
if (!idForPage) {
|
|
597
696
|
idForPage = randomUUID();
|
|
598
|
-
page[
|
|
599
|
-
const testCase = readTestCache(opts.taskFile, opts.taskTitle) || {
|
|
697
|
+
page[midsceneAgentKeyId] = idForPage;
|
|
698
|
+
const testCase = readTestCache(opts.taskFile, opts.taskTitle) || {
|
|
699
|
+
aiTasks: []
|
|
700
|
+
};
|
|
600
701
|
pageAgentMap[idForPage] = new PageAgent(page, {
|
|
601
702
|
testId: `${opts.testId}-${idForPage}`,
|
|
602
703
|
taskFile: opts.taskFile,
|
|
@@ -608,14 +709,20 @@ var PlaywrightAiFixture = () => {
|
|
|
608
709
|
return {
|
|
609
710
|
ai: async ({ page }, use, testInfo) => {
|
|
610
711
|
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
611
|
-
const agent = agentForPage(page, {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const result = await agent.ai(taskPrompt, actionType);
|
|
616
|
-
return result;
|
|
712
|
+
const agent = agentForPage(page, {
|
|
713
|
+
testId: testInfo.testId,
|
|
714
|
+
taskFile,
|
|
715
|
+
taskTitle
|
|
617
716
|
});
|
|
618
|
-
|
|
717
|
+
await use(
|
|
718
|
+
async (taskPrompt, opts) => {
|
|
719
|
+
await page.waitForLoadState("networkidle");
|
|
720
|
+
const actionType = (opts == null ? void 0 : opts.type) || "action";
|
|
721
|
+
const result = await agent.ai(taskPrompt, actionType);
|
|
722
|
+
return result;
|
|
723
|
+
}
|
|
724
|
+
);
|
|
725
|
+
const taskCacheJson = agent.taskExecutor.taskCache.generateTaskCache();
|
|
619
726
|
writeTestCache(taskFile, taskTitle, taskCacheJson);
|
|
620
727
|
if (agent.dumpFile) {
|
|
621
728
|
testInfo.annotations.push({
|
|
@@ -629,7 +736,11 @@ var PlaywrightAiFixture = () => {
|
|
|
629
736
|
},
|
|
630
737
|
aiAction: async ({ page }, use, testInfo) => {
|
|
631
738
|
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
632
|
-
const agent = agentForPage(page, {
|
|
739
|
+
const agent = agentForPage(page, {
|
|
740
|
+
testId: testInfo.testId,
|
|
741
|
+
taskFile,
|
|
742
|
+
taskTitle
|
|
743
|
+
});
|
|
633
744
|
await use(async (taskPrompt) => {
|
|
634
745
|
await page.waitForLoadState("networkidle");
|
|
635
746
|
await agent.aiAction(taskPrompt);
|
|
@@ -646,8 +757,12 @@ var PlaywrightAiFixture = () => {
|
|
|
646
757
|
},
|
|
647
758
|
aiQuery: async ({ page }, use, testInfo) => {
|
|
648
759
|
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
649
|
-
const agent = agentForPage(page, {
|
|
650
|
-
|
|
760
|
+
const agent = agentForPage(page, {
|
|
761
|
+
testId: testInfo.testId,
|
|
762
|
+
taskFile,
|
|
763
|
+
taskTitle
|
|
764
|
+
});
|
|
765
|
+
await use(async (demand) => {
|
|
651
766
|
await page.waitForLoadState("networkidle");
|
|
652
767
|
const result = await agent.aiQuery(demand);
|
|
653
768
|
return result;
|
|
@@ -661,6 +776,27 @@ var PlaywrightAiFixture = () => {
|
|
|
661
776
|
})
|
|
662
777
|
});
|
|
663
778
|
}
|
|
779
|
+
},
|
|
780
|
+
aiAssert: async ({ page }, use, testInfo) => {
|
|
781
|
+
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
782
|
+
const agent = agentForPage(page, {
|
|
783
|
+
testId: testInfo.testId,
|
|
784
|
+
taskFile,
|
|
785
|
+
taskTitle
|
|
786
|
+
});
|
|
787
|
+
await use(async (assertion, errorMsg) => {
|
|
788
|
+
await page.waitForLoadState("networkidle");
|
|
789
|
+
await agent.aiAssert(assertion, errorMsg);
|
|
790
|
+
});
|
|
791
|
+
if (agent.dumpFile) {
|
|
792
|
+
testInfo.annotations.push({
|
|
793
|
+
type: "MIDSCENE_AI_ACTION",
|
|
794
|
+
description: JSON.stringify({
|
|
795
|
+
testId: testInfo.testId,
|
|
796
|
+
dumpPath: agent.dumpFile
|
|
797
|
+
})
|
|
798
|
+
});
|
|
799
|
+
}
|
|
664
800
|
}
|
|
665
801
|
};
|
|
666
802
|
};
|