@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/README.md ADDED
@@ -0,0 +1,7 @@
1
+ ## Documentation
2
+
3
+ See https://midscenejs.com/ for details.
4
+
5
+ ## License
6
+
7
+ Midscene is MIT licensed.
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/playwright/cache.ts
25
- import path2, { join } from "path";
26
- import fs2 from "fs";
27
- import { writeDumpFile, getDumpDirPath } from "@midscene/core/utils";
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 { alignCoordByTrim, base64Encoded, imageInfoOfBase64 } from "@midscene/core/image";
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 = [Math.floor(rect.left + rect.width / 2), Math.floor(rect.top + rect.height / 2)];
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(screenshotBuffer, captureElementSnapshot, page);
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(pageContext, "locate", param.prompt);
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
- } else if (plan2.type === "Input") {
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(element.center[0], element.center[1]);
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
- } else if (plan2.type === "KeyboardPress") {
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
- } else if (plan2.type === "Tap") {
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(element.center[0], element.center[1]);
346
+ await this.page.mouse.click(
347
+ element.center[0],
348
+ element.center[1]
349
+ );
360
350
  }
361
351
  };
362
352
  return taskActionTap;
363
- } else if (plan2.type === "Hover") {
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(element.center[0], element.center[1]);
360
+ await this.page.mouse.move(
361
+ element.center[0],
362
+ element.center[1]
363
+ );
370
364
  }
371
365
  };
372
366
  return taskActionHover;
373
- } else if (plan2.type === "Scroll") {
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(() => window.innerHeight);
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("Unknown scroll event type:", scrollToEventName);
392
+ console.error(
393
+ "Unknown scroll event type:",
394
+ scrollToEventName
395
+ );
396
396
  }
397
397
  }
398
398
  };
399
399
  return taskActionScroll;
400
- } else if (plan2.type === "Error") {
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(pageContext, "plan", userPrompt);
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
- hint: Boolean(planCache)
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.actionAgent = new PageTaskExecutor(this.page, {
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 = writeDumpFile2({
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.actionAgent.action(taskPrompt);
553
+ await this.taskExecutor.action(taskPrompt);
534
554
  } catch (e) {
535
555
  error = e;
536
556
  }
537
- if (this.actionAgent.executionDump) {
538
- this.appendDump(this.actionAgent.executionDump);
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.actionAgent.query(demand);
570
+ result = await this.taskExecutor.query(demand);
551
571
  } catch (e) {
552
572
  error = e;
553
573
  }
554
- if (this.actionAgent.executionDump) {
555
- this.appendDump(this.actionAgent.executionDump);
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
- } else if (type === "query") {
600
+ }
601
+ if (type === "query") {
568
602
  return this.aiQuery(taskPrompt);
569
603
  }
570
- throw new Error(`Unknown or Unsupported task type: ${type}, only support 'action' or 'query'`);
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 midSceneAgentKeyId = "_midSceneAgentId";
690
+ var midsceneAgentKeyId = "_midsceneAgentId";
592
691
  var PlaywrightAiFixture = () => {
593
692
  const pageAgentMap = {};
594
693
  const agentForPage = (page, opts) => {
595
- let idForPage = page[midSceneAgentKeyId];
694
+ let idForPage = page[midsceneAgentKeyId];
596
695
  if (!idForPage) {
597
696
  idForPage = randomUUID();
598
- page[midSceneAgentKeyId] = idForPage;
599
- const testCase = readTestCache(opts.taskFile, opts.taskTitle) || { aiTasks: [] };
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, { testId: testInfo.testId, taskFile, taskTitle });
612
- await use(async (taskPrompt, opts) => {
613
- await page.waitForLoadState("networkidle");
614
- const actionType = (opts == null ? void 0 : opts.type) || "action";
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
- const taskCacheJson = agent.actionAgent.taskCache.generateTaskCache();
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, { testId: testInfo.testId, taskFile, taskTitle });
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, { testId: testInfo.testId, taskFile, taskTitle });
650
- await use(async function(demand) {
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
  };