misoai-web 1.5.6 → 1.5.7

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 (75) hide show
  1. package/dist/es/agent.js +124 -21
  2. package/dist/es/agent.js.map +1 -1
  3. package/dist/es/bridge-mode-browser.js +10 -9
  4. package/dist/es/bridge-mode-browser.js.map +1 -1
  5. package/dist/es/bridge-mode.js +126 -23
  6. package/dist/es/bridge-mode.js.map +1 -1
  7. package/dist/es/chrome-extension.js +132 -28
  8. package/dist/es/chrome-extension.js.map +1 -1
  9. package/dist/es/index.js +140 -21
  10. package/dist/es/index.js.map +1 -1
  11. package/dist/es/midscene-playground.js +124 -21
  12. package/dist/es/midscene-playground.js.map +1 -1
  13. package/dist/es/midscene-server.js.map +1 -1
  14. package/dist/es/playground.js +124 -21
  15. package/dist/es/playground.js.map +1 -1
  16. package/dist/es/playwright-report.js +1 -1
  17. package/dist/es/playwright-report.js.map +1 -1
  18. package/dist/es/playwright.js +140 -21
  19. package/dist/es/playwright.js.map +1 -1
  20. package/dist/es/puppeteer-agent-launcher.js +124 -21
  21. package/dist/es/puppeteer-agent-launcher.js.map +1 -1
  22. package/dist/es/puppeteer.js +124 -21
  23. package/dist/es/puppeteer.js.map +1 -1
  24. package/dist/es/ui-utils.js.map +1 -1
  25. package/dist/es/utils.js +7 -4
  26. package/dist/es/utils.js.map +1 -1
  27. package/dist/es/yaml.js +24 -0
  28. package/dist/es/yaml.js.map +1 -1
  29. package/dist/lib/agent.js +122 -19
  30. package/dist/lib/agent.js.map +1 -1
  31. package/dist/lib/bridge-mode-browser.js +10 -9
  32. package/dist/lib/bridge-mode-browser.js.map +1 -1
  33. package/dist/lib/bridge-mode.js +124 -21
  34. package/dist/lib/bridge-mode.js.map +1 -1
  35. package/dist/lib/chrome-extension.js +130 -26
  36. package/dist/lib/chrome-extension.js.map +1 -1
  37. package/dist/lib/index.js +138 -19
  38. package/dist/lib/index.js.map +1 -1
  39. package/dist/lib/midscene-playground.js +122 -19
  40. package/dist/lib/midscene-playground.js.map +1 -1
  41. package/dist/lib/midscene-server.js.map +1 -1
  42. package/dist/lib/playground.js +122 -19
  43. package/dist/lib/playground.js.map +1 -1
  44. package/dist/lib/playwright-report.js +1 -1
  45. package/dist/lib/playwright-report.js.map +1 -1
  46. package/dist/lib/playwright.js +138 -19
  47. package/dist/lib/playwright.js.map +1 -1
  48. package/dist/lib/puppeteer-agent-launcher.js +122 -19
  49. package/dist/lib/puppeteer-agent-launcher.js.map +1 -1
  50. package/dist/lib/puppeteer.js +122 -19
  51. package/dist/lib/puppeteer.js.map +1 -1
  52. package/dist/lib/ui-utils.js.map +1 -1
  53. package/dist/lib/utils.js +7 -4
  54. package/dist/lib/utils.js.map +1 -1
  55. package/dist/lib/yaml.js +24 -0
  56. package/dist/lib/yaml.js.map +1 -1
  57. package/dist/types/agent.d.ts +10 -7
  58. package/dist/types/bridge-mode-browser.d.ts +2 -3
  59. package/dist/types/bridge-mode.d.ts +2 -3
  60. package/dist/types/{browser-aec1055d.d.ts → browser-9b472ffb.d.ts} +1 -1
  61. package/dist/types/chrome-extension.d.ts +2 -3
  62. package/dist/types/index.d.ts +1 -2
  63. package/dist/types/midscene-server.d.ts +1 -2
  64. package/dist/types/{page-86ab0fe1.d.ts → page-ed0ecb44.d.ts} +19 -9
  65. package/dist/types/playground.d.ts +2 -3
  66. package/dist/types/playwright.d.ts +9 -2
  67. package/dist/types/puppeteer-agent-launcher.d.ts +1 -2
  68. package/dist/types/puppeteer.d.ts +6 -5
  69. package/dist/types/ui-utils.d.ts +1 -1
  70. package/dist/types/utils.d.ts +1 -2
  71. package/dist/types/yaml.d.ts +1 -2
  72. package/iife-script/htmlElement.js +51 -73
  73. package/iife-script/htmlElementDebug.js +33 -54
  74. package/package.json +23 -23
  75. package/LICENSE +0 -21
@@ -52,7 +52,7 @@ function printReportMsg(filepath) {
52
52
  (0, import_utils2.logMsg)(`Midscene - report file updated: ${filepath}`);
53
53
  }
