@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.
Files changed (28) hide show
  1. package/README.md +7 -0
  2. package/dist/es/index.js +306 -170
  3. package/dist/es/playwright-report.js +22 -15
  4. package/dist/lib/index.js +320 -188
  5. package/dist/lib/playwright-report.js +31 -28
  6. package/dist/script/htmlElement.js +30 -18
  7. package/dist/script/types/htmlElement.d.ts +5 -5
  8. package/dist/types/index.d.ts +18 -12
  9. package/dist/types/playwright-report.d.ts +2 -2
  10. package/dist/visualizer-report/index.html +1 -1
  11. package/dist/visualizer-report/static/css/index.eccd04e1.css +1 -0
  12. package/dist/visualizer-report/static/css/index.eccd04e1.css.map +1 -0
  13. package/dist/visualizer-report/static/js/160.5ac2287a.js +6 -0
  14. package/dist/visualizer-report/static/js/160.5ac2287a.js.map +1 -0
  15. package/dist/visualizer-report/static/js/index.e71be3a2.js +1 -0
  16. package/dist/visualizer-report/static/js/index.e71be3a2.js.map +1 -0
  17. package/dist/visualizer-report/static/js/lib-antd.583c9200.js +141 -0
  18. package/dist/visualizer-report/static/js/lib-antd.583c9200.js.map +1 -0
  19. package/package.json +7 -6
  20. package/dist/visualizer-report/static/css/index.c7751597.css +0 -1
  21. package/dist/visualizer-report/static/css/index.c7751597.css.map +0 -1
  22. package/dist/visualizer-report/static/js/915.d3f73af1.js +0 -6
  23. package/dist/visualizer-report/static/js/915.d3f73af1.js.map +0 -1
  24. package/dist/visualizer-report/static/js/index.ae9a86c5.js +0 -1
  25. package/dist/visualizer-report/static/js/index.ae9a86c5.js.map +0 -1
  26. package/dist/visualizer-report/static/js/lib-antd.55d65804.js +0 -189
  27. package/dist/visualizer-report/static/js/lib-antd.55d65804.js.map +0 -1
  28. /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 import_crypto = require("crypto");
56
+ var import_node_crypto = require("crypto");
57
57
 
58
- // src/playwright/cache.ts
59
- var import_path2 = __toESM(require("path"));
60
- var import_fs2 = __toESM(require("fs"));
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 import_fs = __toESM(require("fs"));
65
- var import_assert = __toESM(require("assert"));
66
- var import_path = __toESM(require("path"));
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 = [Math.floor(rect.left + rect.width / 2), Math.floor(rect.top + rect.height / 2)];
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, import_assert.default)(page, "page is required");
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, import_fs.readFileSync)(file);
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(screenshotBuffer, captureElementSnapshot, page);
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, import_assert.default)(pathDir, `can't find pathDir, with ${__dirname}`);
111
- const scriptPath = import_path.default.join(pathDir, "./dist/script/htmlElement.js");
112
- const elementInfosScriptContent = (0, import_fs.readFileSync)(scriptPath, "utf-8");
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 = import_path.default.join(dir, "package.json");
137
- if (import_fs.default.existsSync(packageJsonPath)) {
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 = import_path.default.dirname(dir);
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, import_utils4.getTmpFile)("jpeg");
276
- await this.page.screenshot(__spreadProps(__spreadValues({}, import_utils4.commonScreenshotParam), {
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, import_utils4.sleep)(1e3);
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(pageContext, "locate", param.prompt);
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, import_assert2.default)(element, `Element not found: ${param.prompt}`);
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
- } else if (plan2.type === "Input") {
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(element.center[0], element.center[1]);
344
+ await this.page.mouse.click(
345
+ element.center[0],
346
+ element.center[1]
347
+ );
367
348
  }
368
- (0, import_assert2.default)(taskParam.value, "No value to input");
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
- } else if (plan2.type === "KeyboardPress") {
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, import_assert2.default)(taskParam.value, "No key to press");
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
- } else if (plan2.type === "Tap") {
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, import_assert2.default)(element, "Element not found, cannot tap");
390
- await this.page.mouse.click(element.center[0], element.center[1]);
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
- } else if (plan2.type === "Hover") {
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, import_assert2.default)(element, "Element not found, cannot hover");
400
- await this.page.mouse.move(element.center[0], element.center[1]);
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
- } else if (plan2.type === "Scroll") {
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(() => window.innerHeight);
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("Unknown scroll event type:", scrollToEventName);
419
+ console.error(
420
+ "Unknown scroll event type:",
421
+ scrollToEventName
422
+ );
427
423
  }
428
424
  }
429
425
  };
430
426
  return taskActionScroll;
431
- } else if (plan2.type === "Error") {
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(pageContext, "plan", userPrompt);
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, import_assert2.default)(planResult.plans.length > 0, "No plans found");
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
- hint: Boolean(planCache)
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, import_assert2.default)(
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.actionAgent = new PageTaskExecutor(this.page, {
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, import_utils6.writeDumpFile)({
571
+ this.dumpFile = (0, import_utils4.writeDumpFile)({
556
572
  fileName: `playwright-${this.testId}`,
557
- fileExt: import_utils6.groupedActionDumpFileExt,
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.actionAgent.action(taskPrompt);
580
+ await this.taskExecutor.action(taskPrompt);
565
581
  } catch (e) {
566
582
  error = e;
567
583
  }
568
- if (this.actionAgent.executionDump) {
569
- this.appendDump(this.actionAgent.executionDump);
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.actionAgent.query(demand);
597
+ result = await this.taskExecutor.query(demand);
582
598
  } catch (e) {
583
599
  error = e;
584
600
  }
585
- if (this.actionAgent.executionDump) {
586
- this.appendDump(this.actionAgent.executionDump);
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
- } else if (type === "query") {
627
+ }
628
+ if (type === "query") {
599
629
  return this.aiQuery(taskPrompt);
600
630
  }
601
- throw new Error(`Unknown or Unsupported task type: ${type}, only support 'action' or 'query'`);
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 midSceneAgentKeyId = "_midSceneAgentId";
717
+ var midsceneAgentKeyId = "_midsceneAgentId";
623
718
  var PlaywrightAiFixture = () => {
624
719
  const pageAgentMap = {};
625
720
  const agentForPage = (page, opts) => {
626
- let idForPage = page[midSceneAgentKeyId];
721
+ let idForPage = page[midsceneAgentKeyId];
627
722
  if (!idForPage) {
628
- idForPage = (0, import_crypto.randomUUID)();
629
- page[midSceneAgentKeyId] = idForPage;
630
- const testCase = readTestCache(opts.taskFile, opts.taskTitle) || { aiTasks: [] };
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, { testId: testInfo.testId, taskFile, taskTitle });
643
- await use(async (taskPrompt, opts) => {
644
- await page.waitForLoadState("networkidle");
645
- const actionType = (opts == null ? void 0 : opts.type) || "action";
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
- const taskCacheJson = agent.actionAgent.taskCache.generateTaskCache();
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, { testId: testInfo.testId, taskFile, taskTitle });
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, { testId: testInfo.testId, taskFile, taskTitle });
681
- await use(async function(demand) {
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
  };