@midscene/core 0.18.1-beta-20250611082446.0 → 0.18.1

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 (36) hide show
  1. package/dist/es/ai-model.d.ts +52 -4
  2. package/dist/es/ai-model.js +9 -1
  3. package/dist/es/{chunk-D4ZWVRRG.js → chunk-A7OTPIZJ.js} +19 -6
  4. package/dist/es/chunk-A7OTPIZJ.js.map +1 -0
  5. package/dist/es/{chunk-X2UALJ3B.js → chunk-EYIL4AHP.js} +353 -3
  6. package/dist/es/chunk-EYIL4AHP.js.map +1 -0
  7. package/dist/es/index.d.ts +4 -4
  8. package/dist/es/index.js +19 -26
  9. package/dist/es/index.js.map +1 -1
  10. package/dist/es/{llm-planning-453ffce5.d.ts → llm-planning-a951deb9.d.ts} +2 -2
  11. package/dist/es/{types-00c9086f.d.ts → types-dce56c26.d.ts} +6 -6
  12. package/dist/es/utils.d.ts +1 -1
  13. package/dist/es/utils.js +1 -1
  14. package/dist/lib/ai-model.d.ts +52 -4
  15. package/dist/lib/ai-model.js +10 -2
  16. package/dist/lib/{chunk-D4ZWVRRG.js → chunk-A7OTPIZJ.js} +18 -5
  17. package/dist/lib/chunk-A7OTPIZJ.js.map +1 -0
  18. package/dist/lib/{chunk-X2UALJ3B.js → chunk-EYIL4AHP.js} +371 -21
  19. package/dist/lib/chunk-EYIL4AHP.js.map +1 -0
  20. package/dist/lib/index.d.ts +4 -4
  21. package/dist/lib/index.js +37 -44
  22. package/dist/lib/index.js.map +1 -1
  23. package/dist/lib/{llm-planning-453ffce5.d.ts → llm-planning-a951deb9.d.ts} +2 -2
  24. package/dist/{types/types-00c9086f.d.ts → lib/types-dce56c26.d.ts} +6 -6
  25. package/dist/lib/utils.d.ts +1 -1
  26. package/dist/lib/utils.js +2 -2
  27. package/dist/types/ai-model.d.ts +52 -4
  28. package/dist/types/index.d.ts +4 -4
  29. package/dist/types/{llm-planning-453ffce5.d.ts → llm-planning-a951deb9.d.ts} +2 -2
  30. package/dist/{lib/types-00c9086f.d.ts → types/types-dce56c26.d.ts} +6 -6
  31. package/dist/types/utils.d.ts +1 -1
  32. package/package.json +4 -3
  33. package/dist/es/chunk-D4ZWVRRG.js.map +0 -1
  34. package/dist/es/chunk-X2UALJ3B.js.map +0 -1
  35. package/dist/lib/chunk-D4ZWVRRG.js.map +0 -1
  36. package/dist/lib/chunk-X2UALJ3B.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../src/utils.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport * as path from 'node:path';\nimport { dirname } from 'node:path';\nimport {\n defaultRunDirName,\n getMidsceneRunSubDir,\n} from '@midscene/shared/common';\nimport {\n MIDSCENE_DEBUG_MODE,\n MIDSCENE_OPENAI_INIT_CONFIG_JSON,\n getAIConfig,\n getAIConfigInJson,\n} from '@midscene/shared/env';\nimport { getRunningPkgInfo } from '@midscene/shared/fs';\nimport { assert, logMsg } from '@midscene/shared/utils';\nimport { escapeHtml, ifInBrowser, uuid } from '@midscene/shared/utils';\nimport xss from 'xss';\nimport type { Rect, ReportDumpWithAttributes } from './types';\n\nlet logEnvReady = false;\n\nconst xssOptions = {\n escapeHtml,\n};\nexport const groupedActionDumpFileExt = 'web-dump.json';\n\nconst reportTpl = 'REPLACE_ME_WITH_REPORT_HTML';\n\nfunction getReportTpl() {\n return reportTpl;\n}\n\nexport function replaceStringWithFirstAppearance(\n str: string,\n target: string,\n replacement: string,\n) {\n const index = str.indexOf(target);\n return str.slice(0, index) + replacement + str.slice(index + target.length);\n}\n\nexport function reportHTMLContent(\n dumpData: string | ReportDumpWithAttributes[],\n reportPath?: string,\n): string {\n const tpl = getReportTpl();\n if (!tpl) {\n console.warn('reportTpl is not set, will not write report');\n return '';\n }\n\n const dumpPlaceholder = '{{dump}}';\n\n // verify the template contains the placeholder\n if (!tpl.includes(dumpPlaceholder)) {\n console.warn(\n 'Failed to get the Midscene report template due to the lack of the {{dump}} placeholder. If you are building Midscene.js by yourself, please refer to the contribution guide for more information: https://github.com/web-infra-dev/midscene/blob/main/CONTRIBUTING.md#FAQ',\n );\n return '';\n }\n\n // find the first placeholder position\n const placeholderIndex = tpl.indexOf(dumpPlaceholder);\n\n // split the template into two parts before and after the placeholder\n const firstPart = tpl.substring(0, placeholderIndex);\n const secondPart = tpl.substring(placeholderIndex + dumpPlaceholder.length);\n\n // if reportPath is set, it means we are in write to file mode\n const writeToFile = reportPath && !ifInBrowser;\n let resultContent = '';\n\n // helper function: decide to write to file or append to resultContent\n const appendOrWrite = (content: string): void => {\n if (writeToFile) {\n writeFileSync(reportPath!, `${content}\\n`, {\n flag: 'a',\n });\n } else {\n resultContent += `${content}\\n`;\n }\n };\n\n // if writeToFile is true, write the first part to file, otherwise set the first part to the initial value of resultContent\n if (writeToFile) {\n writeFileSync(reportPath!, firstPart, { flag: 'w' }); // use 'w' flag to overwrite the existing file\n } else {\n resultContent = firstPart;\n }\n\n // generate dump content\n // handle empty data or undefined\n if (\n (Array.isArray(dumpData) && dumpData.length === 0) ||\n typeof dumpData === 'undefined'\n ) {\n const dumpContent =\n '<script type=\"midscene_web_dump\" type=\"application/json\"></script>';\n appendOrWrite(dumpContent);\n }\n // handle string type dumpData\n else if (typeof dumpData === 'string') {\n const dumpContent =\n // biome-ignore lint/style/useTemplate: <explanation> do not use template string here, will cause bundle error\n '<script type=\"midscene_web_dump\" type=\"application/json\">\\n' +\n xss(dumpData, xssOptions) +\n '\\n</script>';\n appendOrWrite(dumpContent);\n }\n // handle array type dumpData\n else {\n // for array, handle each item\n for (let i = 0; i < dumpData.length; i++) {\n const { dumpString, attributes } = dumpData[i];\n const attributesArr = Object.keys(attributes || {}).map((key) => {\n return `${key}=\"${encodeURIComponent(attributes![key])}\"`;\n });\n\n const dumpContent =\n // biome-ignore lint/style/useTemplate: <explanation> do not use template string here, will cause bundle error\n '<script type=\"midscene_web_dump\" type=\"application/json\" ' +\n attributesArr.join(' ') +\n '>\\n' +\n xss(dumpString, xssOptions) +\n '\\n</script>';\n appendOrWrite(dumpContent);\n }\n }\n\n // add the second part\n if (writeToFile) {\n writeFileSync(reportPath!, secondPart, { flag: 'a' });\n return reportPath!;\n }\n\n resultContent += secondPart;\n return resultContent;\n}\n\nexport function writeDumpReport(\n fileName: string,\n dumpData: string | ReportDumpWithAttributes[],\n): string | null {\n if (ifInBrowser) {\n console.log('will not write report in browser');\n return null;\n }\n\n const __dirname = dirname(__filename);\n const midscenePkgInfo = getRunningPkgInfo(__dirname);\n if (!midscenePkgInfo) {\n console.warn('midscenePkgInfo not found, will not write report');\n return null;\n }\n\n const reportPath = path.join(\n getMidsceneRunSubDir('report'),\n `${fileName}.html`,\n );\n\n reportHTMLContent(dumpData, reportPath);\n\n if (process.env.MIDSCENE_DEBUG_LOG_JSON) {\n const jsonPath = `${reportPath}.json`;\n let data = dumpData as ReportDumpWithAttributes[];\n\n if (typeof dumpData === 'string') {\n data = JSON.parse(dumpData) as ReportDumpWithAttributes[];\n }\n\n writeFileSync(\n jsonPath,\n JSON.stringify(\n data.map((item) => ({\n dumpString: JSON.parse(item.dumpString),\n attributes: item.attributes,\n })),\n null,\n 2,\n ),\n );\n\n logMsg(`Midscene - dump file written: ${jsonPath}`);\n }\n\n return reportPath;\n}\n\nexport function writeLogFile(opts: {\n fileName: string;\n fileExt: string;\n fileContent: string;\n type: 'dump' | 'cache' | 'report' | 'tmp';\n generateReport?: boolean;\n}) {\n if (ifInBrowser) {\n return '/mock/report.html';\n }\n const { fileName, fileExt, fileContent, type = 'dump' } = opts;\n const targetDir = getMidsceneRunSubDir(type);\n // Ensure directory exists\n if (!logEnvReady) {\n assert(targetDir, 'logDir should be set before writing dump file');\n\n // gitIgnore in the parent directory\n const gitIgnorePath = path.join(targetDir, '../../.gitignore');\n const gitPath = path.join(targetDir, '../../.git');\n let gitIgnoreContent = '';\n\n if (existsSync(gitPath)) {\n // if the git path exists, we need to add the log folder to the git ignore file\n if (existsSync(gitIgnorePath)) {\n gitIgnoreContent = readFileSync(gitIgnorePath, 'utf-8');\n }\n\n // ignore the log folder\n if (!gitIgnoreContent.includes(`${defaultRunDirName}/`)) {\n writeFileSync(\n gitIgnorePath,\n `${gitIgnoreContent}\\n# Midscene.js dump files\\n${defaultRunDirName}/dump\\n${defaultRunDirName}/report\\n${defaultRunDirName}/tmp\\n${defaultRunDirName}/log\\n`,\n 'utf-8',\n );\n }\n }\n\n logEnvReady = true;\n }\n\n const filePath = path.join(targetDir, `${fileName}.${fileExt}`);\n\n if (type !== 'dump') {\n // do not write dump file any more\n writeFileSync(filePath, fileContent);\n }\n\n if (opts?.generateReport) {\n return writeDumpReport(fileName, fileContent);\n }\n\n return filePath;\n}\n\nexport function getTmpDir(): string | null {\n try {\n const runningPkgInfo = getRunningPkgInfo();\n if (!runningPkgInfo) {\n return null;\n }\n const { name } = runningPkgInfo;\n const tmpPath = path.join(tmpdir(), name);\n mkdirSync(tmpPath, { recursive: true });\n return tmpPath;\n } catch (e) {\n return null;\n }\n}\n\nexport function getTmpFile(fileExtWithoutDot: string): string | null {\n if (ifInBrowser) {\n return null;\n }\n const tmpDir = getTmpDir();\n const filename = `${uuid()}.${fileExtWithoutDot}`;\n return path.join(tmpDir!, filename);\n}\n\nexport function overlapped(container: Rect, target: Rect) {\n // container and the target have some part overlapped\n return (\n container.left < target.left + target.width &&\n container.left + container.width > target.left &&\n container.top < target.top + target.height &&\n container.top + container.height > target.top\n );\n}\n\nexport async function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function replacerForPageObject(key: string, value: any) {\n if (value && value.constructor?.name === 'Page') {\n return '[Page object]';\n }\n if (value && value.constructor?.name === 'Browser') {\n return '[Browser object]';\n }\n return value;\n}\n\nexport function stringifyDumpData(data: any, indents?: number) {\n return JSON.stringify(data, replacerForPageObject, indents);\n}\n\ndeclare const __VERSION__: string;\n\nexport function getVersion() {\n return __VERSION__;\n}\n\nfunction debugLog(...message: any[]) {\n const debugMode = getAIConfig(MIDSCENE_DEBUG_MODE);\n if (debugMode) {\n console.log('[Midscene]', ...message);\n }\n}\n\nlet lastReportedRepoUrl = '';\nexport function uploadTestInfoToServer({ testUrl }: { testUrl: string }) {\n let repoUrl = '';\n let userEmail = '';\n\n const extraConfig = getAIConfigInJson(MIDSCENE_OPENAI_INIT_CONFIG_JSON);\n const serverUrl = extraConfig?.REPORT_SERVER_URL;\n\n try {\n repoUrl = execSync('git config --get remote.origin.url').toString().trim();\n userEmail = execSync('git config --get user.email').toString().trim();\n } catch (error) {\n debugLog('Failed to get git info:', error);\n }\n\n // Only upload test info if:\n // 1. Server URL is configured AND\n // 2. Either:\n // - We have a repo URL that's different from last reported one (to avoid duplicate reports)\n // - OR we don't have a repo URL but have a test URL (for non-git environments)\n if (\n serverUrl &&\n ((repoUrl && repoUrl !== lastReportedRepoUrl) || (!repoUrl && testUrl))\n ) {\n debugLog('Uploading test info to server', {\n serverUrl,\n repoUrl,\n testUrl,\n userEmail,\n });\n\n fetch(serverUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n repo_url: repoUrl,\n test_url: testUrl,\n user_email: userEmail,\n }),\n })\n .then((response) => response.json())\n .then((data) => {\n debugLog('Successfully uploaded test info to server:', data);\n })\n .catch((error) =>\n debugLog('Failed to upload test info to server:', error),\n );\n lastReportedRepoUrl = repoUrl;\n }\n}\n"]}
@@ -47,6 +47,14 @@ import { vlLocateMode } from "@midscene/shared/env";
47
47
  import { treeToList } from "@midscene/shared/extractor";
48
48
  import { compositeElementInfoImg } from "@midscene/shared/img";
49
49
  import { getDebug } from "@midscene/shared/logger";
50
+ var AIActionType = /* @__PURE__ */ ((AIActionType2) => {
51
+ AIActionType2[AIActionType2["ASSERT"] = 0] = "ASSERT";
52
+ AIActionType2[AIActionType2["INSPECT_ELEMENT"] = 1] = "INSPECT_ELEMENT";
53
+ AIActionType2[AIActionType2["EXTRACT_DATA"] = 2] = "EXTRACT_DATA";
54
+ AIActionType2[AIActionType2["PLAN"] = 3] = "PLAN";
55
+ AIActionType2[AIActionType2["DESCRIBE_ELEMENT"] = 4] = "DESCRIBE_ELEMENT";
56
+ return AIActionType2;
57
+ })(AIActionType || {});
50
58
  async function callAiFn(msgs, AIActionTypeValue) {
51
59
  assert(
52
60
  checkAIConfig(),
@@ -694,6 +702,11 @@ async function describeUserPage(context, opt) {
694
702
  const treeRoot = context.tree;
695
703
  const idElementMap = {};
696
704
  const flatElements = treeToList2(treeRoot);
705
+ if (opt?.domIncluded === true && flatElements.length >= 5e3) {
706
+ console.warn(
707
+ 'The number of elements is too large, it may cause the prompt to be too long, please use domIncluded: "visible-only" to reduce the number of elements'
708
+ );
709
+ }
697
710
  flatElements.forEach((element) => {
698
711
  idElementMap[element.id] = element;
699
712
  if (typeof element.indexId !== "undefined") {
@@ -701,12 +714,13 @@ async function describeUserPage(context, opt) {
701
714
  }
702
715
  });
703
716
  let pageDescription = "";
717
+ const visibleOnly = opt?.visibleOnly ?? opt?.domIncluded === "visible-only";
704
718
  if (opt?.domIncluded) {
705
719
  const contentTree = await descriptionOfTree(
706
720
  treeRoot,
707
721
  opt?.truncateTextLength,
708
722
  opt?.filterNonTextContent,
709
- opt?.visibleOnly
723
+ visibleOnly
710
724
  );
711
725
  const sizeDescription = describeSize({ width, height });
712
726
  pageDescription = `The size of the page: ${sizeDescription}
@@ -1288,6 +1302,7 @@ async function call(messages, AIActionTypeValue, responseFormat) {
1288
1302
  const model = getModelName();
1289
1303
  let content;
1290
1304
  let usage;
1305
+ let timeCost;
1291
1306
  const commonConfig = {
1292
1307
  temperature: vlLocateMode2() === "vlm-ui-tars" ? 0 : 0.1,
1293
1308
  stream: false,
@@ -1300,12 +1315,14 @@ async function call(messages, AIActionTypeValue, responseFormat) {
1300
1315
  debugCall(`sending request to ${model}`);
1301
1316
  let result;
1302
1317
  try {
1318
+ const startTime2 = Date.now();
1303
1319
  result = await completion.create({
1304
1320
  model,
1305
1321
  messages,
1306
1322
  response_format: responseFormat,
1307
1323
  ...commonConfig
1308
1324
  });
1325
+ timeCost = Date.now() - startTime2;
1309
1326
  } catch (e) {
1310
1327
  const newError = new Error(
1311
1328
  `failed to call AI model service: ${e.message}. Trouble shooting: https://midscenejs.com/model-provider.html`,
@@ -1343,6 +1360,7 @@ async function call(messages, AIActionTypeValue, responseFormat) {
1343
1360
  }
1344
1361
  return content2;
1345
1362
  };
1363
+ const startTime2 = Date.now();
1346
1364
  const result = await completion.create({
1347
1365
  model,
1348
1366
  system: "You are a versatile professional in software UI automation",
@@ -1353,11 +1371,20 @@ async function call(messages, AIActionTypeValue, responseFormat) {
1353
1371
  response_format: responseFormat,
1354
1372
  ...commonConfig
1355
1373
  });
1374
+ timeCost = Date.now() - startTime2;
1356
1375
  content = result.content[0].text;
1357
1376
  assert3(content, "empty content");
1358
1377
  usage = result.usage;
1359
1378
  }
1360
- return { content: content || "", usage };
1379
+ return {
1380
+ content: content || "",
1381
+ usage: {
1382
+ prompt_tokens: usage?.prompt_tokens ?? 0,
1383
+ completion_tokens: usage?.completion_tokens ?? 0,
1384
+ total_tokens: usage?.total_tokens ?? 0,
1385
+ time_cost: timeCost ?? 0
1386
+ }
1387
+ };
1361
1388
  }
1362
1389
  async function callToGetJSONObject(messages, AIActionTypeValue) {
1363
1390
  let responseFormat;
@@ -1435,6 +1462,325 @@ function safeParseJson(input) {
1435
1462
  throw Error(`failed to parse json response: ${input}`);
1436
1463
  }
1437
1464
 
1465
+ // src/ai-model/prompt/playwright-generator.ts
1466
+ import { PLAYWRIGHT_EXAMPLE_CODE } from "@midscene/shared/constants";
1467
+ var getScreenshotsForLLM = (events, maxScreenshots = 1) => {
1468
+ const eventsWithScreenshots = events.filter(
1469
+ (event) => event.screenshotBefore || event.screenshotAfter || event.screenshotWithBox
1470
+ );
1471
+ const sortedEvents = [...eventsWithScreenshots].sort((a, b) => {
1472
+ if (a.type === "navigation" && b.type !== "navigation")
1473
+ return -1;
1474
+ if (a.type !== "navigation" && b.type === "navigation")
1475
+ return 1;
1476
+ if (a.type === "click" && b.type !== "click")
1477
+ return -1;
1478
+ if (a.type !== "click" && b.type === "click")
1479
+ return 1;
1480
+ return 0;
1481
+ });
1482
+ const screenshots = [];
1483
+ for (const event of sortedEvents) {
1484
+ const screenshot = event.screenshotWithBox || event.screenshotAfter || event.screenshotBefore;
1485
+ if (screenshot && !screenshots.includes(screenshot)) {
1486
+ screenshots.push(screenshot);
1487
+ if (screenshots.length >= maxScreenshots)
1488
+ break;
1489
+ }
1490
+ }
1491
+ return screenshots;
1492
+ };
1493
+ var filterEventsByType = (events) => {
1494
+ return {
1495
+ navigationEvents: events.filter((event) => event.type === "navigation"),
1496
+ clickEvents: events.filter((event) => event.type === "click"),
1497
+ inputEvents: events.filter((event) => event.type === "input"),
1498
+ scrollEvents: events.filter((event) => event.type === "scroll")
1499
+ };
1500
+ };
1501
+ var createEventCounts = (filteredEvents, totalEvents) => {
1502
+ return {
1503
+ navigation: filteredEvents.navigationEvents.length,
1504
+ click: filteredEvents.clickEvents.length,
1505
+ input: filteredEvents.inputEvents.length,
1506
+ scroll: filteredEvents.scrollEvents.length,
1507
+ total: totalEvents
1508
+ };
1509
+ };
1510
+ var extractInputDescriptions = (inputEvents) => {
1511
+ return inputEvents.map((event) => ({
1512
+ description: event.elementDescription || "",
1513
+ value: event.value || ""
1514
+ })).filter((item) => item.description && item.value);
1515
+ };
1516
+ var processEventsForLLM = (events) => {
1517
+ return events.map((event) => ({
1518
+ type: event.type,
1519
+ timestamp: event.timestamp,
1520
+ url: event.url,
1521
+ title: event.title,
1522
+ elementDescription: event.elementDescription,
1523
+ value: event.value,
1524
+ pageInfo: event.pageInfo,
1525
+ elementRect: event.elementRect
1526
+ }));
1527
+ };
1528
+ var prepareEventSummary = (events, options = {}) => {
1529
+ const filteredEvents = filterEventsByType(events);
1530
+ const eventCounts = createEventCounts(filteredEvents, events.length);
1531
+ const startUrl = filteredEvents.navigationEvents.length > 0 ? filteredEvents.navigationEvents[0].url || "" : "";
1532
+ const pageTitles = filteredEvents.navigationEvents.map((event) => event.title).filter((title) => Boolean(title)).slice(0, 5);
1533
+ const clickDescriptions = filteredEvents.clickEvents.map((event) => event.elementDescription).filter((desc) => Boolean(desc)).slice(0, 10);
1534
+ const inputDescriptions = extractInputDescriptions(
1535
+ filteredEvents.inputEvents
1536
+ ).slice(0, 10);
1537
+ const urls = filteredEvents.navigationEvents.map((e) => e.url).filter((url) => Boolean(url)).slice(0, 5);
1538
+ const processedEvents = processEventsForLLM(events);
1539
+ return {
1540
+ testName: options.testName || "Automated test from recorded events",
1541
+ startUrl,
1542
+ eventCounts,
1543
+ pageTitles,
1544
+ urls,
1545
+ clickDescriptions,
1546
+ inputDescriptions,
1547
+ events: processedEvents
1548
+ };
1549
+ };
1550
+ var createMessageContent = (promptText, screenshots = [], includeScreenshots = true) => {
1551
+ const messageContent = [
1552
+ {
1553
+ type: "text",
1554
+ text: promptText
1555
+ }
1556
+ ];
1557
+ if (includeScreenshots && screenshots.length > 0) {
1558
+ messageContent.unshift({
1559
+ type: "text",
1560
+ text: "Here are screenshots from the recording session to help you understand the context:"
1561
+ });
1562
+ screenshots.forEach((screenshot) => {
1563
+ messageContent.push({
1564
+ type: "image_url",
1565
+ image_url: {
1566
+ url: screenshot
1567
+ }
1568
+ });
1569
+ });
1570
+ }
1571
+ return messageContent;
1572
+ };
1573
+ var validateEvents = (events) => {
1574
+ if (!events.length) {
1575
+ throw new Error("No events provided for test generation");
1576
+ }
1577
+ };
1578
+ var generatePlaywrightTest = async (events, options = {}) => {
1579
+ validateEvents(events);
1580
+ const summary = prepareEventSummary(events, {
1581
+ testName: options.testName,
1582
+ maxScreenshots: options.maxScreenshots || 3
1583
+ });
1584
+ const playwrightSummary = {
1585
+ ...summary,
1586
+ waitForNetworkIdle: options.waitForNetworkIdle !== false,
1587
+ waitForNetworkIdleTimeout: options.waitForNetworkIdleTimeout || 2e3,
1588
+ viewportSize: options.viewportSize || { width: 1280, height: 800 }
1589
+ };
1590
+ const screenshots = getScreenshotsForLLM(events, options.maxScreenshots || 3);
1591
+ const promptText = `Generate a Playwright test using @midscene/web/playwright that reproduces this recorded browser session. The test should be based on the following events and follow the structure of the example provided. Make the test descriptive with appropriate assertions and validations.
1592
+
1593
+ Event Summary:
1594
+ ${JSON.stringify(playwrightSummary, null, 2)}
1595
+
1596
+ Generated code should:
1597
+ 1. Import required dependencies
1598
+ 2. Set up the test with proper configuration
1599
+ 3. Include a beforeEach hook to navigate to the starting URL
1600
+ 4. Implement a test that uses Midscene AI methods (aiTap, aiInput, aiAssert, etc.)
1601
+ 5. Include appropriate assertions and validations
1602
+ 6. Follow best practices for Playwright tests
1603
+ 7. Be ready to execute without further modification
1604
+
1605
+ Respond ONLY with the complete Playwright test code, no explanations.`;
1606
+ const messageContent = createMessageContent(
1607
+ promptText,
1608
+ screenshots,
1609
+ options.includeScreenshots !== false
1610
+ );
1611
+ const systemPrompt = `You are an expert test automation engineer specializing in Playwright and Midscene.
1612
+ Your task is to generate a complete, executable Playwright test using @midscene/web/playwright that reproduces a recorded browser session.
1613
+
1614
+ ${PLAYWRIGHT_EXAMPLE_CODE}`;
1615
+ const prompt = [
1616
+ {
1617
+ role: "system",
1618
+ content: systemPrompt
1619
+ },
1620
+ {
1621
+ role: "user",
1622
+ content: messageContent
1623
+ }
1624
+ ];
1625
+ const response = await call(prompt, 2 /* EXTRACT_DATA */);
1626
+ if (response?.content && typeof response.content === "string") {
1627
+ return response.content;
1628
+ }
1629
+ throw new Error("Failed to generate Playwright test code");
1630
+ };
1631
+
1632
+ // src/ai-model/prompt/yaml-generator.ts
1633
+ import { YAML_EXAMPLE_CODE } from "@midscene/shared/constants";
1634
+ var getScreenshotsForLLM2 = (events, maxScreenshots = 1) => {
1635
+ const eventsWithScreenshots = events.filter(
1636
+ (event) => event.screenshotBefore || event.screenshotAfter || event.screenshotWithBox
1637
+ );
1638
+ const sortedEvents = [...eventsWithScreenshots].sort((a, b) => {
1639
+ if (a.type === "navigation" && b.type !== "navigation")
1640
+ return -1;
1641
+ if (a.type !== "navigation" && b.type === "navigation")
1642
+ return 1;
1643
+ if (a.type === "click" && b.type !== "click")
1644
+ return -1;
1645
+ if (a.type !== "click" && b.type === "click")
1646
+ return 1;
1647
+ return 0;
1648
+ });
1649
+ const screenshots = [];
1650
+ for (const event of sortedEvents) {
1651
+ const screenshot = event.screenshotWithBox || event.screenshotAfter || event.screenshotBefore;
1652
+ if (screenshot && !screenshots.includes(screenshot)) {
1653
+ screenshots.push(screenshot);
1654
+ if (screenshots.length >= maxScreenshots)
1655
+ break;
1656
+ }
1657
+ }
1658
+ return screenshots;
1659
+ };
1660
+ var filterEventsByType2 = (events) => {
1661
+ return {
1662
+ navigationEvents: events.filter((event) => event.type === "navigation"),
1663
+ clickEvents: events.filter((event) => event.type === "click"),
1664
+ inputEvents: events.filter((event) => event.type === "input"),
1665
+ scrollEvents: events.filter((event) => event.type === "scroll")
1666
+ };
1667
+ };
1668
+ var createEventCounts2 = (filteredEvents, totalEvents) => {
1669
+ return {
1670
+ navigation: filteredEvents.navigationEvents.length,
1671
+ click: filteredEvents.clickEvents.length,
1672
+ input: filteredEvents.inputEvents.length,
1673
+ scroll: filteredEvents.scrollEvents.length,
1674
+ total: totalEvents
1675
+ };
1676
+ };
1677
+ var extractInputDescriptions2 = (inputEvents) => {
1678
+ return inputEvents.map((event) => ({
1679
+ description: event.elementDescription || "",
1680
+ value: event.value || ""
1681
+ })).filter((item) => item.description && item.value);
1682
+ };
1683
+ var processEventsForLLM2 = (events) => {
1684
+ return events.map((event) => ({
1685
+ type: event.type,
1686
+ timestamp: event.timestamp,
1687
+ url: event.url,
1688
+ title: event.title,
1689
+ elementDescription: event.elementDescription,
1690
+ value: event.value,
1691
+ pageInfo: event.pageInfo,
1692
+ elementRect: event.elementRect
1693
+ }));
1694
+ };
1695
+ var prepareEventSummary2 = (events, options = {}) => {
1696
+ const filteredEvents = filterEventsByType2(events);
1697
+ const eventCounts = createEventCounts2(filteredEvents, events.length);
1698
+ const startUrl = filteredEvents.navigationEvents.length > 0 ? filteredEvents.navigationEvents[0].url || "" : "";
1699
+ const pageTitles = filteredEvents.navigationEvents.map((event) => event.title).filter((title) => Boolean(title)).slice(0, 5);
1700
+ const clickDescriptions = filteredEvents.clickEvents.map((event) => event.elementDescription).filter((desc) => Boolean(desc)).slice(0, 10);
1701
+ const inputDescriptions = extractInputDescriptions2(
1702
+ filteredEvents.inputEvents
1703
+ ).slice(0, 10);
1704
+ const urls = filteredEvents.navigationEvents.map((e) => e.url).filter((url) => Boolean(url)).slice(0, 5);
1705
+ const processedEvents = processEventsForLLM2(events);
1706
+ return {
1707
+ testName: options.testName || "Automated test from recorded events",
1708
+ startUrl,
1709
+ eventCounts,
1710
+ pageTitles,
1711
+ urls,
1712
+ clickDescriptions,
1713
+ inputDescriptions,
1714
+ events: processedEvents
1715
+ };
1716
+ };
1717
+ var validateEvents2 = (events) => {
1718
+ if (!events.length) {
1719
+ throw new Error("No events provided for test generation");
1720
+ }
1721
+ };
1722
+ var generateYamlTest = async (events, options = {}) => {
1723
+ try {
1724
+ validateEvents2(events);
1725
+ const summary = prepareEventSummary2(events, {
1726
+ testName: options.testName,
1727
+ maxScreenshots: options.maxScreenshots || 3
1728
+ });
1729
+ const yamlSummary = {
1730
+ ...summary,
1731
+ includeTimestamps: options.includeTimestamps || false
1732
+ };
1733
+ const screenshots = getScreenshotsForLLM2(
1734
+ events,
1735
+ options.maxScreenshots || 3
1736
+ );
1737
+ const prompt = [
1738
+ {
1739
+ role: "system",
1740
+ content: `You are an expert in Midscene.js YAML test generation. Generate clean, accurate YAML following these rules: ${YAML_EXAMPLE_CODE}`
1741
+ },
1742
+ {
1743
+ role: "user",
1744
+ content: `Generate YAML test for Midscene.js automation from recorded browser events.
1745
+
1746
+ Event Summary:
1747
+ ${JSON.stringify(yamlSummary, null, 2)}
1748
+
1749
+ Convert events:
1750
+ - navigation → target.url
1751
+ - click → aiTap with element description
1752
+ - input → aiInput with value and locate
1753
+ - scroll → aiScroll with appropriate direction
1754
+ - Add aiAssert for important state changes
1755
+
1756
+ Respond with YAML only, no explanations.`
1757
+ }
1758
+ ];
1759
+ if (screenshots.length > 0) {
1760
+ prompt.push({
1761
+ role: "user",
1762
+ content: "Here are screenshots from the recording session to help you understand the context:"
1763
+ });
1764
+ prompt.push({
1765
+ role: "user",
1766
+ content: screenshots.map((screenshot) => ({
1767
+ type: "image_url",
1768
+ image_url: {
1769
+ url: screenshot
1770
+ }
1771
+ }))
1772
+ });
1773
+ }
1774
+ const response = await call(prompt, 2 /* EXTRACT_DATA */);
1775
+ if (response?.content && typeof response.content === "string") {
1776
+ return response.content;
1777
+ }
1778
+ throw new Error("Failed to generate YAML test configuration");
1779
+ } catch (error) {
1780
+ throw new Error(`Failed to generate YAML test: ${error}`);
1781
+ }
1782
+ };
1783
+
1438
1784
  // src/ai-model/inspect.ts
1439
1785
  import {
1440
1786
  MIDSCENE_USE_QWEN_VL as MIDSCENE_USE_QWEN_VL2,
@@ -2215,10 +2561,14 @@ export {
2215
2561
  systemPromptToLocateElement,
2216
2562
  elementByPositionWithElementInfo,
2217
2563
  describeUserPage,
2564
+ call,
2218
2565
  callToGetJSONObject,
2566
+ AIActionType,
2219
2567
  callAiFn,
2220
2568
  adaptBboxToRect,
2221
2569
  expandSearchArea,
2570
+ generatePlaywrightTest,
2571
+ generateYamlTest,
2222
2572
  AiLocateElement,
2223
2573
  AiLocateSection,
2224
2574
  AiExtractElementInfo,
@@ -2228,4 +2578,4 @@ export {
2228
2578
  resizeImageForUiTars
2229
2579
  };
2230
2580
 
2231
- //# sourceMappingURL=chunk-X2UALJ3B.js.map
2581
+ //# sourceMappingURL=chunk-EYIL4AHP.js.map