54
54
  function replaceIllegalPathCharsAndSpace(str) {
55
- return str.replace(/[/\\:*?"<>| ]/g, "-");
55
+ return str.replace(/[:*?"<>| ]/g, "-");
56
56
  }
57
57
 
58
58
  // src/playwright/reporter/index.ts
@@ -1 +1 @@
1
- {"version":3,"mappings":"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","names":["import_utils","dayjs"],"ignoreList":[],"sources":["../../src/playwright/reporter/index.ts","../../src/common/utils.ts"],"sourcesContent":["import {\n printReportMsg,\n replaceIllegalPathCharsAndSpace,\n reportFileName,\n} from '@/common/utils';\nimport type { ReportDumpWithAttributes } from 'misoai-core';\nimport { writeDumpReport } from 'misoai-core/utils';\nimport type {\n FullConfig,\n FullResult,\n Reporter,\n Suite,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\n\nfunction logger(...message: any[]) {\n if (process.env.DEBUG === 'true') {\n console.log('Midscene e2e report:', ...message);\n }\n}\n\nconst testDataList: Array<ReportDumpWithAttributes> = [];\nlet mergedFilename: string;\nconst testTitleToFilename: Map<string, string> = new Map();\n\nfunction getStableFilename(testTitle: string): string {\n if (!testTitleToFilename.has(testTitle)) {\n // use reportFileName to generate the base filename\n // only replace the illegal characters in the file system: /, \\, :, *, ?, \", <, >, |\n const baseTag = `playwright-${replaceIllegalPathCharsAndSpace(testTitle)}`;\n const generatedFilename = reportFileName(baseTag);\n testTitleToFilename.set(testTitle, generatedFilename);\n }\n return testTitleToFilename.get(testTitle)!;\n}\n\nfunction updateReport(mode: 'merged' | 'separate', testId?: string) {\n if (mode === 'separate') {\n // in separate mode, find the data for the corresponding testID and generate a separate report\n const testData = testDataList.find(\n (data) => data.attributes?.playwright_test_id === testId,\n );\n\n if (testData) {\n // use the stable filename\n const stableFilename = getStableFilename(\n testData.attributes?.playwright_test_title,\n );\n\n const reportPath = writeDumpReport(stableFilename, [testData]);\n reportPath && printReportMsg(reportPath);\n }\n } else if (mode === 'merged') {\n // in merged mode, write all test data into one file\n if (!mergedFilename) {\n mergedFilename = reportFileName('playwright-merged');\n }\n\n const reportPath = writeDumpReport(mergedFilename, testDataList);\n reportPath && printReportMsg(reportPath);\n } else {\n throw new Error(\n `Unknown reporter type in playwright config: ${mode}, only support 'merged' or 'separate'`,\n );\n }\n}\n\nfunction getMode(reporterType: string) {\n if (!reporterType) {\n return 'merged';\n }\n\n if (reporterType !== 'merged' && reporterType !== 'separate') {\n throw new Error(\n `Unknown reporter type in playwright config: ${reporterType}, only support 'merged' or 'separate'`,\n );\n }\n\n return reporterType;\n}\n\nclass MidsceneReporter implements Reporter {\n mode?: 'merged' | 'separate';\n\n async onBegin(config: FullConfig, suite: Suite) {\n const reporterType = config.reporter?.[1]?.[1]?.type;\n\n this.mode = getMode(reporterType);\n\n // const suites = suite.allTests();\n // logger(`Starting the run with ${suites.length} tests`);\n }\n\n onTestBegin(test: TestCase, _result: TestResult) {\n // logger(`Starting test ${test.title}`);\n }\n\n onTestEnd(test: TestCase, result: TestResult) {\n const dumpAnnotation = test.annotations.find((annotation) => {\n return annotation.type === 'MIDSCENE_DUMP_ANNOTATION';\n });\n if (!dumpAnnotation?.description) return;\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n const testData: ReportDumpWithAttributes = {\n dumpString: dumpAnnotation.description,\n attributes: {\n playwright_test_id: testId,\n playwright_test_title: `${test.title}${retry}`,\n playwright_test_status: result.status,\n playwright_test_duration: result.duration,\n },\n };\n\n testDataList.push(testData);\n\n updateReport(this.mode!, testId);\n\n test.annotations = test.annotations.filter(\n (annotation) => annotation.type !== 'MIDSCENE_DUMP_ANNOTATION',\n );\n }\n\n onEnd(result: FullResult) {\n updateReport(this.mode!);\n\n logger(`Finished the run: ${result.status}`);\n }\n}\n\nexport default MidsceneReporter;\n","import type { StaticPage } from '@/playground';\nimport type {\n BaseElement,\n ElementTreeNode,\n PlanningLocateParam,\n PlaywrightParserOpt,\n UIContext,\n} from 'misoai-core';\nimport { elementByPositionWithElementInfo } from 'misoai-core/ai-model';\nimport { uploadTestInfoToServer } from 'misoai-core/utils';\nimport { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from 'misoai-shared/env';\nimport type { ElementInfo } from 'misoai-shared/extractor';\nimport {\n generateElementByPosition,\n getNodeFromCacheList,\n traverseTree,\n treeToList,\n} from 'misoai-shared/extractor';\nimport { resizeImgBase64 } from 'misoai-shared/img';\nimport type { DebugFunction } from 'misoai-shared/logger';\nimport { assert, logMsg, uuid } from 'misoai-shared/utils';\nimport dayjs from 'dayjs';\nimport type { Page as PlaywrightPage } from 'playwright';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport { WebElementInfo } from '../web-element';\nimport type { WebPage } from './page';\n\nexport type WebUIContext = UIContext<WebElementInfo> & {\n url: string;\n};\n\nexport async function parseContextFromWebPage(\n page: WebPage,\n _opt?: PlaywrightParserOpt,\n): Promise<WebUIContext> {\n assert(page, 'page is required');\n if ((page as StaticPage)._forceUsePageContext) {\n return await (page as any)._forceUsePageContext();\n }\n const url = await page.url();\n uploadTestInfoToServer({ testUrl: url });\n\n let screenshotBase64: string;\n let tree: ElementTreeNode<ElementInfo>;\n\n await Promise.all([\n page.screenshotBase64().then((base64) => {\n screenshotBase64 = base64;\n }),\n page.getElementsNodeTree().then(async (treeRoot) => {\n tree = treeRoot;\n }),\n ]);\n\n const webTree = traverseTree(tree!, (elementInfo) => {\n const { rect, id, content, attributes, locator, indexId } = elementInfo;\n return new WebElementInfo({\n rect,\n locator,\n id,\n content,\n attributes,\n indexId,\n });\n });\n\n assert(screenshotBase64!, 'screenshotBase64 is required');\n\n const elementsInfo = treeToList(webTree);\n const size = await page.size();\n\n if (size.dpr && size.dpr > 1) {\n // console.time('resizeImgBase64');\n screenshotBase64 = await resizeImgBase64(screenshotBase64, {\n width: size.width,\n height: size.height,\n });\n // console.timeEnd('resizeImgBase64');\n }\n\n return {\n content: elementsInfo!,\n tree: webTree,\n size,\n screenshotBase64: screenshotBase64!,\n url,\n };\n}\n\nexport function reportFileName(tag = 'web') {\n const reportTagName = getAIConfig(MIDSCENE_REPORT_TAG_NAME);\n const dateTimeInFileName = dayjs().format('YYYY-MM-DD_HH-mm-ss');\n // ensure uniqueness at the same time\n const uniqueId = uuid().substring(0, 8);\n return `${reportTagName || tag}-${dateTimeInFileName}-${uniqueId}`;\n}\n\nexport function printReportMsg(filepath: string) {\n logMsg(`Midscene - report file updated: ${filepath}`);\n}\n\n/**\n * Get the current execution file name\n * @returns The name of the current execution file\n */\nexport function getCurrentExecutionFile(trace?: string): string | false {\n const error = new Error();\n const stackTrace = trace || error.stack;\n const pkgDir = process.cwd() || '';\n if (stackTrace) {\n const stackLines = stackTrace.split('\\n');\n for (const line of stackLines) {\n if (\n line.includes('.spec.') ||\n line.includes('.test.') ||\n line.includes('.ts') ||\n line.includes('.js')\n ) {\n const match = line.match(/(?:at\\s+)?(.*?\\.(?:spec|test)\\.[jt]s)/);\n if (match?.[1]) {\n const targetFileName = match[1]\n .replace(pkgDir, '')\n .trim()\n .replace('at ', '');\n return targetFileName;\n }\n }\n }\n }\n return false;\n}\n\nconst testFileIndex = new Map<string, number>();\n\nexport function generateCacheId(fileName?: string): string {\n let taskFile = fileName || getCurrentExecutionFile();\n if (!taskFile) {\n taskFile = uuid();\n console.warn(\n 'Midscene - using random UUID for cache id. Cache may be invalid.',\n );\n }\n\n if (testFileIndex.has(taskFile)) {\n const currentIndex = testFileIndex.get(taskFile);\n if (currentIndex !== undefined) {\n testFileIndex.set(taskFile, currentIndex + 1);\n }\n } else {\n testFileIndex.set(taskFile, 1);\n }\n return `${taskFile}-${testFileIndex.get(taskFile)}`;\n}\n\nexport const ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED =\n 'NOT_IMPLEMENTED_AS_DESIGNED';\n\nexport function replaceIllegalPathCharsAndSpace(str: string) {\n return str.replace(/[/\\\\:*?\"<>| ]/g, '-');\n}\n\nexport function forceClosePopup(\n page: PuppeteerPage | PlaywrightPage,\n debug: DebugFunction,\n) {\n page.on('popup', async (popup) => {\n if (!popup) {\n console.warn('got a popup event, but the popup is not ready yet, skip');\n return;\n }\n const url = await (popup as PuppeteerPage).url();\n console.log(`Popup opened: ${url}`);\n if (!(popup as PuppeteerPage).isClosed()) {\n try {\n await (popup as PuppeteerPage).close(); // Close the newly opened TAB\n } catch (error) {\n debug(`failed to close popup ${url}, error: ${error}`);\n }\n } else {\n debug(`popup is already closed, skip close ${url}`);\n }\n\n if (!page.isClosed()) {\n try {\n await page.goto(url);\n } catch (error) {\n debug(`failed to goto ${url}, error: ${error}`);\n }\n } else {\n debug(`page is already closed, skip goto ${url}`);\n }\n });\n}\n\nexport function matchElementFromPlan(\n planLocateParam: PlanningLocateParam,\n tree: ElementTreeNode<BaseElement>,\n) {\n if (!planLocateParam) {\n return undefined;\n }\n if (planLocateParam.id) {\n return getNodeFromCacheList(planLocateParam.id);\n }\n\n if (planLocateParam.bbox) {\n const centerPosition = {\n x: Math.floor((planLocateParam.bbox[0] + planLocateParam.bbox[2]) / 2),\n y: Math.floor((planLocateParam.bbox[1] + planLocateParam.bbox[3]) / 2),\n };\n let element = elementByPositionWithElementInfo(tree, centerPosition);\n\n if (!element) {\n element = generateElementByPosition(centerPosition) as BaseElement;\n }\n\n return element;\n }\n\n return undefined;\n}\n"]}
1
+ {"version":3,"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQA,sBAAiD;AACjD,mBAAuC;AACvC,iBAAsD;AAEtD,uBAKO;AACP,iBAAgC;AAEhC,IAAAA,gBAAqC;AACrC,mBAAkB;AAsEX,SAAS,eAAe,MAAM,OAAO;AAC1C,QAAM,oBAAgB,wBAAY,mCAAwB;AAC1D,QAAM,yBAAqB,aAAAC,SAAM,EAAE,OAAO,qBAAqB;AAE/D,QAAM,eAAW,oBAAK,EAAE,UAAU,GAAG,CAAC;AACtC,SAAO,GAAG,iBAAiB,GAAG,IAAI,kBAAkB,IAAI,QAAQ;AAClE;AAEO,SAAS,eAAe,UAAkB;AAC/C,4BAAO,mCAAmC,QAAQ,EAAE;AACtD;AA0DO,SAAS,gCAAgC,KAAa;AAE3D,SAAO,IAAI,QAAQ,eAAe,GAAG;AACvC;;;AD5JA,IAAAD,gBAAgC;AAUhC,SAAS,UAAU,SAAgB;AACjC,MAAI,QAAQ,IAAI,UAAU,QAAQ;AAChC,YAAQ,IAAI,wBAAwB,GAAG,OAAO;AAAA,EAChD;AACF;AAEA,IAAM,eAAgD,CAAC;AACvD,IAAI;AACJ,IAAM,sBAA2C,oBAAI,IAAI;AAEzD,SAAS,kBAAkB,WAA2B;AACpD,MAAI,CAAC,oBAAoB,IAAI,SAAS,GAAG;AAGvC,UAAM,UAAU,cAAc,gCAAgC,SAAS,CAAC;AACxE,UAAM,oBAAoB,eAAe,OAAO;AAChD,wBAAoB,IAAI,WAAW,iBAAiB;AAAA,EACtD;AACA,SAAO,oBAAoB,IAAI,SAAS;AAC1C;AAEA,SAAS,aAAa,MAA6B,QAAiB;AAClE,MAAI,SAAS,YAAY;AAEvB,UAAM,WAAW,aAAa;AAAA,MAC5B,CAAC,SAAS,KAAK,YAAY,uBAAuB;AAAA,IACpD;AAEA,QAAI,UAAU;AAEZ,YAAM,iBAAiB;AAAA,QACrB,SAAS,YAAY;AAAA,MACvB;AAEA,YAAM,iBAAa,+BAAgB,gBAAgB,CAAC,QAAQ,CAAC;AAC7D,oBAAc,eAAe,UAAU;AAAA,IACzC;AAAA,EACF,WAAW,SAAS,UAAU;AAE5B,QAAI,CAAC,gBAAgB;AACnB,uBAAiB,eAAe,mBAAmB;AAAA,IACrD;AAEA,UAAM,iBAAa,+BAAgB,gBAAgB,YAAY;AAC/D,kBAAc,eAAe,UAAU;AAAA,EACzC,OAAO;AACL,UAAM,IAAI;AAAA,MACR,+CAA+C,IAAI;AAAA,IACrD;AAAA,EACF;AACF;AAEA,SAAS,QAAQ,cAAsB;AACrC,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,EACT;AAEA,MAAI,iBAAiB,YAAY,iBAAiB,YAAY;AAC5D,UAAM,IAAI;AAAA,MACR,+CAA+C,YAAY;AAAA,IAC7D;AAAA,EACF;AAEA,SAAO;AACT;AAEA,IAAM,mBAAN,MAA2C;AAAA,EAGzC,MAAM,QAAQ,QAAoB,OAAc;AAC9C,UAAM,eAAe,OAAO,WAAW,CAAC,IAAI,CAAC,GAAG;AAEhD,SAAK,OAAO,QAAQ,YAAY;AAAA,EAIlC;AAAA,EAEA,YAAY,MAAgB,SAAqB;AAAA,EAEjD;AAAA,EAEA,UAAU,MAAgB,QAAoB;AAC5C,UAAM,iBAAiB,KAAK,YAAY,KAAK,CAAC,eAAe;AAC3D,aAAO,WAAW,SAAS;AAAA,IAC7B,CAAC;AACD,QAAI,CAAC,gBAAgB;AAAa;AAClC,UAAM,QAAQ,OAAO,QAAQ,WAAW,OAAO,KAAK,MAAM;AAC1D,UAAM,SAAS,GAAG,KAAK,EAAE,GAAG,KAAK;AACjC,UAAM,WAAqC;AAAA,MACzC,YAAY,eAAe;AAAA,MAC3B,YAAY;AAAA,QACV,oBAAoB;AAAA,QACpB,uBAAuB,GAAG,KAAK,KAAK,GAAG,KAAK;AAAA,QAC5C,wBAAwB,OAAO;AAAA,QAC/B,0BAA0B,OAAO;AAAA,MACnC;AAAA,IACF;AAEA,iBAAa,KAAK,QAAQ;AAE1B,iBAAa,KAAK,MAAO,MAAM;AAE/B,SAAK,cAAc,KAAK,YAAY;AAAA,MAClC,CAAC,eAAe,WAAW,SAAS;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAM,QAAoB;AACxB,iBAAa,KAAK,IAAK;AAEvB,WAAO,qBAAqB,OAAO,MAAM,EAAE;AAAA,EAC7C;AACF;AAEA,IAAO,mBAAQ","names":["import_utils","dayjs"],"ignoreList":[],"sources":["../../src/playwright/reporter/index.ts","../../src/common/utils.ts"],"sourcesContent":["import {\n printReportMsg,\n replaceIllegalPathCharsAndSpace,\n reportFileName,\n} from '@/common/utils';\nimport type { ReportDumpWithAttributes } from 'misoai-core';\nimport { writeDumpReport } from 'misoai-core/utils';\nimport type {\n FullConfig,\n FullResult,\n Reporter,\n Suite,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\n\nfunction logger(...message: any[]) {\n if (process.env.DEBUG === 'true') {\n console.log('Midscene e2e report:', ...message);\n }\n}\n\nconst testDataList: Array<ReportDumpWithAttributes> = [];\nlet mergedFilename: string;\nconst testTitleToFilename: Map<string, string> = new Map();\n\nfunction getStableFilename(testTitle: string): string {\n if (!testTitleToFilename.has(testTitle)) {\n // use reportFileName to generate the base filename\n // only replace the illegal characters in the file system: /, \\, :, *, ?, \", <, >, |\n const baseTag = `playwright-${replaceIllegalPathCharsAndSpace(testTitle)}`;\n const generatedFilename = reportFileName(baseTag);\n testTitleToFilename.set(testTitle, generatedFilename);\n }\n return testTitleToFilename.get(testTitle)!;\n}\n\nfunction updateReport(mode: 'merged' | 'separate', testId?: string) {\n if (mode === 'separate') {\n // in separate mode, find the data for the corresponding testID and generate a separate report\n const testData = testDataList.find(\n (data) => data.attributes?.playwright_test_id === testId,\n );\n\n if (testData) {\n // use the stable filename\n const stableFilename = getStableFilename(\n testData.attributes?.playwright_test_title,\n );\n\n const reportPath = writeDumpReport(stableFilename, [testData]);\n reportPath && printReportMsg(reportPath);\n }\n } else if (mode === 'merged') {\n // in merged mode, write all test data into one file\n if (!mergedFilename) {\n mergedFilename = reportFileName('playwright-merged');\n }\n\n const reportPath = writeDumpReport(mergedFilename, testDataList);\n reportPath && printReportMsg(reportPath);\n } else {\n throw new Error(\n `Unknown reporter type in playwright config: ${mode}, only support 'merged' or 'separate'`,\n );\n }\n}\n\nfunction getMode(reporterType: string) {\n if (!reporterType) {\n return 'merged';\n }\n\n if (reporterType !== 'merged' && reporterType !== 'separate') {\n throw new Error(\n `Unknown reporter type in playwright config: ${reporterType}, only support 'merged' or 'separate'`,\n );\n }\n\n return reporterType;\n}\n\nclass MidsceneReporter implements Reporter {\n mode?: 'merged' | 'separate';\n\n async onBegin(config: FullConfig, suite: Suite) {\n const reporterType = config.reporter?.[1]?.[1]?.type;\n\n this.mode = getMode(reporterType);\n\n // const suites = suite.allTests();\n // logger(`Starting the run with ${suites.length} tests`);\n }\n\n onTestBegin(test: TestCase, _result: TestResult) {\n // logger(`Starting test ${test.title}`);\n }\n\n onTestEnd(test: TestCase, result: TestResult) {\n const dumpAnnotation = test.annotations.find((annotation) => {\n return annotation.type === 'MIDSCENE_DUMP_ANNOTATION';\n });\n if (!dumpAnnotation?.description) return;\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n const testData: ReportDumpWithAttributes = {\n dumpString: dumpAnnotation.description,\n attributes: {\n playwright_test_id: testId,\n playwright_test_title: `${test.title}${retry}`,\n playwright_test_status: result.status,\n playwright_test_duration: result.duration,\n },\n };\n\n testDataList.push(testData);\n\n updateReport(this.mode!, testId);\n\n test.annotations = test.annotations.filter(\n (annotation) => annotation.type !== 'MIDSCENE_DUMP_ANNOTATION',\n );\n }\n\n onEnd(result: FullResult) {\n updateReport(this.mode!);\n\n logger(`Finished the run: ${result.status}`);\n }\n}\n\nexport default MidsceneReporter;\n","import type { StaticPage } from '@/playground';\nimport type {\n BaseElement,\n ElementTreeNode,\n PlanningLocateParam,\n PlaywrightParserOpt,\n UIContext,\n} from 'misoai-core';\nimport { elementByPositionWithElementInfo } from 'misoai-core/ai-model';\nimport { uploadTestInfoToServer } from 'misoai-core/utils';\nimport { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from 'misoai-shared/env';\nimport type { ElementInfo } from 'misoai-shared/extractor';\nimport {\n generateElementByPosition,\n getNodeFromCacheList,\n traverseTree,\n treeToList,\n} from 'misoai-shared/extractor';\nimport { resizeImgBase64 } from 'misoai-shared/img';\nimport type { DebugFunction } from 'misoai-shared/logger';\nimport { assert, logMsg, uuid } from 'misoai-shared/utils';\nimport dayjs from 'dayjs';\nimport type { Page as PlaywrightPage } from 'playwright';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport { WebElementInfo } from '../web-element';\nimport type { WebPage } from './page';\n\nexport type WebUIContext = UIContext<WebElementInfo> & {\n url: string;\n};\n\nexport async function parseContextFromWebPage(\n page: WebPage,\n _opt?: PlaywrightParserOpt,\n): Promise<WebUIContext> {\n assert(page, 'page is required');\n if ((page as StaticPage)._forceUsePageContext) {\n return await (page as any)._forceUsePageContext();\n }\n const url = await page.url();\n uploadTestInfoToServer({ testUrl: url });\n\n let screenshotBase64: string;\n let tree: ElementTreeNode<ElementInfo>;\n\n await Promise.all([\n page.screenshotBase64().then((base64) => {\n screenshotBase64 = base64;\n }),\n page.getElementsNodeTree().then(async (treeRoot) => {\n tree = treeRoot;\n }),\n ]);\n\n const webTree = traverseTree(tree!, (elementInfo) => {\n const { rect, id, content, attributes, locator, indexId, isVisible } =\n elementInfo;\n return new WebElementInfo({\n rect,\n locator,\n id,\n content,\n attributes,\n indexId,\n isVisible,\n });\n });\n\n assert(screenshotBase64!, 'screenshotBase64 is required');\n\n const elementsInfo = treeToList(webTree);\n const size = await page.size();\n\n if (size.dpr && size.dpr > 1) {\n // console.time('resizeImgBase64');\n screenshotBase64 = await resizeImgBase64(screenshotBase64, {\n width: size.width,\n height: size.height,\n });\n // console.timeEnd('resizeImgBase64');\n }\n\n return {\n content: elementsInfo!,\n tree: webTree,\n size,\n screenshotBase64: screenshotBase64!,\n url,\n };\n}\n\nexport function reportFileName(tag = 'web') {\n const reportTagName = getAIConfig(MIDSCENE_REPORT_TAG_NAME);\n const dateTimeInFileName = dayjs().format('YYYY-MM-DD_HH-mm-ss');\n // ensure uniqueness at the same time\n const uniqueId = uuid().substring(0, 8);\n return `${reportTagName || tag}-${dateTimeInFileName}-${uniqueId}`;\n}\n\nexport function printReportMsg(filepath: string) {\n logMsg(`Midscene - report file updated: ${filepath}`);\n}\n\n/**\n * Get the current execution file name\n * @returns The name of the current execution file\n */\nexport function getCurrentExecutionFile(trace?: string): string | false {\n const error = new Error();\n const stackTrace = trace || error.stack;\n const pkgDir = process.cwd() || '';\n if (stackTrace) {\n const stackLines = stackTrace.split('\\n');\n for (const line of stackLines) {\n if (\n line.includes('.spec.') ||\n line.includes('.test.') ||\n line.includes('.ts') ||\n line.includes('.js')\n ) {\n const match = line.match(/(?:at\\s+)?(.*?\\.(?:spec|test)\\.[jt]s)/);\n if (match?.[1]) {\n const targetFileName = match[1]\n .replace(pkgDir, '')\n .trim()\n .replace('at ', '');\n return targetFileName;\n }\n }\n }\n }\n return false;\n}\n\nconst testFileIndex = new Map<string, number>();\n\nexport function generateCacheId(fileName?: string): string {\n let taskFile = fileName || getCurrentExecutionFile();\n if (!taskFile) {\n taskFile = uuid();\n console.warn(\n 'Midscene - using random UUID for cache id. Cache may be invalid.',\n );\n }\n\n if (testFileIndex.has(taskFile)) {\n const currentIndex = testFileIndex.get(taskFile);\n if (currentIndex !== undefined) {\n testFileIndex.set(taskFile, currentIndex + 1);\n }\n } else {\n testFileIndex.set(taskFile, 1);\n }\n return `${taskFile}-${testFileIndex.get(taskFile)}`;\n}\n\nexport const ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED =\n 'NOT_IMPLEMENTED_AS_DESIGNED';\n\nexport function replaceIllegalPathCharsAndSpace(str: string) {\n // Only replace characters that are illegal in filenames, but preserve path separators\n return str.replace(/[:*?\"<>| ]/g, '-');\n}\n\nexport function forceClosePopup(\n page: PuppeteerPage | PlaywrightPage,\n debug: DebugFunction,\n) {\n page.on('popup', async (popup) => {\n if (!popup) {\n console.warn('got a popup event, but the popup is not ready yet, skip');\n return;\n }\n const url = await (popup as PuppeteerPage).url();\n console.log(`Popup opened: ${url}`);\n if (!(popup as PuppeteerPage).isClosed()) {\n try {\n await (popup as PuppeteerPage).close(); // Close the newly opened TAB\n } catch (error) {\n debug(`failed to close popup ${url}, error: ${error}`);\n }\n } else {\n debug(`popup is already closed, skip close ${url}`);\n }\n\n if (!page.isClosed()) {\n try {\n await page.goto(url);\n } catch (error) {\n debug(`failed to goto ${url}, error: ${error}`);\n }\n } else {\n debug(`page is already closed, skip goto ${url}`);\n }\n });\n}\n\nexport function matchElementFromPlan(\n planLocateParam: PlanningLocateParam,\n tree: ElementTreeNode<BaseElement>,\n) {\n if (!planLocateParam) {\n return undefined;\n }\n if (planLocateParam.id) {\n return getNodeFromCacheList(planLocateParam.id);\n }\n\n if (planLocateParam.bbox) {\n const centerPosition = {\n x: Math.floor((planLocateParam.bbox[0] + planLocateParam.bbox[2]) / 2),\n y: Math.floor((planLocateParam.bbox[1] + planLocateParam.bbox[3]) / 2),\n };\n let element = elementByPositionWithElementInfo(tree, centerPosition);\n\n if (!element) {\n element = generateElementByPosition(centerPosition) as BaseElement;\n }\n\n return element;\n }\n\n return undefined;\n}\n"]}
@@ -144,6 +144,10 @@ var ScriptPlayer = class {
144
144
  } else if ("aiQuery" in flowItem) {
145
145
  const queryTask = flowItem;
146
146
  const prompt = queryTask.aiQuery;
147
+ const options = {
148
+ domIncluded: queryTask.domIncluded,
149
+ screenshotIncluded: queryTask.screenshotIncluded
150
+ };
147
151
  (0, import_utils.assert)(prompt, "missing prompt for aiQuery");
148
152
  (0, import_utils.assert)(
149
153
  typeof prompt === "string",
@@ -154,6 +158,10 @@ var ScriptPlayer = class {
154
158
  } else if ("aiNumber" in flowItem) {
155
159
  const numberTask = flowItem;
156
160
  const prompt = numberTask.aiNumber;
161
+ const options = {
162
+ domIncluded: numberTask.domIncluded,
163
+ screenshotIncluded: numberTask.screenshotIncluded
164
+ };
157
165
  (0, import_utils.assert)(prompt, "missing prompt for number");
158
166
  (0, import_utils.assert)(
159
167
  typeof prompt === "string",
@@ -164,6 +172,10 @@ var ScriptPlayer = class {
164
172
  } else if ("aiString" in flowItem) {
165
173
  const stringTask = flowItem;
166
174
  const prompt = stringTask.aiString;
175
+ const options = {
176
+ domIncluded: stringTask.domIncluded,
177
+ screenshotIncluded: stringTask.screenshotIncluded
178
+ };
167
179
  (0, import_utils.assert)(prompt, "missing prompt for string");
168
180
  (0, import_utils.assert)(
169
181
  typeof prompt === "string",
@@ -174,6 +186,10 @@ var ScriptPlayer = class {
174
186
  } else if ("aiBoolean" in flowItem) {
175
187
  const booleanTask = flowItem;
176
188
  const prompt = booleanTask.aiBoolean;
189
+ const options = {
190
+ domIncluded: booleanTask.domIncluded,
191
+ screenshotIncluded: booleanTask.screenshotIncluded
192
+ };
177
193
  (0, import_utils.assert)(prompt, "missing prompt for boolean");
178
194
  (0, import_utils.assert)(
179
195
  typeof prompt === "string",
@@ -216,6 +232,9 @@ var ScriptPlayer = class {
216
232
  } else if ("aiTap" in flowItem) {
217
233
  const tapTask = flowItem;
218
234
  await agent.aiTap(tapTask.aiTap, tapTask);
235
+ } else if ("aiRightClick" in flowItem) {
236
+ const rightClickTask = flowItem;
237
+ await agent.aiRightClick(rightClickTask.aiRightClick, rightClickTask);
219
238
  } else if ("aiHover" in flowItem) {
220
239
  const hoverTask = flowItem;
221
240
  await agent.aiHover(hoverTask.aiHover, hoverTask);
@@ -238,6 +257,11 @@ var ScriptPlayer = class {
238
257
  evaluateJavaScriptTask.javascript
239
258
  );
240
259
  this.setResult(evaluateJavaScriptTask.name, result);
260
+ } else if ("logScreenshot" in flowItem) {
261
+ const logScreenshotTask = flowItem;
262
+ await agent.logScreenshot(logScreenshotTask.logScreenshot, {
263
+ content: logScreenshotTask.content || ""
264
+ });
241
265
  } else {
242
266
  throw new Error(`unknown flowItem: ${JSON.stringify(flowItem)}`);
243
267
  }
@@ -486,7 +510,8 @@ var WebElementInfo = class {
486
510
  id,
487
511
  attributes,
488
512
  indexId,
489
- xpaths
513
+ xpaths,
514
+ isVisible
490
515
  }) {
491
516
  this.content = content;
492
517
  this.rect = rect;
@@ -499,6 +524,7 @@ var WebElementInfo = class {
499
524
  this.attributes = attributes;
500
525
  this.indexId = indexId;
501
526
  this.xpaths = xpaths;
527
+ this.isVisible = isVisible;
502
528
  }
503
529
  };
504
530
 
@@ -521,14 +547,15 @@ async function parseContextFromWebPage(page, _opt) {
521
547
  })
522
548
  ]);
523
549
  const webTree = (0, import_extractor.traverseTree)(tree, (elementInfo) => {
524
- const { rect, id, content, attributes, locator, indexId } = elementInfo;
550
+ const { rect, id, content, attributes, locator, indexId, isVisible } = elementInfo;
525
551
  return new WebElementInfo({
526
552
  rect,
527
553
  locator,
528
554
  id,
529
555
  content,
530
556
  attributes,
531
- indexId
557
+ indexId,
558
+ isVisible
532
559
  });
533
560
  });
534
561
  (0, import_utils4.assert)(screenshotBase64, "screenshotBase64 is required");
@@ -558,7 +585,7 @@ function printReportMsg(filepath) {
558
585
  (0, import_utils4.logMsg)(`Midscene - report file updated: ${filepath}`);
559
586
  }
560
587
  function replaceIllegalPathCharsAndSpace(str) {
561
- return str.replace(/[/\\:*?"<>| ]/g, "-");
588
+ return str.replace(/[:*?"<>| ]/g, "-");
562
589
  }
563
590
  function forceClosePopup(page, debug6) {
564
591
  page.on("popup", async (popup) => {
@@ -872,10 +899,10 @@ var PageTaskExecutor = class {
872
899
  if (!taskParam || !taskParam.value) {
873
900
  return;
874
901
  }
875
- await this.page.keyboard.type(taskParam.value);
876
- } else {
877
- await this.page.keyboard.type(taskParam.value);
878
902
  }
903
+ await this.page.keyboard.type(taskParam.value, {
904
+ autoDismissKeyboard: taskParam.autoDismissKeyboard
905
+ });
879
906
  }
880
907
  };
881
908
  tasks.push(taskActionInput);
@@ -904,6 +931,22 @@ var PageTaskExecutor = class {
904
931
  }
905
932
  };
906
933
  tasks.push(taskActionTap);
934
+ } else if (plan2.type === "RightClick") {
935
+ const taskActionRightClick = {
936
+ type: "Action",
937
+ subType: "RightClick",
938
+ thought: plan2.thought,
939
+ locate: plan2.locate,
940
+ executor: async (param, { element }) => {
941
+ (0, import_utils6.assert)(element, "Element not found, cannot right click");
942
+ await this.page.mouse.click(
943
+ element.center[0],
944
+ element.center[1],
945
+ { button: "right" }
946
+ );
947
+ }
948
+ };
949
+ tasks.push(taskActionRightClick);
907
950
  } else if (plan2.type === "Drag") {
908
951
  const taskActionDrag = {
909
952
  type: "Action",
@@ -1432,7 +1475,7 @@ var PageTaskExecutor = class {
1432
1475
  executor: taskExecutor
1433
1476
  };
1434
1477
  }
1435
- async createTypeQueryTask(type, demand) {
1478
+ async createTypeQueryTask(type, demand, opt) {
1436
1479
  const taskExecutor = new import_misoai_core.Executor(
1437
1480
  taskTitleStr(
1438
1481
  type,
@@ -1463,7 +1506,10 @@ var PageTaskExecutor = class {
1463
1506
  result: `${type}, ${demand}`
1464
1507
  };
1465
1508
  }
1466
- const { data, usage } = await this.insight.extract(demandInput);
1509
+ const { data, usage } = await this.insight.extract(
1510
+ demandInput,
1511
+ opt
1512
+ );
1467
1513
  let outputResult = data;
1468
1514
  if (ifTypeRestricted) {
1469
1515
  (0, import_utils6.assert)(data?.result !== void 0, "No result in query data");
@@ -1483,17 +1529,17 @@ var PageTaskExecutor = class {
1483
1529
  executor: taskExecutor
1484
1530
  };
1485
1531
  }
1486
- async query(demand) {
1487
- return this.createTypeQueryTask("Query", demand);
1532
+ async query(demand, opt) {
1533
+ return this.createTypeQueryTask("Query", demand, opt);
1488
1534
  }
1489
- async boolean(prompt) {
1490
- return this.createTypeQueryTask("Boolean", prompt);
1535
+ async boolean(prompt, opt) {
1536
+ return this.createTypeQueryTask("Boolean", prompt, opt);
1491
1537
  }
1492
- async number(prompt) {
1493
- return this.createTypeQueryTask("Number", prompt);
1538
+ async number(prompt, opt) {
1539
+ return this.createTypeQueryTask("Number", prompt, opt);
1494
1540
  }
1495
- async string(prompt) {
1496
- return this.createTypeQueryTask("String", prompt);
1541
+ async string(prompt, opt) {
1542
+ return this.createTypeQueryTask("String", prompt, opt);
1497
1543
  }
1498
1544
  async assert(assertion) {
1499
1545
  const description = `assert: ${assertion}`;
@@ -1629,7 +1675,7 @@ function buildPlans(type, locateParam, param) {
1629
1675
  param: locateParam,
1630
1676
  thought: ""
1631
1677
  } : null;
1632
- if (type === "Tap" || type === "Hover") {
1678
+ if (type === "Tap" || type === "Hover" || type === "RightClick") {
1633
1679
  (0, import_utils8.assert)(locateParam, `missing locate info for action "${type}"`);
1634
1680
  (0, import_utils8.assert)(locatePlan, `missing locate info for action "${type}"`);
1635
1681
  const tapPlan = {
@@ -1709,7 +1755,7 @@ var import_js_yaml3 = __toESM(require("js-yaml"));
1709
1755
  var import_semver = __toESM(require("semver"));
1710
1756
 
1711
1757
  // package.json
1712
- var version = "1.5.6";
1758
+ var version = "1.0.3";
1713
1759
 
1714
1760
  // src/common/task-cache.ts
1715
1761
  var debug3 = (0, import_logger3.getDebug)("cache");
@@ -1840,8 +1886,14 @@ cache file: ${cacheFile}`
1840
1886
  return;
1841
1887
  }
1842
1888
  try {
1889
+ const dir = (0, import_node_path2.dirname)(this.cacheFilePath);
1890
+ if (!(0, import_node_fs2.existsSync)(dir)) {
1891
+ (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
1892
+ debug3("created cache directory: %s", dir);
1893
+ }
1843
1894
  const yamlData = import_js_yaml3.default.dump(this.cache);
1844
1895
  (0, import_node_fs2.writeFileSync)(this.cacheFilePath, yamlData);
1896
+ debug3("cache flushed to file: %s", this.cacheFilePath);
1845
1897
  } catch (err) {
1846
1898
  debug3(
1847
1899
  "write cache to file failed, path: %s, error: %s",
@@ -2099,6 +2151,23 @@ var PageAgent = class {
2099
2151
  metadata
2100
2152
  };
2101
2153
  }
2154
+ async aiRightClick(locatePrompt, opt) {
2155
+ const detailedLocateParam = this.buildDetailedLocateParam(
2156
+ locatePrompt,
2157
+ opt
2158
+ );
2159
+ const plans = buildPlans("RightClick", detailedLocateParam);
2160
+ const { executor, output } = await this.taskExecutor.runPlans(
2161
+ taskTitleStr("RightClick", locateParamStr(detailedLocateParam)),
2162
+ plans,
2163
+ { cacheable: opt?.cacheable }
2164
+ );
2165
+ const metadata = this.afterTaskRunning(executor);
2166
+ return {
2167
+ result: output,
2168
+ metadata
2169
+ };
2170
+ }
2102
2171
  async aiInput(value, locatePrompt, opt) {
2103
2172
  (0, import_utils12.assert)(
2104
2173
  typeof value === "string",
@@ -2530,6 +2599,40 @@ ${errors}`);
2530
2599
  }
2531
2600
  throw new Error("evaluateJavaScript is not supported in current agent");
2532
2601
  }
2602
+ async logScreenshot(title, options) {
2603
+ const screenshotTitle = title || "untitled";
2604
+ const content = options?.content || "";
2605
+ const screenshot = await this.page.screenshotBase64?.();
2606
+ if (screenshot) {
2607
+ const executionDump = {
2608
+ name: screenshotTitle,
2609
+ description: content,
2610
+ tasks: [{
2611
+ type: "Screenshot",
2612
+ subType: "log",
2613
+ status: "finished",
2614
+ executor: null,
2615
+ param: {
2616
+ title: screenshotTitle,
2617
+ content
2618
+ },
2619
+ output: {
2620
+ screenshot
2621
+ },
2622
+ thought: `Logged screenshot: ${screenshotTitle}`,
2623
+ timing: {
2624
+ start: Date.now(),
2625
+ end: Date.now(),
2626
+ cost: 0
2627
+ }
2628
+ }],
2629
+ sdkVersion: "1.0.0",
2630
+ logTime: Date.now(),
2631
+ model_name: "screenshot"
2632
+ };
2633
+ this.appendExecutionDump(executionDump);
2634
+ }
2635
+ }
2533
2636
  async destroy() {
2534
2637
  await this.page.destroy();
2535
2638
  }
@@ -2953,6 +3056,14 @@ var PlaywrightAiFixture = (options) => {
2953
3056
  aiActionType: "aiTap"
2954
3057
  });
2955
3058
  },
3059
+ aiRightClick: async ({ page }, use, testInfo) => {
3060
+ await generateAiFunction({
3061
+ page,
3062
+ testInfo,
3063
+ use,
3064
+ aiActionType: "aiRightClick"
3065
+ });
3066
+ },
2956
3067
  aiHover: async ({ page }, use, testInfo) => {
2957
3068
  await generateAiFunction({
2958
3069
  page,
@@ -3040,6 +3151,14 @@ var PlaywrightAiFixture = (options) => {
3040
3151
  use,
3041
3152
  aiActionType: "aiBoolean"
3042
3153
  });
3154
+ },
3155
+ logScreenshot: async ({ page }, use, testInfo) => {
3156
+ await generateAiFunction({
3157
+ page,
3158
+ testInfo,
3159
+ use,
3160
+ aiActionType: "logScreenshot"
3161
+ });
3043
3162
  }
3044
3163
  };
3045
3164
  };