@midscene/web 0.0.1 → 0.1.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/dist/es/index.js +337 -110
- package/dist/es/playwright-report.js +2380 -0
- package/dist/lib/index.js +338 -110
- package/dist/lib/playwright-report.js +2383 -0
- package/dist/script/htmlElement.js +596 -25
- package/dist/script/types/htmlElement.d.ts +2 -0
- package/dist/types/index.d.ts +116 -21
- package/dist/types/playwright-report.d.ts +10 -0
- package/package.json +25 -4
- package/modern.config.ts +0 -13
- package/modern.inspect.config.ts +0 -20
- package/playwright.config.ts +0 -42
- package/src/html-element/constants.ts +0 -10
- package/src/html-element/debug.ts +0 -3
- package/src/html-element/dom-util.ts +0 -11
- package/src/html-element/extractInfo.ts +0 -168
- package/src/html-element/index.ts +0 -1
- package/src/html-element/util.ts +0 -160
- package/src/img/img.ts +0 -132
- package/src/img/util.ts +0 -28
- package/src/index.ts +0 -2
- package/src/playwright/actions.ts +0 -276
- package/src/playwright/cdp.ts +0 -322
- package/src/playwright/element.ts +0 -74
- package/src/playwright/index.ts +0 -120
- package/src/playwright/utils.ts +0 -88
- package/src/puppeteer/element.ts +0 -49
- package/src/puppeteer/index.ts +0 -6
- package/src/puppeteer/utils.ts +0 -116
- package/tests/e2e/ai-auto-todo.spec.ts +0 -24
- package/tests/e2e/ai-xicha.spec.ts +0 -34
- package/tests/e2e/fixture.ts +0 -6
- package/tests/e2e/generate-test-data.spec.ts +0 -60
- package/tests/e2e/todo-app-midscene.spec.ts +0 -98
- package/tests/e2e/tool.ts +0 -63
- package/tsconfig.json +0 -23
- package/vitest.config.ts +0 -14
package/dist/es/index.js
CHANGED
|
@@ -19,24 +19,21 @@ var __spreadValues = (a, b) => {
|
|
|
19
19
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
20
|
|
|
21
21
|
// src/playwright/index.ts
|
|
22
|
-
import {
|
|
22
|
+
import { randomUUID } from "crypto";
|
|
23
23
|
|
|
24
|
-
// src/playwright/
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
plan
|
|
29
|
-
} from "@midscene/core";
|
|
30
|
-
import { commonScreenshotParam, getTmpFile, sleep } from "@midscene/core/utils";
|
|
31
|
-
import { base64Encoded as base64Encoded2 } from "@midscene/core/image";
|
|
24
|
+
// src/playwright/cache.ts
|
|
25
|
+
import path2, { join } from "path";
|
|
26
|
+
import fs2 from "fs";
|
|
27
|
+
import { writeDumpFile, getDumpDirPath } from "@midscene/core/utils";
|
|
32
28
|
|
|
33
|
-
// src/
|
|
29
|
+
// src/common/utils.ts
|
|
34
30
|
import fs, { readFileSync } from "fs";
|
|
35
31
|
import assert from "assert";
|
|
36
32
|
import path from "path";
|
|
37
33
|
import { alignCoordByTrim, base64Encoded, imageInfoOfBase64 } from "@midscene/core/image";
|
|
34
|
+
import { getTmpFile } from "@midscene/core/utils";
|
|
38
35
|
|
|
39
|
-
// src/
|
|
36
|
+
// src/web-element.ts
|
|
40
37
|
var WebElementInfo = class {
|
|
41
38
|
constructor({
|
|
42
39
|
content,
|
|
@@ -54,24 +51,13 @@ var WebElementInfo = class {
|
|
|
54
51
|
this.id = id;
|
|
55
52
|
this.attributes = attributes;
|
|
56
53
|
}
|
|
57
|
-
async tap() {
|
|
58
|
-
await this.page.mouse.click(this.center[0], this.center[1]);
|
|
59
|
-
}
|
|
60
|
-
async hover() {
|
|
61
|
-
await this.page.mouse.move(this.center[0], this.center[1]);
|
|
62
|
-
}
|
|
63
|
-
async type(text) {
|
|
64
|
-
await this.page.keyboard.type(text);
|
|
65
|
-
}
|
|
66
|
-
async press(key) {
|
|
67
|
-
await this.page.keyboard.press(key);
|
|
68
|
-
}
|
|
69
54
|
};
|
|
70
55
|
|
|
71
|
-
// src/
|
|
72
|
-
async function
|
|
56
|
+
// src/common/utils.ts
|
|
57
|
+
async function parseContextFromWebPage(page, _opt) {
|
|
73
58
|
assert(page, "page is required");
|
|
74
|
-
const
|
|
59
|
+
const url = page.url();
|
|
60
|
+
const file = getTmpFile("jpeg");
|
|
75
61
|
await page.screenshot({ path: file, type: "jpeg", quality: 75 });
|
|
76
62
|
const screenshotBuffer = readFileSync(file);
|
|
77
63
|
const screenshotBase64 = base64Encoded(file);
|
|
@@ -81,7 +67,8 @@ async function parseContextFromPlaywrightPage(page, _opt) {
|
|
|
81
67
|
return {
|
|
82
68
|
content: elementsInfo,
|
|
83
69
|
size,
|
|
84
|
-
screenshotBase64
|
|
70
|
+
screenshotBase64,
|
|
71
|
+
url
|
|
85
72
|
};
|
|
86
73
|
}
|
|
87
74
|
async function getElementInfosFromPage(page) {
|
|
@@ -123,17 +110,138 @@ function findNearestPackageJson(dir) {
|
|
|
123
110
|
return findNearestPackageJson(parentDir);
|
|
124
111
|
}
|
|
125
112
|
|
|
126
|
-
// src/playwright/
|
|
127
|
-
|
|
128
|
-
|
|
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
|
+
// src/common/tasks.ts
|
|
235
|
+
var PageTaskExecutor = class {
|
|
236
|
+
constructor(page, opts) {
|
|
129
237
|
this.page = page;
|
|
130
238
|
this.insight = new Insight(async () => {
|
|
131
|
-
return await
|
|
239
|
+
return await parseContextFromWebPage(page);
|
|
132
240
|
});
|
|
133
|
-
this.
|
|
241
|
+
this.taskCache = new TaskCache(opts);
|
|
134
242
|
}
|
|
135
243
|
async recordScreenshot(timing) {
|
|
136
|
-
const file =
|
|
244
|
+
const file = getTmpFile2("jpeg");
|
|
137
245
|
await this.page.screenshot(__spreadProps(__spreadValues({}, commonScreenshotParam), {
|
|
138
246
|
path: file
|
|
139
247
|
}));
|
|
@@ -179,14 +287,41 @@ var PlayWrightActionAgent = class {
|
|
|
179
287
|
insightDump = dump;
|
|
180
288
|
};
|
|
181
289
|
this.insight.onceDumpUpdatedFn = dumpCollector;
|
|
182
|
-
const
|
|
290
|
+
const pageContext = await this.insight.contextRetrieverFn();
|
|
291
|
+
const locateCache = this.taskCache.readCache(pageContext, "locate", param.prompt);
|
|
292
|
+
let locateResult;
|
|
293
|
+
const callAI = this.insight.aiVendorFn;
|
|
294
|
+
const element = await this.insight.locate(param.prompt, {
|
|
295
|
+
callAI: async (message) => {
|
|
296
|
+
if (locateCache) {
|
|
297
|
+
locateResult = locateCache;
|
|
298
|
+
return Promise.resolve(locateCache);
|
|
299
|
+
}
|
|
300
|
+
locateResult = await callAI(message);
|
|
301
|
+
return locateResult;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
183
304
|
assert2(element, `Element not found: ${param.prompt}`);
|
|
305
|
+
if (locateResult) {
|
|
306
|
+
this.taskCache.saveCache({
|
|
307
|
+
type: "locate",
|
|
308
|
+
pageContext: {
|
|
309
|
+
url: pageContext.url,
|
|
310
|
+
size: pageContext.size
|
|
311
|
+
},
|
|
312
|
+
prompt: param.prompt,
|
|
313
|
+
response: locateResult
|
|
314
|
+
});
|
|
315
|
+
}
|
|
184
316
|
return {
|
|
185
317
|
output: {
|
|
186
318
|
element
|
|
187
319
|
},
|
|
188
320
|
log: {
|
|
189
321
|
dump: insightDump
|
|
322
|
+
},
|
|
323
|
+
cache: {
|
|
324
|
+
hit: Boolean(locateResult)
|
|
190
325
|
}
|
|
191
326
|
};
|
|
192
327
|
}
|
|
@@ -272,43 +407,66 @@ var PlayWrightActionAgent = class {
|
|
|
272
407
|
return tasks;
|
|
273
408
|
}
|
|
274
409
|
async action(userPrompt) {
|
|
275
|
-
|
|
276
|
-
|
|
410
|
+
const taskExecutor = new Executor(userPrompt);
|
|
411
|
+
taskExecutor.description = userPrompt;
|
|
277
412
|
let plans = [];
|
|
278
413
|
const planningTask = {
|
|
279
414
|
type: "Planning",
|
|
280
415
|
param: {
|
|
281
416
|
userPrompt
|
|
282
417
|
},
|
|
283
|
-
async
|
|
284
|
-
const
|
|
418
|
+
executor: async (param) => {
|
|
419
|
+
const pageContext = await this.insight.contextRetrieverFn();
|
|
420
|
+
let planResult;
|
|
421
|
+
const planCache = this.taskCache.readCache(pageContext, "plan", userPrompt);
|
|
422
|
+
if (planCache) {
|
|
423
|
+
planResult = planCache;
|
|
424
|
+
} else {
|
|
425
|
+
planResult = await plan(param.userPrompt, {
|
|
426
|
+
context: pageContext
|
|
427
|
+
});
|
|
428
|
+
}
|
|
285
429
|
assert2(planResult.plans.length > 0, "No plans found");
|
|
286
430
|
plans = planResult.plans;
|
|
431
|
+
this.taskCache.saveCache({
|
|
432
|
+
type: "plan",
|
|
433
|
+
pageContext: {
|
|
434
|
+
url: pageContext.url,
|
|
435
|
+
size: pageContext.size
|
|
436
|
+
},
|
|
437
|
+
prompt: userPrompt,
|
|
438
|
+
response: planResult
|
|
439
|
+
});
|
|
287
440
|
return {
|
|
288
|
-
output: planResult
|
|
441
|
+
output: planResult,
|
|
442
|
+
cache: {
|
|
443
|
+
hint: Boolean(planCache)
|
|
444
|
+
}
|
|
289
445
|
};
|
|
290
446
|
}
|
|
291
447
|
};
|
|
292
448
|
try {
|
|
293
|
-
await
|
|
294
|
-
await
|
|
295
|
-
this.
|
|
449
|
+
await taskExecutor.append(this.wrapExecutorWithScreenshot(planningTask));
|
|
450
|
+
await taskExecutor.flush();
|
|
451
|
+
this.executionDump = taskExecutor.dump();
|
|
296
452
|
const executables = await this.convertPlanToExecutable(plans);
|
|
297
|
-
await
|
|
298
|
-
await
|
|
299
|
-
this.
|
|
453
|
+
await taskExecutor.append(executables);
|
|
454
|
+
await taskExecutor.flush();
|
|
455
|
+
this.executionDump = taskExecutor.dump();
|
|
300
456
|
assert2(
|
|
301
|
-
|
|
302
|
-
`failed to execute tasks: ${
|
|
457
|
+
taskExecutor.status !== "error",
|
|
458
|
+
`failed to execute tasks: ${taskExecutor.status}, msg: ${taskExecutor.errorMsg || ""}`
|
|
303
459
|
);
|
|
304
460
|
} catch (e) {
|
|
305
|
-
this.
|
|
461
|
+
this.executionDump = taskExecutor.dump();
|
|
306
462
|
const err = new Error(e.message, { cause: e });
|
|
307
463
|
throw err;
|
|
308
464
|
}
|
|
309
465
|
}
|
|
310
466
|
async query(demand) {
|
|
311
|
-
|
|
467
|
+
const description = JSON.stringify(demand);
|
|
468
|
+
const taskExecutor = new Executor(description);
|
|
469
|
+
taskExecutor.description = description;
|
|
312
470
|
let data;
|
|
313
471
|
const queryTask = {
|
|
314
472
|
type: "Insight",
|
|
@@ -330,11 +488,11 @@ var PlayWrightActionAgent = class {
|
|
|
330
488
|
}
|
|
331
489
|
};
|
|
332
490
|
try {
|
|
333
|
-
await
|
|
334
|
-
await
|
|
335
|
-
this.
|
|
491
|
+
await taskExecutor.append(this.wrapExecutorWithScreenshot(queryTask));
|
|
492
|
+
await taskExecutor.flush();
|
|
493
|
+
this.executionDump = taskExecutor.dump();
|
|
336
494
|
} catch (e) {
|
|
337
|
-
this.
|
|
495
|
+
this.executionDump = taskExecutor.dump();
|
|
338
496
|
const err = new Error(e.message, { cause: e });
|
|
339
497
|
throw err;
|
|
340
498
|
}
|
|
@@ -342,101 +500,170 @@ var PlayWrightActionAgent = class {
|
|
|
342
500
|
}
|
|
343
501
|
};
|
|
344
502
|
|
|
345
|
-
// src/
|
|
346
|
-
var
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
groupName,
|
|
503
|
+
// src/common/agent.ts
|
|
504
|
+
var PageAgent = class {
|
|
505
|
+
constructor(page, opts) {
|
|
506
|
+
this.page = page;
|
|
507
|
+
this.dumps = [
|
|
508
|
+
{
|
|
509
|
+
groupName: (opts == null ? void 0 : opts.taskFile) || "unnamed",
|
|
353
510
|
executions: []
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
511
|
+
}
|
|
512
|
+
];
|
|
513
|
+
this.testId = (opts == null ? void 0 : opts.testId) || String(process.pid);
|
|
514
|
+
this.actionAgent = new PageTaskExecutor(this.page, {
|
|
515
|
+
cache: (opts == null ? void 0 : opts.cache) || { aiTasks: [] }
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
appendDump(execution) {
|
|
519
|
+
const currentDump = this.dumps[0];
|
|
357
520
|
currentDump.executions.push(execution);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
caseName = titlePath.pop();
|
|
368
|
-
groupName = titlePath.join(" > ");
|
|
369
|
-
} else if (titlePath.length === 1) {
|
|
370
|
-
caseName = titlePath[0];
|
|
371
|
-
groupName = caseName;
|
|
372
|
-
} else {
|
|
373
|
-
caseName = "unnamed";
|
|
374
|
-
groupName = "unnamed";
|
|
375
|
-
}
|
|
376
|
-
return { groupName, caseName };
|
|
377
|
-
};
|
|
378
|
-
const aiAction = async (page, testInfo, taskPrompt) => {
|
|
379
|
-
const { groupName, caseName } = groupAndCaseForTest(testInfo);
|
|
380
|
-
const actionAgent = new PlayWrightActionAgent(page, { taskName: caseName });
|
|
521
|
+
}
|
|
522
|
+
writeOutActionDumps() {
|
|
523
|
+
this.dumpFile = writeDumpFile2({
|
|
524
|
+
fileName: `playwright-${this.testId}`,
|
|
525
|
+
fileExt: groupedActionDumpFileExt,
|
|
526
|
+
fileContent: JSON.stringify(this.dumps)
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
async aiAction(taskPrompt) {
|
|
381
530
|
let error;
|
|
382
531
|
try {
|
|
383
|
-
await actionAgent.action(taskPrompt);
|
|
532
|
+
await this.actionAgent.action(taskPrompt);
|
|
384
533
|
} catch (e) {
|
|
385
534
|
error = e;
|
|
386
535
|
}
|
|
387
|
-
if (actionAgent.
|
|
388
|
-
appendDump(
|
|
389
|
-
writeOutActionDumps();
|
|
536
|
+
if (this.actionAgent.executionDump) {
|
|
537
|
+
this.appendDump(this.actionAgent.executionDump);
|
|
538
|
+
this.writeOutActionDumps();
|
|
390
539
|
}
|
|
391
540
|
if (error) {
|
|
392
541
|
console.error(error);
|
|
393
542
|
throw new Error(error.message, { cause: error });
|
|
394
543
|
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const { groupName, caseName } = groupAndCaseForTest(testInfo);
|
|
398
|
-
const actionAgent = new PlayWrightActionAgent(page, { taskName: caseName });
|
|
544
|
+
}
|
|
545
|
+
async aiQuery(demand) {
|
|
399
546
|
let error;
|
|
400
547
|
let result;
|
|
401
548
|
try {
|
|
402
|
-
result = await actionAgent.query(demand);
|
|
549
|
+
result = await this.actionAgent.query(demand);
|
|
403
550
|
} catch (e) {
|
|
404
551
|
error = e;
|
|
405
552
|
}
|
|
406
|
-
if (actionAgent.
|
|
407
|
-
appendDump(
|
|
408
|
-
writeOutActionDumps();
|
|
553
|
+
if (this.actionAgent.executionDump) {
|
|
554
|
+
this.appendDump(this.actionAgent.executionDump);
|
|
555
|
+
this.writeOutActionDumps();
|
|
409
556
|
}
|
|
410
557
|
if (error) {
|
|
411
558
|
console.error(error);
|
|
412
559
|
throw new Error(error.message, { cause: error });
|
|
413
560
|
}
|
|
414
561
|
return result;
|
|
562
|
+
}
|
|
563
|
+
async ai(taskPrompt, type = "action") {
|
|
564
|
+
if (type === "action") {
|
|
565
|
+
return this.aiAction(taskPrompt);
|
|
566
|
+
} else if (type === "query") {
|
|
567
|
+
return this.aiQuery(taskPrompt);
|
|
568
|
+
}
|
|
569
|
+
throw new Error(`Unknown or Unsupported task type: ${type}, only support 'action' or 'query'`);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// src/playwright/index.ts
|
|
574
|
+
var groupAndCaseForTest = (testInfo) => {
|
|
575
|
+
let taskFile;
|
|
576
|
+
let taskTitle;
|
|
577
|
+
const titlePath = [...testInfo.titlePath];
|
|
578
|
+
if (titlePath.length > 1) {
|
|
579
|
+
taskTitle = titlePath.pop();
|
|
580
|
+
taskFile = `${titlePath.join(" > ")}:${testInfo.line}`;
|
|
581
|
+
} else if (titlePath.length === 1) {
|
|
582
|
+
taskTitle = titlePath[0];
|
|
583
|
+
taskFile = `${taskTitle}:${testInfo.line}`;
|
|
584
|
+
} else {
|
|
585
|
+
taskTitle = "unnamed";
|
|
586
|
+
taskFile = "unnamed";
|
|
587
|
+
}
|
|
588
|
+
return { taskFile, taskTitle };
|
|
589
|
+
};
|
|
590
|
+
var midSceneAgentKeyId = "_midSceneAgentId";
|
|
591
|
+
var PlaywrightAiFixture = () => {
|
|
592
|
+
const pageAgentMap = {};
|
|
593
|
+
const agentForPage = (page, opts) => {
|
|
594
|
+
let idForPage = page[midSceneAgentKeyId];
|
|
595
|
+
if (!idForPage) {
|
|
596
|
+
idForPage = randomUUID();
|
|
597
|
+
page[midSceneAgentKeyId] = idForPage;
|
|
598
|
+
const testCase = readTestCache(opts.taskFile, opts.taskTitle) || { aiTasks: [] };
|
|
599
|
+
pageAgentMap[idForPage] = new PageAgent(page, {
|
|
600
|
+
testId: `${opts.testId}-${idForPage}`,
|
|
601
|
+
taskFile: opts.taskFile,
|
|
602
|
+
cache: testCase
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
return pageAgentMap[idForPage];
|
|
415
606
|
};
|
|
416
607
|
return {
|
|
417
|
-
// shortcut
|
|
418
608
|
ai: async ({ page }, use, testInfo) => {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
609
|
+
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
610
|
+
const agent = agentForPage(page, { testId: testInfo.testId, taskFile, taskTitle });
|
|
611
|
+
await use(async (taskPrompt, opts) => {
|
|
612
|
+
await page.waitForLoadState("networkidle");
|
|
613
|
+
const actionType = (opts == null ? void 0 : opts.type) || "action";
|
|
614
|
+
const result = await agent.ai(taskPrompt, actionType);
|
|
615
|
+
return result;
|
|
426
616
|
});
|
|
617
|
+
const taskCacheJson = agent.actionAgent.taskCache.generateTaskCache();
|
|
618
|
+
writeTestCache(taskFile, taskTitle, taskCacheJson);
|
|
619
|
+
if (agent.dumpFile) {
|
|
620
|
+
testInfo.annotations.push({
|
|
621
|
+
type: "MIDSCENE_AI_ACTION",
|
|
622
|
+
description: JSON.stringify({
|
|
623
|
+
testId: testInfo.testId,
|
|
624
|
+
dumpPath: agent.dumpFile
|
|
625
|
+
})
|
|
626
|
+
});
|
|
627
|
+
}
|
|
427
628
|
},
|
|
428
629
|
aiAction: async ({ page }, use, testInfo) => {
|
|
630
|
+
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
631
|
+
const agent = agentForPage(page, { testId: testInfo.testId, taskFile, taskTitle });
|
|
429
632
|
await use(async (taskPrompt) => {
|
|
430
|
-
await
|
|
633
|
+
await page.waitForLoadState("networkidle");
|
|
634
|
+
await agent.aiAction(taskPrompt);
|
|
431
635
|
});
|
|
636
|
+
if (agent.dumpFile) {
|
|
637
|
+
testInfo.annotations.push({
|
|
638
|
+
type: "MIDSCENE_AI_ACTION",
|
|
639
|
+
description: JSON.stringify({
|
|
640
|
+
testId: testInfo.testId,
|
|
641
|
+
dumpPath: agent.dumpFile
|
|
642
|
+
})
|
|
643
|
+
});
|
|
644
|
+
}
|
|
432
645
|
},
|
|
433
646
|
aiQuery: async ({ page }, use, testInfo) => {
|
|
647
|
+
const { taskFile, taskTitle } = groupAndCaseForTest(testInfo);
|
|
648
|
+
const agent = agentForPage(page, { testId: testInfo.testId, taskFile, taskTitle });
|
|
434
649
|
await use(async function(demand) {
|
|
435
|
-
|
|
650
|
+
await page.waitForLoadState("networkidle");
|
|
651
|
+
const result = await agent.aiQuery(demand);
|
|
652
|
+
return result;
|
|
436
653
|
});
|
|
654
|
+
if (agent.dumpFile) {
|
|
655
|
+
testInfo.annotations.push({
|
|
656
|
+
type: "MIDSCENE_AI_ACTION",
|
|
657
|
+
description: JSON.stringify({
|
|
658
|
+
testId: testInfo.testId,
|
|
659
|
+
dumpPath: agent.dumpFile
|
|
660
|
+
})
|
|
661
|
+
});
|
|
662
|
+
}
|
|
437
663
|
}
|
|
438
664
|
};
|
|
439
665
|
};
|
|
440
666
|
export {
|
|
441
|
-
PlaywrightAiFixture
|
|
667
|
+
PlaywrightAiFixture,
|
|
668
|
+
PageAgent as PuppeteerAgent
|
|
442
669
|
};
|