├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.en-US.yml │ ├── feature-request.en-US.yml │ └── llm-connectivity-issue---模型连接错误.md ├── pr-labeler.yml ├── release.yml └── workflows │ ├── ai-evaluation.yml │ ├── ai-unit-test.yml │ ├── ai.yml │ ├── ci.yml │ ├── issue-close-require.yml │ ├── issue-labeled.yml │ ├── lint.yml │ ├── pr-label.yml │ └── release.yml ├── .gitignore ├── .husky └── commit-msg ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README.zh.md ├── apps ├── android-playground │ ├── README.md │ ├── package.json │ ├── rsbuild.config.ts │ ├── src │ │ ├── App.less │ │ ├── App.tsx │ │ ├── adb-device │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── env.d.ts │ │ ├── favicon.ico │ │ ├── icons │ │ │ ├── linked.svg │ │ │ ├── screenshot.svg │ │ │ └── unlink.svg │ │ ├── index.tsx │ │ ├── scrcpy-player │ │ │ ├── index.less │ │ │ └── index.tsx │ │ └── scripts │ │ │ └── blank_polyfill.ts │ └── tsconfig.json ├── chrome-extension │ ├── README.md │ ├── package.json │ ├── rsbuild.config.ts │ ├── scripts │ │ └── pack-extension.js │ ├── src │ │ ├── App.less │ │ ├── App.tsx │ │ ├── component │ │ │ ├── playground.less │ │ │ └── playground.tsx │ │ ├── env.d.ts │ │ ├── extension │ │ │ ├── bridge.less │ │ │ ├── bridge.tsx │ │ │ ├── common.less │ │ │ ├── misc.tsx │ │ │ ├── popup.less │ │ │ ├── popup.tsx │ │ │ └── recorder │ │ │ │ ├── ExportControls.tsx │ │ │ │ ├── README.md │ │ │ │ ├── components │ │ │ │ ├── ProgressModal.tsx │ │ │ │ ├── RecordDetail.tsx │ │ │ │ ├── RecordList.tsx │ │ │ │ ├── SessionModals.tsx │ │ │ │ └── index.ts │ │ │ │ ├── generators │ │ │ │ ├── README.md │ │ │ │ ├── index.ts │ │ │ │ ├── playwrightGenerator.ts │ │ │ │ ├── shared │ │ │ │ │ ├── testGenerationUtils.ts │ │ │ │ │ └── types.ts │ │ │ │ └── yamlGenerator.ts │ │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useLifecycleCleanup.ts │ │ │ │ ├── useRecordingControl.ts │ │ │ │ ├── useRecordingSession.ts │ │ │ │ └── useTabMonitoring.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── logger.ts │ │ │ │ ├── recorder.less │ │ │ │ ├── shared │ │ │ │ └── exportControlsUtils.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ ├── icons │ │ │ ├── bridge.svg │ │ │ ├── play.svg │ │ │ ├── playground-2.svg │ │ │ └── playground.svg │ │ ├── index.tsx │ │ ├── scripts │ │ │ ├── blank_polyfill.ts │ │ │ ├── event-recorder-bridge.ts │ │ │ ├── stop-water-flow.ts │ │ │ ├── water-flow.ts │ │ │ └── worker.ts │ │ ├── store.tsx │ │ ├── utils.ts │ │ └── utils │ │ │ ├── eventOptimizer.ts │ │ │ └── indexedDB.ts │ ├── static │ │ ├── fonts │ │ │ └── open-sans │ │ │ │ ├── Apache License.txt │ │ │ │ ├── open-sans-10-black │ │ │ │ ├── open-sans-10-black.fnt │ │ │ │ └── open-sans-10-black.png │ │ │ │ ├── open-sans-12-black │ │ │ │ ├── open-sans-12-black.fnt │ │ │ │ └── open-sans-12-black.png │ │ │ │ ├── open-sans-128-black │ │ │ │ ├── open-sans-128-black.fnt │ │ │ │ └── open-sans-128-black.png │ │ │ │ ├── open-sans-128-white │ │ │ │ ├── open-sans-128-white.fnt │ │ │ │ └── open-sans-128-white.png │ │ │ │ ├── open-sans-14-black │ │ │ │ ├── open-sans-14-black.fnt │ │ │ │ └── open-sans-14-black.png │ │ │ │ ├── open-sans-16-black │ │ │ │ ├── open-sans-16-black.fnt │ │ │ │ └── open-sans-16-black.png │ │ │ │ ├── open-sans-16-white │ │ │ │ ├── open-sans-16-white.fnt │ │ │ │ └── open-sans-16-white.png │ │ │ │ ├── open-sans-32-black │ │ │ │ ├── open-sans-32-black.fnt │ │ │ │ └── open-sans-32-black.png │ │ │ │ ├── open-sans-32-white │ │ │ │ ├── open-sans-32-white.fnt │ │ │ │ └── open-sans-32-white.png │ │ │ │ ├── open-sans-64-black │ │ │ │ ├── open-sans-64-black.fnt │ │ │ │ └── open-sans-64-black.png │ │ │ │ ├── open-sans-64-white │ │ │ │ ├── open-sans-64-white.fnt │ │ │ │ └── open-sans-64-white.png │ │ │ │ ├── open-sans-8-black │ │ │ │ ├── open-sans-8-black.fnt │ │ │ │ └── open-sans-8-black.png │ │ │ │ └── open-sans-8-white │ │ │ │ ├── open-sans-8-white.fnt │ │ │ │ └── open-sans-8-white.png │ │ ├── icon128.png │ │ └── manifest.json │ └── tsconfig.json ├── recorder-form │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── rsbuild.config.ts │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── components │ │ │ └── CanvasSelector.tsx │ │ ├── env.d.ts │ │ └── index.tsx │ └── tsconfig.json ├── report │ ├── .gitignore │ ├── README.md │ ├── e2e │ │ └── check-html.yaml │ ├── package.json │ ├── rsbuild.config.ts │ ├── src │ │ ├── App.less │ │ ├── App.tsx │ │ ├── blank_polyfill.ts │ │ ├── components │ │ │ ├── PlaywrightCaseSelector.tsx │ │ │ ├── common.less │ │ │ ├── detail-panel.less │ │ │ ├── detail-panel.tsx │ │ │ ├── detail-side.less │ │ │ ├── detail-side.tsx │ │ │ ├── global-hover-preview.less │ │ │ ├── global-hover-preview.tsx │ │ │ ├── open-in-playground.less │ │ │ ├── open-in-playground.tsx │ │ │ ├── panel-title.less │ │ │ ├── panel-title.tsx │ │ │ ├── pixi-loader.tsx │ │ │ ├── playground-demo-ui-context.json │ │ │ ├── playground.tsx │ │ │ ├── side-item.tsx │ │ │ ├── sidebar.less │ │ │ ├── sidebar.tsx │ │ │ ├── store.tsx │ │ │ ├── timeline.less │ │ │ ├── timeline.tsx │ │ │ └── yaml-player-component.tsx │ │ ├── env.d.ts │ │ ├── index.less │ │ ├── index.tsx │ │ └── types.ts │ ├── template │ │ └── index.html │ ├── test-data │ │ ├── ai-shop.json │ │ ├── ai-todo.json │ │ ├── error.json │ │ ├── online-order.json │ │ ├── query-only.json │ │ ├── search-headphone-on-ebay.json │ │ ├── swag-lab.json │ │ └── taobao.json │ └── tsconfig.json └── site │ ├── .gitignore │ ├── build.sh │ ├── docs │ ├── en │ │ ├── API.mdx │ │ ├── automate-with-scripts-in-yaml.mdx │ │ ├── blog-introducing-instant-actions-and-deep-think.md │ │ ├── blog-programming-practice-using-structured-api.md │ │ ├── blog-support-android-automation.mdx │ │ ├── bridge-mode-by-chrome-extension.mdx │ │ ├── caching.mdx │ │ ├── changelog.mdx │ │ ├── choose-a-model.mdx │ │ ├── common │ │ │ ├── prepare-android.mdx │ │ │ ├── prepare-key-for-further-use.mdx │ │ │ ├── setup-env.mdx │ │ │ └── start-experience.mdx │ │ ├── data-privacy.md │ │ ├── faq.md │ │ ├── index.mdx │ │ ├── integrate-with-android.mdx │ │ ├── integrate-with-playwright.mdx │ │ ├── integrate-with-puppeteer.mdx │ │ ├── llm-txt.mdx │ │ ├── mcp.mdx │ │ ├── model-provider.mdx │ │ ├── prompting-tips.md │ │ ├── quick-experience-with-android.mdx │ │ └── quick-experience.mdx │ ├── public │ │ ├── android-playground.png │ │ ├── android-set-env.png │ │ ├── android-usb-debug-en.png │ │ ├── android-usb-debug.png │ │ ├── blog │ │ │ ├── 0.10.0-2.png │ │ │ ├── 0.10.0.png │ │ │ ├── 0.11.0-2.png │ │ │ ├── 0.11.0.png │ │ │ ├── 0.13.0.jpeg │ │ │ ├── 0.5.0-2.png │ │ │ ├── 0.5.0.png │ │ │ ├── 0.6.0-2.png │ │ │ ├── 0.6.0-3.png │ │ │ ├── 0.6.0-4.png │ │ │ ├── 0.6.0-5.png │ │ │ ├── 0.6.0-6.png │ │ │ ├── 0.6.0.png │ │ │ ├── 0.9.0.png │ │ │ ├── ai-ide-convert-prompt-result.png │ │ │ ├── ai-ide-convert-prompt.png │ │ │ ├── android-playground-lark-poster-cn.png │ │ │ ├── android-playground-lark-poster-en.png │ │ │ ├── coze-sidebar.png │ │ │ ├── export-video.png │ │ │ ├── logScreenshot-api.png │ │ │ ├── report-coze-deep-think.png │ │ │ ├── report-instant-action.png │ │ │ └── report-planning.png │ │ ├── bridge_in_extension.jpg │ │ ├── cache │ │ │ ├── no-cache-time.png │ │ │ └── use-cache-time.png │ │ ├── midescene-playground-entry.jpg │ │ ├── midscene-bridge-mode.jpg │ │ ├── midscene-extension.jpg │ │ ├── midscene-icon.png │ │ ├── midscene_with_text_light.png │ │ ├── playground.png │ │ └── report.gif │ └── zh │ │ ├── API.mdx │ │ ├── automate-with-scripts-in-yaml.mdx │ │ ├── blog-introducing-instant-actions-and-deep-think.md │ │ ├── blog-programming-practice-using-structured-api.md │ │ ├── blog-support-android-automation.mdx │ │ ├── bridge-mode-by-chrome-extension.mdx │ │ ├── caching.mdx │ │ ├── changelog.mdx │ │ ├── choose-a-model.mdx │ │ ├── common │ │ ├── prepare-android.mdx │ │ ├── prepare-key-for-further-use.mdx │ │ ├── setup-env.mdx │ │ └── start-experience.mdx │ │ ├── data-privacy.md │ │ ├── faq.md │ │ ├── index.mdx │ │ ├── integrate-with-android.mdx │ │ ├── integrate-with-playwright.mdx │ │ ├── integrate-with-puppeteer.mdx │ │ ├── llm-txt.mdx │ │ ├── mcp.mdx │ │ ├── model-provider.mdx │ │ ├── prompting-tips.md │ │ ├── quick-experience-with-android.mdx │ │ └── quick-experience.mdx │ ├── i18n.json │ ├── package.json │ ├── route.json │ ├── rspress.config.ts │ ├── styles │ └── index.css │ └── tsconfig.json ├── biome.json ├── commitlint.config.js ├── cspell.config.cjs ├── nx.json ├── package.json ├── packages ├── android-playground │ ├── .gitignore │ ├── README.md │ ├── bin │ │ ├── android-playground │ │ └── server.bin │ ├── modern.config.ts │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── scrcpy-server.ts │ └── tsconfig.json ├── android │ ├── .gitignore │ ├── README.md │ ├── bin │ │ └── yadb │ ├── modern.config.ts │ ├── package.json │ ├── src │ │ ├── agent │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── page │ │ │ └── index.ts │ │ └── utils │ │ │ └── index.ts │ ├── tests │ │ └── ai │ │ │ ├── ebay.test.ts │ │ │ ├── setting.test.ts │ │ │ ├── todo.test.ts │ │ │ └── travel.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── cli │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── bin │ │ └── midscene │ ├── modern.config.ts │ ├── package.json │ ├── src │ │ ├── args.ts │ │ ├── cli-utils.ts │ │ ├── http-server.d.ts │ │ ├── index.ts │ │ ├── printer.ts │ │ ├── tty-renderer.ts │ │ └── yaml-runner.ts │ ├── tests │ │ ├── ai │ │ │ ├── __snapshots__ │ │ │ │ └── bin.test.ts.snap │ │ │ ├── bin.test.ts │ │ │ └── bridge.test.ts │ │ ├── midscene_scripts │ │ │ ├── local │ │ │ │ ├── local-error-message.yml │ │ │ │ └── local.yml │ │ │ └── online │ │ │ │ └── online.yaml │ │ ├── midscene_scripts_bridge │ │ │ ├── current_tab │ │ │ │ └── check_content.yaml │ │ │ └── new_tab │ │ │ │ ├── bing.yaml │ │ │ │ ├── local.yml │ │ │ │ └── open-new-tab.yaml │ │ ├── server_root │ │ │ └── index.html │ │ └── unit-test │ │ │ ├── __snapshots__ │ │ │ └── cli-utils.test.ts.snap │ │ │ └── cli-utils.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── core │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── modern.config.ts │ ├── package.json │ ├── src │ │ ├── ai-model │ │ │ ├── action-executor.ts │ │ │ ├── common.ts │ │ │ ├── index.ts │ │ │ ├── inspect.ts │ │ │ ├── llm-planning.ts │ │ │ ├── prompt │ │ │ │ ├── assertion.ts │ │ │ │ ├── common.ts │ │ │ │ ├── describe.ts │ │ │ │ ├── extraction.ts │ │ │ │ ├── llm-locator.ts │ │ │ │ ├── llm-planning.ts │ │ │ │ ├── llm-section-locator.ts │ │ │ │ ├── playwright-generator.ts │ │ │ │ ├── ui-tars-locator.ts │ │ │ │ ├── ui-tars-planning.ts │ │ │ │ ├── util.ts │ │ │ │ └── yaml-generator.ts │ │ │ ├── service-caller │ │ │ │ ├── index.ts │ │ │ │ └── types.d.ts │ │ │ └── ui-tars-planning.ts │ │ ├── image │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── insight │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── tree.ts │ │ ├── types.ts │ │ ├── utils.ts │ │ └── yaml.ts │ ├── tests │ │ ├── ai │ │ │ ├── assert │ │ │ │ └── assert.test.ts │ │ │ ├── connectivity.test.ts │ │ │ ├── extract │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── extract.test.ts.snap │ │ │ │ └── extract.test.ts │ │ │ ├── insight │ │ │ │ └── insight.test.ts │ │ │ ├── llm-inspect.test.ts │ │ │ ├── llm-planning │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── basic.test.ts.snap │ │ │ │ │ ├── input.test.ts.snap │ │ │ │ │ ├── planning-input.test.ts.snap │ │ │ │ │ └── planning.test.ts.snap │ │ │ │ ├── basic.test.ts │ │ │ │ └── input.test.ts │ │ │ ├── llm-section-locator.test.ts │ │ │ ├── parse-action.test.ts │ │ │ └── ui-tars-planning │ │ │ │ ├── output.png │ │ │ │ └── plan-to-target.test.ts │ │ ├── evaluation.ts │ │ ├── fixtures │ │ │ ├── baidu.png │ │ │ ├── dump-for-utils-test.json │ │ │ └── dump.json │ │ ├── tsconfig.json │ │ ├── unit-test │ │ │ ├── __snapshots__ │ │ │ │ ├── llm-planning.test.ts.snap │ │ │ │ └── tree.test.ts.snap │ │ │ ├── env.test.ts │ │ │ ├── executor │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.ts.snap │ │ │ │ └── index.test.ts │ │ │ ├── llm-planning.test.ts │ │ │ ├── mocks │ │ │ │ └── intl-mock.ts │ │ │ ├── prompt │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── assertion.test.ts.snap │ │ │ │ │ ├── describe.test.ts.snap │ │ │ │ │ └── prompt.test.ts.snap │ │ │ │ ├── assertion.test.ts │ │ │ │ ├── describe.test.ts │ │ │ │ ├── playwright-generator.test.ts │ │ │ │ ├── prompt.test.ts │ │ │ │ └── utils.test.ts │ │ │ ├── types-rightclick.test.ts │ │ │ └── utils.test.ts │ │ └── utils.ts │ ├── third-party-licenses.txt │ ├── tsconfig.json │ └── vitest.config.ts ├── evaluation │ ├── .gitignore │ ├── README.md │ ├── data-generator │ │ ├── fixture.ts │ │ ├── generator-headed.spec.ts │ │ ├── generator-headless.spec.ts │ │ └── utils.ts │ ├── package.json │ ├── page-cases │ │ ├── assertion │ │ │ ├── online_order.json │ │ │ └── online_order_list.json │ │ ├── inspect │ │ │ ├── antd-carousel.json │ │ │ ├── antd-carousel.json-coordinates-annotated.png │ │ │ ├── aweme-login.json │ │ │ ├── aweme-login.json-coordinates-annotated.png │ │ │ ├── aweme-play.json │ │ │ ├── aweme-play.json-coordinates-annotated.png │ │ │ ├── online_order.json │ │ │ ├── online_order.json-coordinates-annotated.png │ │ │ ├── online_order_list.json │ │ │ ├── online_order_list.json-coordinates-annotated.png │ │ │ ├── taobao.json │ │ │ ├── taobao.json-coordinates-annotated.png │ │ │ ├── todo.json │ │ │ └── todo.json-coordinates-annotated.png │ │ ├── planning │ │ │ ├── antd-form-vl.json │ │ │ ├── antd-form-vl.json-planning-coordinates-annotated.png │ │ │ ├── antd-tooltip-vl.json │ │ │ ├── antd-tooltip-vl.json-planning-coordinates-annotated.png │ │ │ ├── aweme-login-vl.json │ │ │ ├── todo-vl.json │ │ │ ├── todo-vl.json-planning-coordinates-annotated.png │ │ │ └── todo.json │ │ └── section-locator │ │ │ ├── antd-tooltip.json │ │ │ └── antd-tooltip.json-coordinates-annotated.png │ ├── page-data │ │ ├── antd-carousel │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── antd-form │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── antd-pagination │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── antd-tooltip │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── aweme-login │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── aweme-play │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── githubstatus │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── image-only │ │ │ └── .gitignore │ │ ├── online_order │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── online_order_list │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── taobao │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── todo-input-with-value │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ ├── todo │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ │ └── visualstudio │ │ │ ├── element-snapshot.json │ │ │ ├── element-tree.json │ │ │ ├── element-tree.txt │ │ │ ├── input.png │ │ │ ├── output.png │ │ │ ├── output_without_text.png │ │ │ └── resize-output.png │ ├── playwright.config.ts │ ├── src │ │ └── test-analyzer.ts │ ├── tests │ │ ├── assertion.test.ts │ │ ├── llm-locator.test.ts │ │ ├── llm-planning.test.ts │ │ ├── llm-section-locator.test.ts │ │ ├── screenspot-v2-evaluation.test.ts │ │ └── util.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── mcp │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── rslib.config.ts │ ├── scripts │ │ └── inspect.mjs │ ├── src │ │ ├── index.ts │ │ ├── midscene.ts │ │ ├── prompts.ts │ │ ├── puppeteer.ts │ │ ├── resources.ts │ │ ├── tools.ts │ │ └── utils.ts │ ├── tests │ │ ├── index.test.ts │ │ ├── test-login.mjs │ │ └── tsconfig.json │ ├── tsconfig.json │ └── vitest.config.ts ├── recorder │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── rslib.config.ts │ ├── src │ │ ├── Button.tsx │ │ ├── RecordTimeline.css │ │ ├── RecordTimeline.tsx │ │ ├── button.css │ │ ├── index.tsx │ │ ├── recorder-iife-index.ts │ │ └── recorder.ts │ └── tsconfig.json ├── shared │ ├── README.md │ ├── modern.config.ts │ ├── modern.inspect.config.ts │ ├── package.json │ ├── src │ │ ├── common.ts │ │ ├── constants │ │ │ ├── example-code.ts │ │ │ └── index.ts │ │ ├── env.ts │ │ ├── extractor │ │ │ ├── constants.ts │ │ │ ├── debug.ts │ │ │ ├── dom-util.ts │ │ │ ├── index.ts │ │ │ ├── locator.ts │ │ │ ├── tree.ts │ │ │ ├── util.ts │ │ │ └── web-extractor.ts │ │ ├── img │ │ │ ├── box-select.ts │ │ │ ├── draw-box.ts │ │ │ ├── get-jimp.ts │ │ │ ├── index.ts │ │ │ ├── info.ts │ │ │ ├── jimp.d.ts │ │ │ └── transform.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── modern-app-env.d.ts │ │ ├── node │ │ │ └── fs.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── us-keyboard-layout.ts │ │ └── utils.ts │ ├── tests │ │ ├── fixtures │ │ │ ├── 2x2.jpeg │ │ │ ├── baidu.png │ │ │ ├── colorful.png │ │ │ ├── dump.json │ │ │ ├── heytea.jpeg │ │ │ ├── icon.png │ │ │ ├── long-text.png │ │ │ ├── reference-of-list.png │ │ │ ├── reference.png │ │ │ └── table.png │ │ ├── tsconfig.json │ │ ├── unit-test │ │ │ ├── __snapshots__ │ │ │ │ └── tree.test.ts.snap │ │ │ ├── fs.test.ts │ │ │ ├── image │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.ts.snap │ │ │ │ └── index.test.ts │ │ │ ├── keyboard.test.ts │ │ │ └── tree.test.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── visualizer │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── modern.config.ts │ ├── package.json │ ├── src │ │ ├── blank_polyfill.ts │ │ ├── component │ │ │ ├── blackboard.less │ │ │ ├── blackboard.tsx │ │ │ ├── color.tsx │ │ │ ├── common.less │ │ │ ├── describer.less │ │ │ ├── describer.tsx │ │ │ ├── env-config.tsx │ │ │ ├── github-star.less │ │ │ ├── github-star.tsx │ │ │ ├── logo.less │ │ │ ├── logo.tsx │ │ │ ├── misc.tsx │ │ │ ├── pixi-loader.tsx │ │ │ ├── player.less │ │ │ ├── player.tsx │ │ │ ├── playground-demo-ui-context.json │ │ │ ├── playground │ │ │ │ ├── ConfigSelector.tsx │ │ │ │ ├── ContextPreview.tsx │ │ │ │ ├── HistorySelector.tsx │ │ │ │ ├── PlaygroundResult.tsx │ │ │ │ ├── PromptInput.tsx │ │ │ │ ├── ServiceModeControl.tsx │ │ │ │ ├── index.less │ │ │ │ ├── playground-constants.tsx │ │ │ │ ├── playground-types.ts │ │ │ │ ├── playground-utils.ts │ │ │ │ ├── useServerValid.ts │ │ │ │ └── useStaticPageAgent.ts │ │ │ ├── replay-scripts.tsx │ │ │ ├── shiny-text.less │ │ │ ├── shiny-text.tsx │ │ │ └── store │ │ │ │ ├── history.ts │ │ │ │ └── store.tsx │ │ ├── extension │ │ │ ├── jimp.d.ts │ │ │ └── utils.ts │ │ ├── global.d.ts │ │ ├── icons │ │ │ ├── close.svg │ │ │ ├── history.svg │ │ │ ├── magnifying-glass.svg │ │ │ └── setting.svg │ │ ├── index.tsx │ │ ├── init.ts │ │ ├── types.d.ts │ │ └── utils.ts │ └── tsconfig.json └── web-integration │ ├── .gitignore │ ├── README.md │ ├── bin │ └── midscene-playground │ ├── modern.config.ts │ ├── package.json │ ├── scripts │ └── check-exports.js │ ├── src │ ├── bridge-mode │ │ ├── agent-cli-side.ts │ │ ├── browser.ts │ │ ├── common.ts │ │ ├── index.ts │ │ ├── io-client.ts │ │ ├── io-server.ts │ │ └── page-browser-side.ts │ ├── chrome-extension │ │ ├── agent.ts │ │ ├── cdpInput.ts │ │ ├── dynamic-scripts.ts │ │ ├── index.ts │ │ └── page.ts │ ├── common │ │ ├── agent.ts │ │ ├── page.d.ts │ │ ├── plan-builder.ts │ │ ├── task-cache.ts │ │ ├── tasks.ts │ │ ├── ui-utils.ts │ │ └── utils.ts │ ├── index.ts │ ├── page.ts │ ├── playground │ │ ├── agent.ts │ │ ├── bin.ts │ │ ├── index.ts │ │ ├── server.ts │ │ └── static-page.ts │ ├── playwright │ │ ├── ai-fixture.ts │ │ ├── index.ts │ │ ├── page.ts │ │ └── reporter │ │ │ ├── index.ts │ │ │ └── select-cache-file.ts │ ├── puppeteer │ │ ├── agent-launcher.ts │ │ ├── base-page.ts │ │ ├── index.ts │ │ └── page.ts │ ├── web-element.ts │ └── yaml │ │ ├── builder.ts │ │ ├── index.ts │ │ ├── player.ts │ │ └── utils.ts │ ├── tests │ ├── ai │ │ ├── bridge │ │ │ ├── agent.test.ts │ │ │ ├── keyboard-event.test.ts │ │ │ ├── open-new-tab.test.ts │ │ │ └── temp.test.ts │ │ ├── fixtures │ │ │ └── ui-context.json │ │ └── web │ │ │ ├── playwright-reporter-test │ │ │ └── todo-report.spec.ts │ │ │ ├── playwright │ │ │ ├── ai-auto-todo.spec.ts │ │ │ ├── ai-online-order.spec.ts │ │ │ ├── ai-shop.spec.ts │ │ │ ├── fixture.ts │ │ │ ├── memory-release.spec.ts │ │ │ ├── open-new-tab.spec.ts │ │ │ └── util.ts │ │ │ ├── puppeteer │ │ │ ├── agent.test.ts │ │ │ ├── e2e.test.ts │ │ │ ├── open-new-tab.test.ts │ │ │ ├── query.test.ts │ │ │ ├── scroll.html │ │ │ └── utils.ts │ │ │ └── static │ │ │ └── static-page.test.ts │ ├── playwright.config.ts │ ├── tsconfig.json │ └── unit-test │ │ ├── __snapshots__ │ │ ├── agent.test.ts.snap │ │ ├── plan-builder.test.ts.snap │ │ ├── task-cache.test.ts.snap │ │ ├── web-extractor.test.ts.snap │ │ └── yaml.test.ts.snap │ │ ├── agent.test.ts │ │ ├── bridge │ │ └── io.test.ts │ │ ├── fixtures │ │ ├── cookie │ │ │ └── httpbin.dev_cookies.json │ │ ├── dump-with-invisible.json │ │ ├── dump.json │ │ ├── extractor │ │ │ ├── child.html │ │ │ └── scroll │ │ │ │ ├── input.png │ │ │ │ └── output.png │ │ └── web-extractor │ │ │ ├── assets │ │ │ ├── search-dark.svg │ │ │ └── search.svg │ │ │ ├── child.html │ │ │ ├── index.html │ │ │ ├── input.png │ │ │ ├── merge-rects.html │ │ │ ├── output.png │ │ │ └── scroll │ │ │ ├── input.png │ │ │ └── output.png │ │ ├── http-server.d.ts │ │ ├── page-task-executor-rightclick.test.ts │ │ ├── plan-builder.test.ts │ │ ├── playground-server.test.ts │ │ ├── task-cache.test.ts │ │ ├── util.test.ts │ │ ├── web-extractor.test.ts │ │ └── yaml │ │ ├── __snapshots__ │ │ ├── player.test.ts.snap │ │ └── utils.test.ts.snap │ │ ├── player.test.ts │ │ ├── server_root │ │ └── index.html │ │ └── utils.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── scripts ├── dictionary.txt └── release.js /.github/ISSUE_TEMPLATE/feature-request.en-US.yml: -------------------------------------------------------------------------------- 1 | name: '💡 Feature Request' 2 | description: Submit a new feature request to Midscene 3 | title: '[Feature]: ' 4 | type: Enhancement 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for submitting new feature requests! Before submitting, please note: 10 | 11 | - Confirmed that this is a common feature and cannot be implemented through existing APIs. 12 | - Make sure you searched in the [Issues](https://github.com/web-infra-dev/midscene/issues) and didn't find the same request. 13 | 14 | - type: textarea 15 | id: description 16 | attributes: 17 | label: What problem does this feature solve? 18 | description: Please describe the usage scenario for this feature. 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: api 24 | attributes: 25 | label: What does the proposed API look like? 26 | description: Describe the new API, provide some code examples. 27 | validations: 28 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/llm-connectivity-issue---模型连接错误.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: LLM Connectivity Issue / 模型连接错误 3 | about: How to solve the LLM connectivity problem 4 | title: "[Connectivity]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Read this before open issue 11 | 12 | How to choose and config a model: https://midscenejs.com/model-provider.html 13 | 14 | Use this project to check the connection: https://github.com/web-infra-dev/midscene-example/tree/main/connectivity-test 15 | 16 | ## If the error persists, tell us these information 17 | 18 | - Where are you using Midscene.js (Chrome extension, yaml with cli, Puppeteer,…) 19 | 20 | - The version of Midscene.js or Extension 21 | 22 | - The error message 23 | 24 | - The model name and endpoint (if could be public) 25 | 26 | ## Security Check 27 | 28 | Do NOT include your API key in your issue! Revoke it immediately if it has already been leaked in your issue. 29 | -------------------------------------------------------------------------------- /.github/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | 'change: feat': 2 | - '/^(feat|types|style)/' 3 | 'change: fix': 4 | - '/^fix/' 5 | 'change: perf': 6 | - '/^perf/' 7 | 'change: breaking': 8 | - '/^breaking change/' 9 | 'change: docs': 10 | - '/^docs/' 11 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | exclude: 5 | authors: 6 | # Ignore the release PR created by github-actions 7 | - github-actions 8 | categories: 9 | - title: Breaking Changes 🍭 10 | labels: 11 | - 'change: breaking' 12 | - title: New Features 🎉 13 | labels: 14 | - 'change: feat' 15 | - title: Performance 🚀 16 | labels: 17 | - 'change: perf' 18 | - title: Bug Fixes 🐞 19 | labels: 20 | - 'change: fix' 21 | - title: Document 📖 22 | labels: 23 | - 'change: docs' 24 | - title: Other Changes 25 | labels: 26 | - '*' 27 | -------------------------------------------------------------------------------- /.github/workflows/issue-close-require.yml: -------------------------------------------------------------------------------- 1 | name: Issue Close Require 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | permissions: 8 | # Permits `actions-cool/issues-helper` to close an issue 9 | issues: write 10 | contents: read 11 | 12 | jobs: 13 | issue-close-require: 14 | runs-on: ubuntu-latest 15 | if: github.repository == 'web-infra-dev/midscene' 16 | steps: 17 | - name: need reproduction 18 | uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3.6.0 19 | with: 20 | actions: 'close-issues' 21 | labels: 'need reproduction' 22 | inactive-day: 5 23 | body: | 24 | As the issue was labelled with `need reproduction`, but no response in 5 days. This issue will be closed. Feel free to comment and reopen it if you have any further questions. For background, see [Why reproductions are required](https://antfu.me/posts/why-reproductions-are-required). 25 | 26 | 由于该 issue 被标记为 "需要重现",但在 5 天内没有回应,因此该 issue 将被关闭。如果你有任何进一步的问题,请随时发表评论并重新打开该 issue。背景请参考 [为什么需要最小重现](https://antfu.me/posts/why-reproductions-are-required-zh)。 -------------------------------------------------------------------------------- /.github/workflows/issue-labeled.yml: -------------------------------------------------------------------------------- 1 | name: Issue Labeled 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | permissions: 8 | contents: read 9 | # Permits `actions-cool/issues-helper` to comment on an issue 10 | issues: write 11 | 12 | jobs: 13 | reply-labeled: 14 | name: Reply need reproduction 15 | runs-on: ubuntu-latest 16 | if: github.repository == 'web-infra-dev/midscene' 17 | steps: 18 | - name: need reproduction 19 | if: github.event.label.name == 'need reproduction' 20 | uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3.6.0 21 | with: 22 | actions: 'create-comment' 23 | issue-number: ${{ github.event.issue.number }} 24 | body: | 25 | Hello @${{ github.event.issue.user.login }}. Please provide a reproduction repository or online demo. For background, see [Why reproductions are required](https://antfu.me/posts/why-reproductions-are-required). Thanks ❤️ -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | main: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | node-version: [18.19.0] 20 | 21 | env: 22 | MIDSCENE_OPENAI_INIT_CONFIG_JSON: ${{ secrets.MIDSCENE_OPENAI_INIT_CONFIG_JSON }} 23 | MIDSCENE_OPENAI_MODEL: ${{ secrets.MIDSCENE_OPENAI_MODEL }} 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Fetch all branches 31 | run: git fetch --all 32 | 33 | - name: Setup pnpm 34 | uses: pnpm/action-setup@v2 35 | with: 36 | version: 9.3.0 37 | 38 | - name: Setup Node.js 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: '18' 42 | cache: 'pnpm' 43 | 44 | - name: Install Dependencies 45 | run: pnpm install --ignore-scripts 46 | 47 | - name: Check Dependency Version 48 | run: pnpm run check-dependency-version 49 | 50 | - name: Biome lint 51 | run: npx biome check . --diagnostic-level=warn --no-errors-on-unmatched 52 | 53 | -------------------------------------------------------------------------------- /.github/workflows/pr-label.yml: -------------------------------------------------------------------------------- 1 | name: PR Labeler 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | 9 | permissions: 10 | # Permits `github/issue-labeler` to add a label to a pull request 11 | pull-requests: write 12 | contents: read 13 | 14 | jobs: 15 | change-labeling: 16 | name: Labeling for changes 17 | runs-on: ubuntu-latest 18 | if: github.repository == 'web-infra-dev/midscene' 19 | steps: 20 | - uses: github/issue-labeler@v3.4 21 | with: 22 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 23 | configuration-path: .github/pr-labeler.yml 24 | enable-versioned-regex: 0 25 | include-title: 1 26 | sync-labels: 1 27 | 28 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no -- commitlint --edit "$1" -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | strict-peer-dependencies=false 3 | save-prefix='' 4 | save-workspace-protocol=rolling 5 | ignore-compatibility-db=true 6 | use-lockfile-v6=true 7 | puppeteer_download_base_url=https://cdn.npmmirror.com/binaries/chrome-for-testing -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.organizeImports.biome": "explicit" 4 | }, 5 | "editor.defaultFormatter": "biomejs.biome", 6 | "editor.formatOnSave": true, 7 | "cSpell.words": [ 8 | "AITEST", 9 | "Aliyun", 10 | "aweme", 11 | "bbox", 12 | "bytedance", 13 | "deepseek", 14 | "doubao", 15 | "douyin", 16 | "fkill", 17 | "httpbin", 18 | "iconfont", 19 | "modelcontextprotocol", 20 | "openrouter", 21 | "qwen", 22 | "taobao", 23 | "targetcreated", 24 | "Volcengine", 25 | "xpaths", 26 | "Yadb" 27 | ], 28 | "[jsonc]": { 29 | "editor.defaultFormatter": "biomejs.biome" 30 | }, 31 | "[plaintext]": { 32 | "editor.defaultFormatter": "esbenp.prettier-vscode" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Bytedance, Inc. and its affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/android-playground/README.md: -------------------------------------------------------------------------------- 1 | # Midscene Android Playground 2 | 3 | Playground tool for Android cli @midscene/android. 4 | 5 | See https://midscenejs.com/ for details. 6 | -------------------------------------------------------------------------------- /apps/android-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "android-playground", 3 | "private": true, 4 | "version": "0.12.4", 5 | "type": "module", 6 | "scripts": { 7 | "build": "rsbuild build", 8 | "dev": "rsbuild dev --open", 9 | "preview": "rsbuild preview" 10 | }, 11 | "dependencies": { 12 | "@ant-design/icons": "^5.3.1", 13 | "@midscene/android": "workspace:*", 14 | "@midscene/core": "workspace:*", 15 | "@midscene/shared": "workspace:*", 16 | "@midscene/visualizer": "workspace:*", 17 | "@midscene/web": "workspace:*", 18 | "@yume-chan/scrcpy": "^1.1.0", 19 | "@yume-chan/scrcpy-decoder-webcodecs": "1.1.0", 20 | "antd": "^5.21.6", 21 | "dayjs": "^1.11.11", 22 | "react": "18.3.1", 23 | "react-dom": "18.3.1", 24 | "socket.io-client": "4.8.1" 25 | }, 26 | "devDependencies": { 27 | "@rsbuild/core": "^1.3.22", 28 | "@rsbuild/plugin-less": "^1.2.4", 29 | "@rsbuild/plugin-node-polyfill": "1.3.0", 30 | "@rsbuild/plugin-react": "^1.3.1", 31 | "@rsbuild/plugin-svgr": "^1.1.1", 32 | "@types/react": "^18.3.1", 33 | "@types/react-dom": "^18.3.1", 34 | "archiver": "^6.0.0", 35 | "less": "^4.2.0", 36 | "typescript": "^5.8.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /apps/android-playground/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="@rsbuild/core/types" /> 2 | 3 | declare module '*.svg' { 4 | const content: string; 5 | export default content; 6 | } 7 | declare module '*.svg?react' { 8 | const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>; 9 | export default ReactComponent; 10 | } 11 | -------------------------------------------------------------------------------- /apps/android-playground/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/android-playground/src/favicon.ico -------------------------------------------------------------------------------- /apps/android-playground/src/icons/linked.svg: -------------------------------------------------------------------------------- 1 | <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path fill-rule="evenodd" clip-rule="evenodd" d="M9.68001 1.99996C10.88 0.799959 12.88 0.719959 14.08 1.91996C15.28 3.11996 15.2 5.11996 14 6.31996L11.84 8.47996C11.6 8.71996 11.28 8.71996 11.04 8.47996C10.8 8.23996 10.8 7.91996 11.04 7.67996L13.2 5.51996C14 4.71996 14 3.43996 13.28 2.71996C12.56 1.99996 11.28 1.99996 10.48 2.79996L8.32001 4.95996C8.08001 5.19996 7.76001 5.19996 7.52001 4.95996C7.28001 4.71996 7.28001 4.39996 7.52001 4.15996L9.68001 1.99996ZM5.60001 13.2L7.76001 11.04C8.00001 10.8 8.32001 10.8 8.56001 11.04C8.80001 11.28 8.80001 11.6 8.56001 11.84L6.40001 14C5.20001 15.2 3.28001 15.36 2.00001 14.08C0.640014 12.8 0.800014 10.88 2.08001 9.67996L4.24001 7.51996C4.48001 7.27996 4.80001 7.27996 5.04001 7.51996C5.28001 7.75996 5.28001 8.07996 5.04001 8.31996L2.88001 10.48C2.08001 11.28 2.00001 12.48 2.80001 13.28C3.60001 14.08 4.80001 14 5.60001 13.2ZM10.24 4.95991C10.48 4.71991 10.88 4.71991 11.2 4.95991C11.44 5.19991 11.44 5.59991 11.2 5.91991L5.92001 11.1999C5.68001 11.4399 5.28001 11.4399 4.96001 11.1999C4.72001 10.9599 4.72001 10.5599 4.96001 10.2399L10.24 4.95991Z" fill="black" fill-opacity="0.25"/> 3 | </svg> 4 | -------------------------------------------------------------------------------- /apps/android-playground/src/icons/unlink.svg: -------------------------------------------------------------------------------- 1 | <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M14.08 1.92002C12.88 0.72002 10.88 0.80002 9.68 2.00002L7.52 4.16002C7.28 4.40002 7.28 4.72002 7.52 4.96002C7.76 5.20002 8.08 5.20002 8.32 4.96002L10.48 2.80002C11.28 2.00002 12.56 2.00002 13.28 2.72002C14 3.44002 14 4.72002 13.2 5.52002L11.04 7.68002C10.8 7.92002 10.8 8.24002 11.04 8.48002C11.28 8.72002 11.6 8.72002 11.84 8.48002L14 6.32002C15.2 5.12002 15.28 3.12002 14.08 1.92002ZM7.76 11.04L5.6 13.2C4.8 14 3.6 14.08 2.8 13.28C2 12.48 2.08 11.28 2.88 10.48L5.04 8.32002C5.28 8.08002 5.28 7.76002 5.04 7.52002C4.8 7.28002 4.48 7.28002 4.24 7.52002L2.08 9.68002C0.799999 10.88 0.639999 12.8 2 14.08C3.28 15.36 5.2 15.2 6.4 14L8.56 11.84C8.8 11.6 8.8 11.28 8.56 11.04C8.32 10.8 8 10.8 7.76 11.04ZM5.92 4.96002C5.68 4.72002 5.28 4.72002 4.96 4.96002C4.72 5.20002 4.72 5.60002 4.96 5.92002L10.24 11.2C10.48 11.44 10.88 11.44 11.2 11.2C11.44 10.96 11.44 10.56 11.2 10.24L5.92 4.96002Z" fill="#FF4550"/> 3 | </svg> 4 | -------------------------------------------------------------------------------- /apps/android-playground/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | 5 | const rootEl = document.getElementById('root'); 6 | if (rootEl) { 7 | const root = ReactDOM.createRoot(rootEl); 8 | root.render(<App />); 9 | } 10 | -------------------------------------------------------------------------------- /apps/android-playground/src/scripts/blank_polyfill.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /apps/android-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "ES2020"], 4 | "jsx": "react-jsx", 5 | "target": "ES2020", 6 | "skipLibCheck": true, 7 | "useDefineForClassFields": true, 8 | 9 | /* modules */ 10 | "module": "ESNext", 11 | "isolatedModules": true, 12 | "resolveJsonModule": true, 13 | "moduleResolution": "bundler", 14 | "allowImportingTsExtensions": true, 15 | "noEmit": true, 16 | 17 | /* type checking */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /apps/chrome-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-extension", 3 | "private": true, 4 | "version": "0.12.4", 5 | "type": "module", 6 | "scripts": { 7 | "build": "rsbuild build && npm run pack-extension", 8 | "dev": "rsbuild dev --open", 9 | "preview": "rsbuild preview", 10 | "pack-extension": "node scripts/pack-extension.js" 11 | }, 12 | "dependencies": { 13 | "@ant-design/icons": "^5.3.1", 14 | "@midscene/core": "workspace:*", 15 | "@midscene/recorder": "workspace:*", 16 | "@midscene/report": "workspace:*", 17 | "@midscene/shared": "workspace:*", 18 | "@midscene/visualizer": "workspace:*", 19 | "@midscene/web": "workspace:*", 20 | "antd": "^5.21.6", 21 | "canvas-confetti": "1.9.3", 22 | "dayjs": "^1.11.11", 23 | "react": "18.3.1", 24 | "react-dom": "18.3.1", 25 | "zustand": "4.5.2" 26 | }, 27 | "devDependencies": { 28 | "@rsbuild/core": "^1.3.22", 29 | "@rsbuild/plugin-less": "^1.2.4", 30 | "@rsbuild/plugin-node-polyfill": "1.3.0", 31 | "@rsbuild/plugin-react": "^1.3.1", 32 | "@rsbuild/plugin-svgr": "^1.1.1", 33 | "@rsbuild/plugin-type-check": "1.2.3", 34 | "@types/chrome": "0.0.279", 35 | "@types/react": "^18.3.1", 36 | "@types/react-dom": "^18.3.1", 37 | "archiver": "^6.0.0", 38 | "less": "^4.2.0", 39 | "typescript": "^5.8.3" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/App.less: -------------------------------------------------------------------------------- 1 | .command-form { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | overflow: hidden; 6 | } 7 | 8 | .form-content { 9 | display: grid; 10 | grid-template-rows: auto 1fr; 11 | height: 100%; 12 | min-height: 0; 13 | position: relative; 14 | gap: 24px; 15 | } 16 | 17 | .result-container { 18 | grid-row: 2; 19 | display: flex; 20 | flex-direction: column; 21 | min-height: 0; 22 | position: relative; 23 | overflow: auto; 24 | } 25 | 26 | .result-container>div { 27 | flex: 1; 28 | height: auto; 29 | min-height: 0; 30 | } -------------------------------------------------------------------------------- /apps/chrome-extension/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.less'; 2 | import { PlaygroundPopup } from './extension/popup'; 3 | 4 | export default function App() { 5 | return <PlaygroundPopup />; 6 | } 7 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="@rsbuild/core/types" /> 2 | 3 | declare module '*.svg' { 4 | const content: string; 5 | export default content; 6 | } 7 | declare module '*.svg?react' { 8 | const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>; 9 | export default ReactComponent; 10 | } 11 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/extension/common.less: -------------------------------------------------------------------------------- 1 | @main-text: #3b3b3b; 2 | 3 | @primary-color: #2B83FF; 4 | @main-orange: #F9483E; 5 | 6 | @side-bg: #F8F8F8; 7 | @title-bg: @side-bg; 8 | @border-color: #E5E5E5; 9 | @heavy-border-color: #888; 10 | 11 | @selected-bg: #bfc4da80; 12 | @hover-bg: #dcdcdc80; 13 | 14 | @weak-bg: #F3F3F3; 15 | @weak-text: #777; 16 | @footer-text: #CCC; 17 | 18 | @toolbar-btn-bg: #E9E9E9; 19 | 20 | @layout-space: 20px; 21 | 22 | @side-horizontal-padding: 10px; 23 | @side-vertical-spacing: 10px; 24 | 25 | @layout-extension-space-horizontal: 20px; 26 | @layout-extension-space-vertical: 20px; 27 | 28 | .clear-button-container { 29 | width: 28; 30 | height: 28; 31 | border-radius: 4px; 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | margin: 4px 0; 36 | position: fixed; 37 | top: 60px; 38 | right: 12px; 39 | background: #fff; 40 | z-index: 999; 41 | border: 1px solid rgba(240, 240, 240); 42 | } -------------------------------------------------------------------------------- /apps/chrome-extension/src/extension/misc.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ArrowRightOutlined, 3 | CheckOutlined, 4 | ClockCircleOutlined, 5 | CloseOutlined, 6 | LogoutOutlined, 7 | MinusOutlined, 8 | WarningOutlined, 9 | } from '@ant-design/icons'; 10 | 11 | export const iconForStatus = (status: string): JSX.Element => { 12 | switch (status) { 13 | case 'finished': 14 | case 'passed': 15 | case 'success': 16 | case 'connected': 17 | return ( 18 | <span style={{ color: '#2B8243' }}> 19 | <CheckOutlined /> 20 | </span> 21 | ); 22 | 23 | case 'finishedWithWarning': 24 | return ( 25 | <span style={{ color: '#f7bb05' }}> 26 | <WarningOutlined /> 27 | </span> 28 | ); 29 | case 'failed': 30 | case 'closed': 31 | case 'timedOut': 32 | case 'interrupted': 33 | return ( 34 | <span style={{ color: '#FF0A0A' }}> 35 | <CloseOutlined /> 36 | </span> 37 | ); 38 | case 'pending': 39 | return <ClockCircleOutlined />; 40 | case 'cancelled': 41 | case 'skipped': 42 | return <LogoutOutlined />; 43 | case 'running': 44 | return <ArrowRightOutlined />; 45 | default: 46 | return <MinusOutlined />; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/extension/recorder/components/index.ts: -------------------------------------------------------------------------------- 1 | export { RecordList } from './RecordList'; 2 | export { RecordDetail } from './RecordDetail'; 3 | export { SessionModals } from './SessionModals'; 4 | export { ProgressModal } from './ProgressModal'; 5 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/extension/recorder/generators/index.ts: -------------------------------------------------------------------------------- 1 | // Main generators 2 | export { generatePlaywrightTest } from './playwrightGenerator'; 3 | export { generateYamlTest, exportEventsToYaml } from './yamlGenerator'; 4 | 5 | // Shared utilities 6 | export * from './shared/types'; 7 | export * from './shared/testGenerationUtils'; 8 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/extension/recorder/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useRecordingSession } from './useRecordingSession'; 2 | export { useRecordingControl } from './useRecordingControl'; 3 | export { useTabMonitoring } from './useTabMonitoring'; 4 | export { useLifecycleCleanup } from './useLifecycleCleanup'; 5 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/icons/play.svg: -------------------------------------------------------------------------------- 1 | <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M3.4668 2.05664C3.66983 1.93942 3.92001 1.93942 4.12305 2.05664L11.7012 6.43164C11.904 6.54891 12.0293 6.76565 12.0293 7C12.0293 7.23435 11.904 7.45109 11.7012 7.56836L4.12305 11.9434C3.92001 12.0606 3.66983 12.0606 3.4668 11.9434C3.26375 11.8261 3.13867 11.6095 3.13867 11.375V2.625C3.13867 2.39054 3.26375 2.17387 3.4668 2.05664Z" stroke="white" stroke-width="1.3125" stroke-linejoin="round"/> 3 | </svg> 4 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/icons/playground-2.svg: -------------------------------------------------------------------------------- 1 | <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M3.55921 2.65821C4.0413 2.20419 4.76402 2.12006 5.33753 2.45118L16.1627 8.70118C16.6268 8.96913 16.9127 9.46411 16.9127 10C16.9127 10.5359 16.6268 11.0309 16.1627 11.2988L5.33753 17.5488C4.76402 17.88 4.0413 17.7958 3.55921 17.3418C3.07715 16.8878 2.94958 16.1714 3.24573 15.5791L6.03479 10L3.24573 4.42091C2.94958 3.82861 3.07715 3.11224 3.55921 2.65821ZM7.71253 10L4.58753 16.25L15.4127 10L4.58753 3.75001L7.71253 10Z" fill="currentColor"/> 3 | </svg> 4 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/icons/playground.svg: -------------------------------------------------------------------------------- 1 | <svg 2 | width="20" 3 | height="20" 4 | viewBox="0 0 20 20" 5 | fill="none" 6 | xmlns="http://www.w3.org/2000/svg" 7 | aria-label="Playground" 8 | > 9 | <title>Playground</title> 10 | <rect width="20" height="20" rx="10" fill="#2B83FF" /> 11 | <path 12 | d="M6.86597 5.88171C7.04673 5.71147 7.3179 5.67947 7.53296 5.80359L10.781 7.67859L14.0281 9.55359C14.2021 9.65407 14.3093 9.83993 14.3093 10.0409C14.3093 10.2419 14.2021 10.4277 14.0281 10.5282L10.781 12.4032L7.53296 14.2782C7.3179 14.4023 7.04673 14.3703 6.86597 14.2001C6.68535 14.0298 6.63776 13.761 6.74878 13.5389L8.4978 10.0409L6.74878 6.54285C6.63776 6.32081 6.68535 6.05198 6.86597 5.88171Z" 13 | fill="#2B83FF" 14 | stroke="white" 15 | stroke-width="1.125" 16 | stroke-linejoin="round" 17 | /> 18 | </svg> -------------------------------------------------------------------------------- /apps/chrome-extension/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | 5 | const rootEl = document.getElementById('root'); 6 | if (rootEl) { 7 | const root = ReactDOM.createRoot(rootEl); 8 | root.render(<App />); 9 | } 10 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/scripts/blank_polyfill.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /apps/chrome-extension/src/scripts/stop-water-flow.ts: -------------------------------------------------------------------------------- 1 | if (typeof window.midsceneWaterFlowAnimation !== 'undefined') { 2 | window.midsceneWaterFlowAnimation.disable(); 3 | } 4 | -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-10-black/open-sans-10-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-10-black/open-sans-10-black.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-12-black/open-sans-12-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-12-black/open-sans-12-black.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-128-black/open-sans-128-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-128-black/open-sans-128-black.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-128-white/open-sans-128-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-128-white/open-sans-128-white.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-14-black/open-sans-14-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-14-black/open-sans-14-black.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-16-black/open-sans-16-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-16-black/open-sans-16-black.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-16-white/open-sans-16-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-16-white/open-sans-16-white.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-32-black/open-sans-32-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-32-black/open-sans-32-black.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-32-white/open-sans-32-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-32-white/open-sans-32-white.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-64-black/open-sans-64-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-64-black/open-sans-64-black.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-64-white/open-sans-64-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-64-white/open-sans-64-white.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-8-black/open-sans-8-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-8-black/open-sans-8-black.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/fonts/open-sans/open-sans-8-white/open-sans-8-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/fonts/open-sans/open-sans-8-white/open-sans-8-white.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/chrome-extension/static/icon128.png -------------------------------------------------------------------------------- /apps/chrome-extension/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Midscene.js", 3 | "description": "Open-source SDK for automating web pages using natural language through AI.", 4 | "version": "0.82", 5 | "manifest_version": 3, 6 | "permissions": [ 7 | "activeTab", 8 | "tabs", 9 | "sidePanel", 10 | "debugger", 11 | "scripting" 12 | ], 13 | "incognito": "split", 14 | "background": { 15 | "service_worker": "./scripts/worker.js" 16 | }, 17 | "host_permissions": [ 18 | "<all_urls>" 19 | ], 20 | "action": { 21 | "default_icon": "icon128.png", 22 | "default_title": "Midscene.js" 23 | }, 24 | "side_panel": { 25 | "default_path": "./index.html" 26 | }, 27 | "icons": { 28 | "128": "icon128.png" 29 | } 30 | } -------------------------------------------------------------------------------- /apps/chrome-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "ES2020"], 4 | "jsx": "react-jsx", 5 | "target": "ES2020", 6 | "skipLibCheck": true, 7 | "useDefineForClassFields": true, 8 | 9 | /* modules */ 10 | "module": "ESNext", 11 | "isolatedModules": true, 12 | "resolveJsonModule": true, 13 | "moduleResolution": "bundler", 14 | "allowImportingTsExtensions": true, 15 | "noEmit": true, 16 | 17 | /* type checking */ 18 | "strict": true, 19 | "noUnusedLocals": false, 20 | "noUnusedParameters": false 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /apps/recorder-form/.gitignore: -------------------------------------------------------------------------------- 1 | # Local 2 | .DS_Store 3 | *.local 4 | *.log* 5 | 6 | # Dist 7 | node_modules 8 | dist/ 9 | 10 | # IDE 11 | .vscode/* 12 | !.vscode/extensions.json 13 | .idea 14 | -------------------------------------------------------------------------------- /apps/recorder-form/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recorder-form", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "rsbuild build", 8 | "dev": "rsbuild dev --open", 9 | "preview": "rsbuild preview" 10 | }, 11 | "dependencies": { 12 | "antd": "^5.21.6", 13 | "dayjs": "^1.11.11", 14 | "react": "18.3.1", 15 | "react-dom": "18.3.1", 16 | "@midscene/recorder": "workspace:*" 17 | }, 18 | "devDependencies": { 19 | "@rsbuild/plugin-node-polyfill": "1.3.0", 20 | "@rsbuild/core": "^1.3.22", 21 | "@rsbuild/plugin-react": "^1.3.1", 22 | "@types/react": "^18.3.1", 23 | "@types/react-dom": "^18.3.1", 24 | "typescript": "^5.8.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/recorder-form/rsbuild.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@rsbuild/core'; 2 | import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'; 3 | import { pluginReact } from '@rsbuild/plugin-react'; 4 | 5 | export default defineConfig({ 6 | server: { 7 | port: 3001, 8 | }, 9 | plugins: [pluginReact(), pluginNodePolyfill()], 10 | }); 11 | -------------------------------------------------------------------------------- /apps/recorder-form/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="@rsbuild/core/types" /> 2 | -------------------------------------------------------------------------------- /apps/recorder-form/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | 5 | const rootEl = document.getElementById('root'); 6 | if (rootEl) { 7 | const root = ReactDOM.createRoot(rootEl); 8 | root.render( 9 | <React.StrictMode> 10 | <App /> 11 | </React.StrictMode>, 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/recorder-form/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "ES2020"], 4 | "jsx": "react-jsx", 5 | "target": "ES2020", 6 | "noEmit": true, 7 | "skipLibCheck": true, 8 | "useDefineForClassFields": true, 9 | 10 | /* modules */ 11 | "module": "ESNext", 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "moduleResolution": "bundler", 15 | "allowImportingTsExtensions": true, 16 | 17 | /* type checking */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true 21 | }, 22 | "include": ["src", "../../packages/record/src/RecordTimeline.tsx"] 23 | } 24 | -------------------------------------------------------------------------------- /apps/report/.gitignore: -------------------------------------------------------------------------------- 1 | # Local 2 | .DS_Store 3 | *.local 4 | *.log* 5 | 6 | # Dist 7 | node_modules 8 | dist/ 9 | 10 | # IDE 11 | .vscode/* 12 | !.vscode/extensions.json 13 | .idea 14 | 15 | # Midscene.js dump files 16 | midscene_run/report 17 | midscene_run/tmp 18 | -------------------------------------------------------------------------------- /apps/report/e2e/check-html.yaml: -------------------------------------------------------------------------------- 1 | target: 2 | serve: ./dist 3 | url: demo.html 4 | tasks: 5 | - name: check console html 6 | flow: 7 | - ai: Click the 'Insight / Locate' on Left 8 | - sleep: 3000 9 | - aiAssert: There is a 'Open in Playground' button on the page 10 | -------------------------------------------------------------------------------- /apps/report/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@midscene/report", 3 | "version": "0.13.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "rsbuild dev", 7 | "build": "rsbuild build", 8 | "dev:rsdoctor": "RSDOCTOR=true rsbuild dev", 9 | "build:rsdoctor": "RSDOCTOR=true rsbuild build", 10 | "preview": "rsbuild preview", 11 | "e2e": "node ../../packages/cli/bin/midscene ./e2e/" 12 | }, 13 | "dependencies": { 14 | "@ant-design/icons": "^5.3.1", 15 | "@midscene/core": "workspace:*", 16 | "@midscene/visualizer": "workspace:*", 17 | "@midscene/web": "workspace:*", 18 | "@midscene/shared": "workspace:*", 19 | "@modern-js/runtime": "2.60.6", 20 | "@rsbuild/core": "^1.3.22", 21 | "@rsbuild/plugin-less": "^1.2.4", 22 | "@rsbuild/plugin-react": "^1.3.1", 23 | "@types/chrome": "0.0.279", 24 | "antd": "^5.21.6", 25 | "pixi-filters": "6.0.5", 26 | "pixi.js": "8.1.1", 27 | "react": "18.3.1", 28 | "react-dom": "18.3.1", 29 | "react-resizable-panels": "2.0.22", 30 | "zustand": "4.5.2" 31 | }, 32 | "devDependencies": { 33 | "@rsbuild/plugin-node-polyfill": "1.3.0", 34 | "@rsdoctor/rspack-plugin": "1.0.2", 35 | "@types/react": "^18.3.1", 36 | "@types/react-dom": "^18.3.1", 37 | "less": "^4.2.0", 38 | "typescript": "^5.8.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /apps/report/src/App.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/report/src/App.less -------------------------------------------------------------------------------- /apps/report/src/blank_polyfill.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /apps/report/src/components/common.less: -------------------------------------------------------------------------------- 1 | @main-text: #3b3b3b; 2 | 3 | @primary-color: #06b1ab; 4 | @main-orange: #F9483E; 5 | 6 | @side-bg: #F8F8F8; 7 | @title-bg: @side-bg; 8 | @border-color: #E5E5E5; 9 | @heavy-border-color: #888; 10 | 11 | @selected-bg: #bfc4da80; 12 | @hover-bg: #dcdcdc80; 13 | 14 | @weak-bg: #F3F3F3; 15 | @weak-text: #777; 16 | @footer-text: #CCC; 17 | 18 | @toolbar-btn-bg: #E9E9E9; 19 | 20 | @layout-space: 20px; 21 | 22 | @side-horizontal-padding: 10px; 23 | @side-vertical-spacing: 10px; 24 | 25 | 26 | @layout-extension-space-horizontal: 20px; 27 | @layout-extension-space-vertical: 20px; 28 | -------------------------------------------------------------------------------- /apps/report/src/components/global-hover-preview.less: -------------------------------------------------------------------------------- 1 | @import './common.less'; 2 | 3 | @max-size: 400px; 4 | .global-hover-preview { 5 | position: fixed; 6 | display: block; 7 | max-width: @max-size; 8 | max-height: @max-size; 9 | overflow: hidden; 10 | z-index: 10; 11 | text-align: center; 12 | border: 1px solid @border-color; 13 | box-sizing: border-box; 14 | background: @side-bg; 15 | box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, 0.2); 16 | 17 | img { 18 | max-width: @max-size; 19 | max-height: @max-size; 20 | width: auto; 21 | height: auto; 22 | } 23 | } -------------------------------------------------------------------------------- /apps/report/src/components/open-in-playground.less: -------------------------------------------------------------------------------- 1 | .playground-drawer { 2 | .ant-drawer-body { 3 | padding: 0; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /apps/report/src/components/panel-title.less: -------------------------------------------------------------------------------- 1 | @import './common.less'; 2 | 3 | 4 | .panel-title { 5 | background: @title-bg; 6 | border-top: 1px solid @border-color; 7 | border-bottom: 1px solid @border-color; 8 | margin-top: -1px; 9 | padding: 2px @side-horizontal-padding; 10 | .task-list-name { 11 | font-weight: bold; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/report/src/components/panel-title.tsx: -------------------------------------------------------------------------------- 1 | import './panel-title.less'; 2 | 3 | const PanelTitle = (props: { 4 | title: string; 5 | subTitle?: string; 6 | }): JSX.Element => { 7 | const subTitleEl = props.subTitle ? ( 8 | <div className="task-list-sub-name">{props.subTitle}</div> 9 | ) : null; 10 | return ( 11 | <div className="panel-title"> 12 | <div className="task-list-name">{props.title}</div> 13 | {subTitleEl} 14 | </div> 15 | ); 16 | }; 17 | 18 | export default PanelTitle; 19 | -------------------------------------------------------------------------------- /apps/report/src/components/pixi-loader.tsx: -------------------------------------------------------------------------------- 1 | import 'pixi.js/unsafe-eval'; 2 | import * as PIXI from 'pixi.js'; 3 | 4 | const globalTextureMap = new Map<string, PIXI.Texture>(); 5 | 6 | export const loadTexture = async (img: string) => { 7 | if (globalTextureMap.has(img)) return; 8 | return PIXI.Assets.load(img).then((texture) => { 9 | globalTextureMap.set(img, texture); 10 | }); 11 | }; 12 | 13 | export const getTextureFromCache = (name: string) => { 14 | return globalTextureMap.get(name); 15 | }; 16 | 17 | export const getTexture = async (name: string) => { 18 | if (globalTextureMap.has(name)) { 19 | return globalTextureMap.get(name); 20 | } 21 | 22 | await loadTexture(name); 23 | return globalTextureMap.get(name); 24 | }; 25 | -------------------------------------------------------------------------------- /apps/report/src/components/side-item.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/report/src/components/side-item.tsx -------------------------------------------------------------------------------- /apps/report/src/components/timeline.less: -------------------------------------------------------------------------------- 1 | @import './common.less'; 2 | 3 | @base-height: 110px; 4 | 5 | .timeline-wrapper { 6 | flex-basis: @base-height; 7 | flex-grow: 0; 8 | flex-shrink: 0; 9 | 10 | width: 100%; 11 | height: 100%; 12 | border-bottom: 1px solid @border-color; 13 | position: relative; 14 | box-sizing: border-box; 15 | 16 | .timeline-canvas-wrapper { 17 | width: 100%; 18 | height: 100%; 19 | } 20 | 21 | canvas { 22 | width: 100%; 23 | height: 100%; 24 | } 25 | } -------------------------------------------------------------------------------- /apps/report/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="@rsbuild/core/types" /> 2 | -------------------------------------------------------------------------------- /apps/report/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | import { App } from './App'; 3 | 4 | const rootEl = document.getElementById('root'); 5 | 6 | if (rootEl) { 7 | const root = ReactDOM.createRoot(rootEl); 8 | 9 | root.render(<App />); 10 | } else { 11 | console.error('no root element found'); 12 | } 13 | -------------------------------------------------------------------------------- /apps/report/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { GroupedActionDump } from '@midscene/core'; 2 | import type { AnimationScript } from '@midscene/visualizer'; 3 | 4 | // Core visualization types 5 | export interface ExecutionDumpWithPlaywrightAttributes 6 | extends GroupedActionDump { 7 | attributes: Record<string, any>; 8 | } 9 | 10 | export interface VisualizerProps { 11 | logoAction?: () => void; 12 | dumps?: ExecutionDumpWithPlaywrightAttributes[]; 13 | } 14 | 15 | // Store types 16 | export interface StoreState { 17 | dump: GroupedActionDump | null; 18 | _executionDumpLoadId: number; 19 | replayAllMode: boolean; 20 | setReplayAllMode: (mode: boolean) => void; 21 | allExecutionAnimation: AnimationScript[] | null; 22 | insightWidth: number | null; 23 | insightHeight: number | null; 24 | setGroupedDump: (dump: GroupedActionDump) => void; 25 | reset: () => void; 26 | } 27 | -------------------------------------------------------------------------------- /apps/report/template/index.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html> 3 | <head> 4 | <title>Report - Midscene.js</title> 5 | <link 6 | rel="icon" 7 | type="image/png" 8 | sizes="32x32" 9 | href="https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/favicon-32x32.png" 10 | /> 11 | </head> 12 | <body> 13 | <!-- it should be replaced by the actual content --> 14 | <div id="<%= mountId %>" style="height: 100vh; width: 100vw"></div> 15 | </body> 16 | </html> 17 | -------------------------------------------------------------------------------- /apps/report/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "ES2020"], 4 | "jsx": "react-jsx", 5 | "target": "ES2020", 6 | "noEmit": true, 7 | "skipLibCheck": true, 8 | "useDefineForClassFields": true, 9 | 10 | /* modules */ 11 | "module": "ESNext", 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | "moduleResolution": "bundler", 15 | "allowImportingTsExtensions": true, 16 | 17 | /* type checking */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/site/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | doc_build 26 | 27 | # Midscene.js dump files 28 | midscene_run/report 29 | midscene_run/dump 30 | -------------------------------------------------------------------------------- /apps/site/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | npm i -g pnpm@7 6 | 7 | pnpm install 8 | 9 | npm run build 10 | 11 | mkdir output 12 | mkdir output_resource 13 | 14 | cp -rf doc_build/* ./output 15 | cp -rf doc_build/* ./output_resource 16 | 17 | 18 | if [ -f route.json ]; then 19 | cp route.json ./output 20 | fi 21 | -------------------------------------------------------------------------------- /apps/site/docs/en/common/prepare-key-for-further-use.mdx: -------------------------------------------------------------------------------- 1 | Prepare the config for the AI model you want to use. You can check the supported models in [Choose a model](../choose-a-model) -------------------------------------------------------------------------------- /apps/site/docs/en/common/setup-env.mdx: -------------------------------------------------------------------------------- 1 | ## Setup AI model service 2 | 3 | Set your model configs into the environment variables. You may refer to [choose a model](../choose-a-model) for more details. 4 | 5 | ```bash 6 | # replace with your own 7 | export OPENAI_API_KEY="sk-abcdefghijklmnopqrstuvwxyz" 8 | ``` 9 | -------------------------------------------------------------------------------- /apps/site/docs/en/common/start-experience.mdx: -------------------------------------------------------------------------------- 1 | ## Start experiencing 2 | 3 | After the configuration, you can immediately experience Midscene. There are three main tabs in the extension: 4 | 5 | - **Action**: interact with the web page. This is also known as "Auto Planning". For example: 6 | ``` 7 | type Midscene in the search box 8 | click the login button 9 | ``` 10 | 11 | - **Query**: extract JSON data from the web page 12 | 13 | ``` 14 | extract the user id from the page, return in \{ id: string \} 15 | ``` 16 | 17 | - **Assert**: validate the page 18 | 19 | ``` 20 | the page title is "Midscene" 21 | ``` 22 | 23 | - **Tap**: perform a single tap on the element where you want to click. This is also known as "Instant Action". 24 | 25 | ``` 26 | the login button 27 | ``` 28 | 29 | Enjoy ! 30 | 31 | > For the different between "Auto Planning" and "Instant Action", please refer to the [API](../API.html) document. 32 | 33 | ## Want to write some code ? 34 | 35 | After experiencing, you may want to write some code to integrate Midscene. There are multiple ways to do that. Please refer to the documents below: 36 | 37 | * [Automate with Scripts in YAML](../automate-with-scripts-in-yaml) 38 | -------------------------------------------------------------------------------- /apps/site/docs/en/data-privacy.md: -------------------------------------------------------------------------------- 1 | # Data Privacy 2 | 3 | Midscene.js is an open-source project (GitHub: [Midscene](https://github.com/web-infra-dev/midscene/)) under the MIT license. You can see all the codes in the public repository. 4 | 5 | When using Midscene.js, your page data (including the screenshot) is sent directly to the AI model provider you choose. No third-party platform will have access to this data. All you need to be concerned about is the data privacy policy of the model provider. 6 | 7 | If you prefer building Midscene.js and its Chrome Extension in your own environment instead of using the published versions, you can refer to the [Contributing Guide](https://github.com/web-infra-dev/midscene/blob/main/CONTRIBUTING.md) to find building instructions. 8 | 9 | -------------------------------------------------------------------------------- /apps/site/docs/en/llm-txt.mdx: -------------------------------------------------------------------------------- 1 | # LLMs.txt Documentation 2 | 3 | How to get tools like Cursor, Windstatic, GitHub Copilot, ChatGPT, and Claude to understand Midscene.js. 4 | 5 | We support LLMs.txt files for making the Midscene.js documentation available to large language models. 6 | 7 | ## Directory Overview 8 | 9 | The following files are available. 10 | 11 | - [llms.txt](https://midscenejs.com/llms.txt): The main LLMs.txt file 12 | - [llms-full.txt](https://midscenejs.com/llms-full.txt): The complete documentation for Midscene.js 13 | 14 | 15 | ## Usage 16 | 17 | ### Cursor 18 | 19 | Use `@Docs` feature in Cursor to include the LLMs.txt files in your project. 20 | 21 | [Read more](https://docs.cursor.com/context/@-symbols/@-docs) 22 | 23 | ### Windstatic 24 | 25 | Reference the LLMs.txt files using `@` or in your `.windsurfrules` files. 26 | 27 | [Read more](https://docs.windsurf.com/windsurf/getting-started#memories-and-rules) 28 | 29 | 30 | -------------------------------------------------------------------------------- /apps/site/docs/en/quick-experience-with-android.mdx: -------------------------------------------------------------------------------- 1 | import StartExperience from './common/start-experience.mdx'; 2 | import PrepareAndroid from './common/prepare-android.mdx'; 3 | 4 | # Quick Experience with Android 5 | 6 | By using Midscene.js playground, you can quickly experience the main features of Midscene on Android devices, without needing to write any code. 7 | 8 |  9 | 10 | <PrepareAndroid /> 11 | 12 | ## Run Playground 13 | 14 | ```bash 15 | npx --yes @midscene/android-playground 16 | ``` 17 | 18 | ## Config API Key 19 | 20 | Click the gear button to enter the configuration page and paste your API key config. 21 | 22 |  23 | 24 | Refer to [Config Model and Provider](./model-provider) document, config the API Key. 25 | 26 | <StartExperience /> 27 | 28 | * [Integrate javascript SDK with Android](./integrate-with-android) 29 | -------------------------------------------------------------------------------- /apps/site/docs/public/android-playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/android-playground.png -------------------------------------------------------------------------------- /apps/site/docs/public/android-set-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/android-set-env.png -------------------------------------------------------------------------------- /apps/site/docs/public/android-usb-debug-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/android-usb-debug-en.png -------------------------------------------------------------------------------- /apps/site/docs/public/android-usb-debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/android-usb-debug.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.10.0-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.10.0-2.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.10.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.10.0.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.11.0-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.11.0-2.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.11.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.11.0.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.13.0.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.13.0.jpeg -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.5.0-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.5.0-2.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.5.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.5.0.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.6.0-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.6.0-2.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.6.0-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.6.0-3.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.6.0-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.6.0-4.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.6.0-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.6.0-5.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.6.0-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.6.0-6.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.6.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.6.0.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/0.9.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/0.9.0.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/ai-ide-convert-prompt-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/ai-ide-convert-prompt-result.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/ai-ide-convert-prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/ai-ide-convert-prompt.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/android-playground-lark-poster-cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/android-playground-lark-poster-cn.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/android-playground-lark-poster-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/android-playground-lark-poster-en.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/coze-sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/coze-sidebar.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/export-video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/export-video.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/logScreenshot-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/logScreenshot-api.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/report-coze-deep-think.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/report-coze-deep-think.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/report-instant-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/report-instant-action.png -------------------------------------------------------------------------------- /apps/site/docs/public/blog/report-planning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/blog/report-planning.png -------------------------------------------------------------------------------- /apps/site/docs/public/bridge_in_extension.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/bridge_in_extension.jpg -------------------------------------------------------------------------------- /apps/site/docs/public/cache/no-cache-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/cache/no-cache-time.png -------------------------------------------------------------------------------- /apps/site/docs/public/cache/use-cache-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/cache/use-cache-time.png -------------------------------------------------------------------------------- /apps/site/docs/public/midescene-playground-entry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/midescene-playground-entry.jpg -------------------------------------------------------------------------------- /apps/site/docs/public/midscene-bridge-mode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/midscene-bridge-mode.jpg -------------------------------------------------------------------------------- /apps/site/docs/public/midscene-extension.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/midscene-extension.jpg -------------------------------------------------------------------------------- /apps/site/docs/public/midscene-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/midscene-icon.png -------------------------------------------------------------------------------- /apps/site/docs/public/midscene_with_text_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/midscene_with_text_light.png -------------------------------------------------------------------------------- /apps/site/docs/public/playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/playground.png -------------------------------------------------------------------------------- /apps/site/docs/public/report.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/apps/site/docs/public/report.gif -------------------------------------------------------------------------------- /apps/site/docs/zh/common/prepare-key-for-further-use.mdx: -------------------------------------------------------------------------------- 1 | 准备你想要使用的 AI 模型及其 API Key。你可以在 [选择模型](../choose-a-model) 文档中查看 Midscene.js 支持的模型。 -------------------------------------------------------------------------------- /apps/site/docs/zh/common/setup-env.mdx: -------------------------------------------------------------------------------- 1 | ## 配置 AI 模型服务 2 | 3 | 将你的模型配置写入环境变量。更多信息请查看 [选择 AI 模型](../choose-a-model)。 4 | 5 | ```bash 6 | # 替换为你的 API Key 7 | export OPENAI_API_KEY="sk-abcdefghijklmnopqrstuvwxyz" 8 | ``` 9 | -------------------------------------------------------------------------------- /apps/site/docs/zh/common/start-experience.mdx: -------------------------------------------------------------------------------- 1 | ## 开始体验 2 | 3 | 配置完成后,你可以立即开始使用 Midscene。它一共有三个关键操作 Tab: 4 | 5 | - **Action**: 与网页进行交互,这就是所谓的自动规划(Auto Planning)。比如 6 | ``` 7 | 在搜索框中输入 Midscene 8 | 点击登录按钮 9 | ``` 10 | 11 | - **Query**: 从界面中提取 JSON 数据 12 | ``` 13 | 提取页面中的用户 ID,返回 \{ id: string \} 14 | ``` 15 | 16 | - **Assert**: 验证页面 17 | 18 | ``` 19 | 页面标题是 Midscene 20 | ``` 21 | 22 | - **Tap**: 在某个元素上点击,这就是所谓的即时操作(Instant Action)。 23 | ``` 24 | 登录按钮 25 | ``` 26 | 27 | 快来试试吧! 28 | 29 | > 关于自动规划(Auto Planning)和即时操作(Instant Action)的区别,请参考 [API](../API.html) 文档。 30 | 31 | ## 想将 Midscene 集成到代码? 32 | 33 | 插件体验结束后,你可能想将 Midscene 集成到代码中。这里有几种不同集成形式的文档: 34 | 35 | * [使用 YAML 格式的自动化脚本](../automate-with-scripts-in-yaml) 36 | -------------------------------------------------------------------------------- /apps/site/docs/zh/data-privacy.md: -------------------------------------------------------------------------------- 1 | # 数据隐私 2 | 3 | Midscene.js 是一个开源项目(GitHub: [Midscene](https://github.com/web-infra-dev/midscene/)),遵循 MIT 许可证。你可以在公开仓库中查看到所有代码。 4 | 5 | 当使用 Midscene.js 时,你的页面数据(包括截图)将直接发送到你配置的 AI 模型提供商。没有第三方平台会访问这些数据。你需要关注的是模型提供商的数据隐私政策。 6 | 7 | 如果你希望在你自己的环境中构建 Midscene.js 和它的 Chrome 扩展(而不是使用我们已发布的版本),你可以参考 [贡献指南](https://github.com/web-infra-dev/midscene/blob/main/CONTRIBUTING.md) 以找到构建说明。 8 | 9 | -------------------------------------------------------------------------------- /apps/site/docs/zh/llm-txt.mdx: -------------------------------------------------------------------------------- 1 | # LLMs.txt 文档 2 | 3 | 如何让 Cursor、Windstatic、GitHub Copilot、ChatGPT 和 Claude 等工具理解 Midscene.js。 4 | 5 | 我们支持 LLMs.txt 文件,使 Midscene.js 的文档可供大型语言模型使用。 6 | 7 | ## 目录概览 8 | 9 | 以下文件可供使用: 10 | 11 | - [llms.txt](https://midscenejs.com/llms.txt):主要的 LLMs.txt 文件 12 | - [llms-full.txt](https://midscenejs.com/llms-full.txt):Midscene.js 的完整文档 13 | 14 | 15 | ## 使用方法 16 | 17 | ### Cursor 18 | 19 | 在 Cursor 中使用 `@Docs` 功能来将 LLMs.txt 文件包含到你的项目中。 20 | 21 | [阅读更多](https://docs.cursor.com/context/@-symbols/@-docs) 22 | 23 | ### Windstatic 24 | 25 | 使用 `@` 或在你的 `.windsurfrules` 文件中引用 LLMs.txt 文件。 26 | 27 | [阅读更多](https://docs.windsurf.com/windsurf/getting-started#memories-and-rules) 28 | 29 | 30 | -------------------------------------------------------------------------------- /apps/site/docs/zh/quick-experience-with-android.mdx: -------------------------------------------------------------------------------- 1 | import StartExperience from './common/start-experience.mdx'; 2 | import PrepareAndroid from './common/prepare-android.mdx'; 3 | 4 | # 使用 Android Playground 快速体验 5 | 6 | 通过使用 Midscene.js Android 设备,你可以快速在 Android 设备上体验 Midscene 的主要功能,而无需编写任何代码。 7 | 8 | 该 Playground 和 `@midscene/android` 包共享一份代码,因此你可以将其视为 Midscene Android SDK 的一个 Playground 或调试工具。 9 | 10 |  11 | 12 | <PrepareAndroid /> 13 | 14 | ## 启动 Playground 15 | 16 | ```bash 17 | npx --yes @midscene/android-playground 18 | ``` 19 | 20 | ## 配置 API Key 21 | 22 | 点击齿轮按钮,进入配置页面: 23 | 24 |  25 | 26 | 参考 [配置模型和服务商](./model-provider) 文档,配置 API Key。 27 | 28 | <StartExperience /> 29 | 30 | * [与 Android(adb) 集成](./integrate-with-android) 31 | -------------------------------------------------------------------------------- /apps/site/i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "gettingStarted": { 3 | "en": "Getting Started", 4 | "zh": "开始" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doc", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "rspress dev", 8 | "build": "rspress build", 9 | "preview": "rspress preview" 10 | }, 11 | "engines": { 12 | "pnpm": ">=9.3.0", 13 | "node": ">=18.19.0" 14 | }, 15 | "packageManager": "pnpm@9.3.0", 16 | "dependencies": { 17 | "querystring": "0.2.1", 18 | "react": "18.3.1", 19 | "react-dom": "18.3.1" 20 | }, 21 | "devDependencies": { 22 | "@rspress/plugin-llms": "2.0.0-beta.4", 23 | "rspress-plugin-sitemap": "1.1.2", 24 | "@types/node": "^18.0.0", 25 | "rspress": "2.0.0-beta.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/site/route.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "statusPages": { 4 | "404": "404.html" 5 | } 6 | }, 7 | "routes": [ 8 | { 9 | "isApi": false, 10 | "entryPath": "./", 11 | "isSpa": false, 12 | "isGarr": false, 13 | "isSSR": false, 14 | "redirect": "", 15 | "regexDomain": false 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/site/styles/index.css: -------------------------------------------------------------------------------- 1 | .rspress-logo { 2 | height: 2.6rem; 3 | } 4 | 5 | :root { 6 | --rp-c-text-1: #0b140f; 7 | } 8 | 9 | /* Footer styles */ 10 | 11 | .footer { 12 | background-color: transparent; 13 | text-align: center; 14 | } 15 | 16 | .footer::before, 17 | .footer::after { 18 | content: none; 19 | border: none; 20 | } 21 | 22 | .footer-content { 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | flex-wrap: wrap; 27 | gap: 15px; 28 | border: none; 29 | } 30 | 31 | .footer-logo { 32 | width: 40px; 33 | border: none; 34 | } 35 | 36 | .footer-text { 37 | margin: 0; 38 | font-size: 14px; 39 | border: none; 40 | white-space: nowrap; 41 | } 42 | 43 | @media (max-width: 768px) { 44 | .md\:text-6xl { 45 | font-size: 3.3rem; 46 | line-height: 1; 47 | } 48 | .footer-content { 49 | flex-direction: column; 50 | align-items: center; 51 | text-align: center; 52 | } 53 | .footer-logo { 54 | margin-bottom: 10px; 55 | } 56 | .footer-bottom { 57 | text-align: center; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /apps/site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "esModuleInterop": true 5 | }, 6 | "mdx": { 7 | "checkMdx": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cspell.config.cjs: -------------------------------------------------------------------------------- 1 | const { banWords } = require('cspell-ban-words'); 2 | 3 | module.exports = { 4 | version: '0.2', 5 | language: 'en', 6 | files: ['**/*.{ts,tsx,js,jsx,md,mdx}'], 7 | enableFiletypes: ['mdx'], 8 | ignoreRegExpList: [ 9 | // ignore markdown anchors such as [modifyRsbuildConfig](#modifyrsbuildconfig) 10 | '#.*?\\)', 11 | ], 12 | ignorePaths: [ 13 | 'dist', 14 | 'dist-*', 15 | 'compiled', 16 | 'coverage', 17 | 'doc_build', 18 | 'node_modules', 19 | 'pnpm-lock.yaml', 20 | 'midscene_run', 21 | ], 22 | flagWords: banWords, 23 | dictionaries: ['dictionary'], 24 | dictionaryDefinitions: [ 25 | { 26 | name: 'dictionary', 27 | path: './scripts/dictionary.txt', 28 | addWords: true, 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "namedInputs": { 4 | "build": [ 5 | "default", 6 | "{projectRoot}/tsconfig.json", 7 | "{projectRoot}/package.json", 8 | "{projectRoot}/modern.config.*", 9 | "{projectRoot}/scripts/**/*", 10 | "!{projectRoot}/**/*.{md,mdx}", 11 | "!{projectRoot}/vitest.config.ts", 12 | "!{projectRoot}/**/?(*.)+(spec|test).ts" 13 | ] 14 | }, 15 | "targetDefaults": { 16 | "dev": { 17 | "dependsOn": ["^build"] 18 | }, 19 | "build": { 20 | "dependsOn": ["^build"], 21 | "cache": true, 22 | "inputs": ["build", "^build", "{workspaceRoot}/package.json"], 23 | "outputs": ["{projectRoot}/dist"] 24 | }, 25 | "build:watch": { 26 | "dependsOn": ["^build"] 27 | }, 28 | "test": { 29 | "cache": false 30 | }, 31 | "e2e": { 32 | "dependsOn": ["^build"] 33 | }, 34 | "e2e:ui": { 35 | "dependsOn": ["^build"] 36 | } 37 | }, 38 | "defaultBase": "main" 39 | } 40 | -------------------------------------------------------------------------------- /packages/android-playground/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Midscene.js dump files 3 | midscene_run/report 4 | midscene_run/tmp 5 | static/ -------------------------------------------------------------------------------- /packages/android-playground/README.md: -------------------------------------------------------------------------------- 1 | ## Android playground 2 | 3 | Android is driven by adb, so you need install adb first: 4 | - [CLI](https://developer.android.com/tools/adb) 5 | 6 | ```bash 7 | npx @midscene/android-playground 8 | ``` -------------------------------------------------------------------------------- /packages/android-playground/bin/android-playground: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../dist/lib/index.js'); 4 | -------------------------------------------------------------------------------- /packages/android-playground/bin/server.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/android-playground/bin/server.bin -------------------------------------------------------------------------------- /packages/android-playground/modern.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, moduleTools } from '@modern-js/module-tools'; 2 | 3 | export default defineConfig({ 4 | plugins: [moduleTools()], 5 | buildPreset: 'npm-library', 6 | buildConfig: { 7 | input: { 8 | index: './src/index.ts', 9 | }, 10 | target: 'es2020', 11 | dts: { 12 | respectExternal: true, 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /packages/android-playground/src/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { AndroidAgent, AndroidDevice } from '@midscene/android'; 3 | import { 4 | PLAYGROUND_SERVER_PORT, 5 | SCRCPY_SERVER_PORT, 6 | } from '@midscene/shared/constants'; 7 | import PlaygroundServer from '@midscene/web/midscene-server'; 8 | import ScrcpyServer from './scrcpy-server'; 9 | 10 | const staticDir = path.join(__dirname, '../../static'); 11 | const playgroundServer = new PlaygroundServer( 12 | AndroidDevice, 13 | AndroidAgent, 14 | staticDir, 15 | ); 16 | const scrcpyServer = new ScrcpyServer(); 17 | 18 | const main = async () => { 19 | const { default: open } = await import('open'); 20 | try { 21 | await Promise.all([ 22 | playgroundServer.launch(PLAYGROUND_SERVER_PORT), 23 | scrcpyServer.launch(SCRCPY_SERVER_PORT), 24 | ]); 25 | console.log( 26 | `Midscene playground server is running on http://localhost:${playgroundServer.port}`, 27 | ); 28 | open(`http://localhost:${playgroundServer.port}`); 29 | } catch (error) { 30 | console.error('Failed to start servers:', error); 31 | process.exit(1); 32 | } 33 | }; 34 | 35 | main(); 36 | -------------------------------------------------------------------------------- /packages/android-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "isolatedModules": true, 10 | "jsx": "preserve", 11 | "lib": ["DOM", "ESNext"], 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "rootDir": "src", 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "module": "ES2020", 18 | "target": "es2020", 19 | "types": ["node"] 20 | }, 21 | "exclude": ["**/node_modules"], 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/android/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Midscene.js dump files 3 | midscene_run/report 4 | midscene_run/tmp 5 | -------------------------------------------------------------------------------- /packages/android/README.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | Automate UI actions, extract data, and perform assertions using AI. It offers JavaScript SDK, Chrome extension, and support for scripting in YAML. 4 | 5 | See https://midscenejs.com/ for details. 6 | 7 | ## License 8 | 9 | Midscene is MIT licensed. -------------------------------------------------------------------------------- /packages/android/bin/yadb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/android/bin/yadb -------------------------------------------------------------------------------- /packages/android/modern.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, moduleTools } from '@modern-js/module-tools'; 2 | 3 | export default defineConfig({ 4 | plugins: [moduleTools()], 5 | buildPreset: 'npm-library', 6 | buildConfig: { 7 | input: { 8 | index: './src/index.ts', 9 | }, 10 | target: 'es2020', 11 | dts: { 12 | respectExternal: true, 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /packages/android/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@midscene/android", 3 | "version": "0.21.2", 4 | "description": "Android automation library for Midscene", 5 | "keywords": [ 6 | "Android UI automation", 7 | "Android AI testing", 8 | "Android automation library", 9 | "Android automation tool", 10 | "Android use" 11 | ], 12 | "main": "./dist/lib/index.js", 13 | "types": "./dist/types/index.d.ts", 14 | "files": ["bin", "dist", "README.md"], 15 | "exports": { 16 | ".": { 17 | "types": "./dist/types/index.d.ts", 18 | "default": "./dist/lib/index.js" 19 | } 20 | }, 21 | "scripts": { 22 | "dev": "modern dev", 23 | "build": "modern build -c ./modern.config.ts", 24 | "build:watch": "modern build -w -c ./modern.config.ts", 25 | "test": "vitest --run", 26 | "test:u": "vitest --run -u", 27 | "test:ai": "MIDSCENE_CACHE=true npm run test" 28 | }, 29 | "dependencies": { 30 | "@midscene/core": "workspace:*", 31 | "@midscene/shared": "workspace:*", 32 | "@midscene/web": "workspace:*", 33 | "appium-adb": "12.12.1" 34 | }, 35 | "devDependencies": { 36 | "@modern-js/module-tools": "2.60.6", 37 | "@types/node": "^18.0.0", 38 | "dotenv": "16.4.5", 39 | "typescript": "^5.8.3", 40 | "vitest": "3.0.5" 41 | }, 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /packages/android/src/index.ts: -------------------------------------------------------------------------------- 1 | export { AndroidDevice } from './page'; 2 | export { AndroidAgent, agentFromAdbDevice } from './agent'; 3 | export { getConnectedDevices } from './utils'; 4 | export { overrideAIConfig } from '@midscene/shared/env'; 5 | -------------------------------------------------------------------------------- /packages/android/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { ADB, type Device } from 'appium-adb'; 2 | import { debugPage } from '../page'; 3 | 4 | export async function getConnectedDevices(): Promise<Device[]> { 5 | try { 6 | const adb = await ADB.createADB({ 7 | adbExecTimeout: 60000, 8 | }); 9 | const devices = await adb.getConnectedDevices(); 10 | 11 | debugPage(`Found ${devices.length} connected devices: `, devices); 12 | 13 | return devices; 14 | } catch (error: any) { 15 | console.error('Failed to get device list:', error); 16 | throw new Error( 17 | `Unable to get connected Android device list, please check https://midscenejs.com/integrate-with-android.html#faq : ${error.message}`, 18 | { 19 | cause: error, 20 | }, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/android/tests/ai/setting.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, vi } from 'vitest'; 2 | import { agentFromAdbDevice, getConnectedDevices } from '../../src'; 3 | 4 | vi.setConfig({ 5 | testTimeout: 90 * 1000, 6 | }); 7 | 8 | describe( 9 | 'android integration', 10 | async () => { 11 | await it('Android settings page demo for scroll', async () => { 12 | const devices = await getConnectedDevices(); 13 | const agent = await agentFromAdbDevice(devices[0].udid, { 14 | aiActionContext: 15 | 'If any location, permission, user agreement, etc. popup, click agree. If login page pops up, close it.', 16 | }); 17 | 18 | await agent.launch('com.android.settings/.Settings'); 19 | 20 | await agent.aiAction('scroll list to bottom'); 21 | await agent.aiAction('open "More settings"'); 22 | await agent.aiAction('scroll list to bottom'); 23 | await agent.aiAction('scroll list to top'); 24 | await agent.aiAction('swipe down one screen'); 25 | await agent.aiAction('swipe up one screen'); 26 | }); 27 | }, 28 | 360 * 1000, 29 | ); 30 | -------------------------------------------------------------------------------- /packages/android/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "isolatedModules": true, 10 | "jsx": "preserve", 11 | "lib": ["DOM", "ESNext"], 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "rootDir": "src", 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "module": "ES2020", 18 | "target": "es2020", 19 | "types": ["node"] 20 | }, 21 | "exclude": ["**/node_modules"], 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/android/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import dotenv from 'dotenv'; 3 | import { defineConfig } from 'vitest/config'; 4 | import { version } from './package.json'; 5 | 6 | /** 7 | * Read environment variables from file. 8 | * https://github.com/motdotla/dotenv 9 | */ 10 | dotenv.config({ 11 | path: path.join(__dirname, '../../.env'), 12 | }); 13 | 14 | const testFiles = ['tests/ai/**/*.test.ts']; 15 | 16 | export default defineConfig({ 17 | resolve: { 18 | alias: { 19 | '@': path.resolve(__dirname, 'src'), 20 | }, 21 | }, 22 | test: { 23 | include: testFiles, 24 | testTimeout: 3 * 60 * 1000, // Global timeout set to 10 seconds 25 | dangerouslyIgnoreUnhandledErrors: !!process.env.CI, // showcase.test.ts is not stable 26 | }, 27 | define: { 28 | __VERSION__: `'${version}'`, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /packages/cli/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # midscene.js 3 | midscene_run/ 4 | 5 | status*.json -------------------------------------------------------------------------------- /packages/cli/.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .pnp 4 | .pnp.js 5 | .evn.local 6 | .env.*.local 7 | .history 8 | .rts* 9 | *.log* 10 | *.pid 11 | *.pid.* 12 | *.report 13 | *.lcov 14 | lib-cov 15 | 16 | doc_build/ 17 | node_modules/ 18 | .npm 19 | .lock-wscript 20 | .yarn-integrity 21 | .node_repl_history 22 | .nyc_output 23 | *.tsbuildinfo 24 | .eslintcache 25 | .sonarlint 26 | 27 | coverage/ 28 | release/ 29 | output/ 30 | output_resource/ 31 | 32 | .vscode/**/* 33 | !.vscode/settings.json 34 | !.vscode/extensions.json 35 | .idea/ 36 | 37 | **/*/api/typings/auto-generated 38 | **/*/adapters/**/index.ts 39 | **/*/adapters/**/index.js 40 | 41 | src/ 42 | midscene_run/ 43 | log/ 44 | docs/ 45 | tests/ -------------------------------------------------------------------------------- /packages/cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Bytedance, Inc. and its affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | An AI-powered automation SDK can control the page, perform assertions, and extract data in JSON format using natural language. 4 | 5 | See https://midscenejs.com/ for details. 6 | 7 | ## License 8 | 9 | Midscene is MIT licensed. -------------------------------------------------------------------------------- /packages/cli/bin/midscene: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../dist/lib/index.js') -------------------------------------------------------------------------------- /packages/cli/modern.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, moduleTools } from '@modern-js/module-tools'; 2 | import { version } from './package.json'; 3 | 4 | export default defineConfig({ 5 | plugins: [moduleTools()], 6 | buildPreset: 'npm-library', 7 | buildConfig: { 8 | input: { 9 | index: 'src/index.ts', 10 | }, 11 | externals: ['node:buffer', 'puppeteer'], 12 | target: 'es2020', 13 | define: { 14 | __VERSION__: version, 15 | }, 16 | sourceMap: true, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /packages/cli/src/args.ts: -------------------------------------------------------------------------------- 1 | import type minimist from 'minimist'; 2 | 3 | export type ArgumentValueType = string | boolean | number; 4 | export function findOnlyItemInArgs( 5 | args: minimist.ParsedArgs, 6 | name: string, 7 | ): string | boolean | number | undefined { 8 | const found = args[name]; 9 | if (found === undefined) { 10 | return false; 11 | } 12 | 13 | if (Array.isArray(found) && found.length > 1) { 14 | throw new Error(`Multiple values found for ${name}`); 15 | } 16 | 17 | return found; 18 | } 19 | 20 | export interface OrderedArgumentItem { 21 | name: string; 22 | value: ArgumentValueType; 23 | } 24 | 25 | export function orderMattersParse(args: string[]): OrderedArgumentItem[] { 26 | const orderedArgs: OrderedArgumentItem[] = []; 27 | args.forEach((arg, index) => { 28 | if (arg.startsWith('--')) { 29 | const key = arg.substring(2); 30 | let value: ArgumentValueType = 31 | args[index + 1] && !args[index + 1].startsWith('--') 32 | ? args[index + 1] 33 | : true; 34 | 35 | if (typeof value === 'string' && /^\d+$/.test(value)) { 36 | value = Number.parseInt(value, 10); 37 | } 38 | orderedArgs.push({ name: key, value }); 39 | } 40 | }); 41 | 42 | return orderedArgs; 43 | } 44 | -------------------------------------------------------------------------------- /packages/cli/src/http-server.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'http-server' { 2 | export function createServer(options: http.ServerOptions): { 3 | server: http.Server; 4 | listen: (port: number, host: string, callback: () => void) => void; 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /packages/cli/tests/ai/bridge.test.ts: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { describe, test } from 'vitest'; 3 | const cliBin = require.resolve('../../bin/midscene'); 4 | 5 | const describeIf = process.env.BRIDGE_MODE ? describe : describe.skip; 6 | 7 | describeIf( 8 | 'bridge', 9 | { 10 | timeout: 1000 * 60 * 3, 11 | }, 12 | () => { 13 | test('open new tab', async () => { 14 | const params = [ 15 | './tests/midscene_scripts_bridge/new_tab/open-new-tab.yaml', 16 | ]; 17 | await execa(cliBin, params); 18 | }); 19 | }, 20 | ); 21 | -------------------------------------------------------------------------------- /packages/cli/tests/midscene_scripts/local/local-error-message.yml: -------------------------------------------------------------------------------- 1 | target: 2 | serve: ./tests/server_root 3 | url: index.html 4 | tasks: 5 | - name: check title 6 | flow: 7 | - aiAssert: the content title is "Your App" 8 | errorMessage: "something error when assert title" 9 | -------------------------------------------------------------------------------- /packages/cli/tests/midscene_scripts/local/local.yml: -------------------------------------------------------------------------------- 1 | target: 2 | serve: ./tests/server_root 3 | url: index.html 4 | tasks: 5 | - name: check title 6 | flow: 7 | - aiAssert: the content title is "My App" 8 | -------------------------------------------------------------------------------- /packages/cli/tests/midscene_scripts/online/online.yaml: -------------------------------------------------------------------------------- 1 | # login to sauce demo, extract the items info into a json file, and assert the price of 'Sauce Labs Fleece Jacket' 2 | 3 | web: 4 | url: https://www.saucedemo.com/ 5 | output: ./midscene_output/sauce-demo-items.json 6 | 7 | tasks: 8 | - name: login 9 | flow: 10 | - aiAction: type 'standard_user' in user name input, type 'secret_sauce' in password, click 'Login' 11 | 12 | - name: extract items info 13 | flow: 14 | - aiQuery: > 15 | {name: string, price: number, actionBtnName: string, imageUrl: string}[], return item name, price and the action button name on the lower right corner of each item, and the image url of each item (like 'Remove') 16 | name: items 17 | domIncluded: true 18 | - aiAssert: The price of 'Sauce Labs Fleece Jacket' is 49.99 19 | 20 | - name: run javascript code 21 | flow: 22 | - javascript: > 23 | document.title 24 | name: page-title 25 | -------------------------------------------------------------------------------- /packages/cli/tests/midscene_scripts_bridge/current_tab/check_content.yaml: -------------------------------------------------------------------------------- 1 | target: 2 | bridgeMode: currentTab 3 | tasks: 4 | - name: check page content 5 | flow: 6 | - aiAssert: this is a web page 7 | -------------------------------------------------------------------------------- /packages/cli/tests/midscene_scripts_bridge/new_tab/bing.yaml: -------------------------------------------------------------------------------- 1 | target: 2 | url: https://www.bing.com 3 | bridgeMode: newTabWithUrl 4 | tasks: 5 | - name: search weather 6 | flow: 7 | - sleep: 5000 8 | - ai: input 'weather today' in input box, click search button 9 | - sleep: 5000 10 | 11 | - name: check result 12 | flow: 13 | - aiAssert: the result shows the weather info 14 | 15 | - name: evaluateJavaScript 16 | flow: 17 | - javascript: alert('finished! this is javascript') 18 | -------------------------------------------------------------------------------- /packages/cli/tests/midscene_scripts_bridge/new_tab/local.yml: -------------------------------------------------------------------------------- 1 | target: 2 | serve: ./tests/server_root 3 | url: index.html 4 | bridgeMode: newTabWithUrl 5 | tasks: 6 | - name: check title 7 | flow: 8 | - aiAssert: the content title is "My App" 9 | -------------------------------------------------------------------------------- /packages/cli/tests/midscene_scripts_bridge/new_tab/open-new-tab.yaml: -------------------------------------------------------------------------------- 1 | target: 2 | url: https://www.bing.com 3 | forceSameTabNavigation: true 4 | bridgeMode: newTabWithUrl 5 | closeNewTabsAfterDisconnect: true 6 | 7 | tasks: 8 | - name: search weather 9 | flow: 10 | - sleep: 5000 11 | - ai: input 'midscene github' in input box, click search button 12 | - ai: click the first result 13 | - sleep: 5000 14 | 15 | - name: check result 16 | flow: 17 | - aiAssert: the page is "midscene github" 18 | -------------------------------------------------------------------------------- /packages/cli/tests/server_root/index.html: -------------------------------------------------------------------------------- 1 | <h1>My App</h1> 2 | <p>This is a test page</p> 3 | <div>Width:</div> 4 | <div id="j_width"></div> 5 | <div>Height:</div> 6 | <div id="j_height"></div> 7 | <script> 8 | document.getElementById('j_width').innerText = window.innerWidth; 9 | document.getElementById('j_height').innerText = window.innerHeight; 10 | </script> 11 | </body> -------------------------------------------------------------------------------- /packages/cli/tests/unit-test/__snapshots__/cli-utils.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`cli utils > match exact file 1`] = `[]`; 4 | 5 | exports[`cli utils > match files 1`] = ` 6 | [ 7 | "tests/midscene_scripts/local/local-error-message.yml", 8 | "tests/midscene_scripts/local/local.yml", 9 | ] 10 | `; 11 | 12 | exports[`cli utils > match folder 1`] = ` 13 | [ 14 | "tests/midscene_scripts/local/local-error-message.yml", 15 | "tests/midscene_scripts/local/local.yml", 16 | "tests/midscene_scripts/online/online.yaml", 17 | ] 18 | `; 19 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "isolatedModules": true, 9 | "jsx": "preserve", 10 | "lib": ["ESNext", "DOM"], 11 | "moduleResolution": "node", 12 | "paths": { 13 | "@/*": ["./src/*"] 14 | }, 15 | "resolveJsonModule": true, 16 | "rootDir": ".", 17 | "skipLibCheck": true, 18 | "strict": true 19 | }, 20 | "exclude": ["**/node_modules"], 21 | "include": ["src", "tests"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/cli/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import dotenv from 'dotenv'; 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | dotenv.config({ 6 | path: path.join(__dirname, '../../.env'), 7 | }); 8 | 9 | const enableAiTest = Boolean(process.env.AITEST); 10 | const basicTest = ['tests/unit-test/**/*.test.ts']; 11 | 12 | export default defineConfig({ 13 | test: { 14 | include: enableAiTest ? ['tests/ai/**/*.test.ts'] : basicTest, 15 | testTimeout: 3 * 60 * 1000, // Global timeout set to 3 minutes 16 | }, 17 | resolve: { 18 | alias: { 19 | '@': path.resolve(__dirname, 'src'), 20 | }, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # midscene.js 3 | midscene_run/ 4 | report/ -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .pnp 4 | .pnp.js 5 | .evn.local 6 | .env.*.local 7 | .history 8 | .rts* 9 | *.log* 10 | *.pid 11 | *.pid.* 12 | *.report 13 | *.lcov 14 | lib-cov 15 | 16 | doc_build/ 17 | node_modules/ 18 | .npm 19 | .lock-wscript 20 | .yarn-integrity 21 | .node_repl_history 22 | .nyc_output 23 | *.tsbuildinfo 24 | .eslintcache 25 | .sonarlint 26 | 27 | coverage/ 28 | release/ 29 | output/ 30 | output_resource/ 31 | 32 | .vscode/**/* 33 | !.vscode/settings.json 34 | !.vscode/extensions.json 35 | .idea/ 36 | 37 | **/*/api/typings/auto-generated 38 | **/*/adapters/**/index.ts 39 | **/*/adapters/**/index.js 40 | 41 | src/ 42 | midscene_run/ 43 | log/ 44 | docs/ 45 | tests/ -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Bytedance, Inc. and its affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | Automate browser actions, extract data, and perform assertions using AI. It offers JavaScript SDK, Chrome extension, and support for scripting in YAML. 4 | 5 | See https://midscenejs.com/ for details. 6 | 7 | ## License 8 | 9 | Midscene is MIT licensed. -------------------------------------------------------------------------------- /packages/core/modern.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, moduleTools } from '@modern-js/module-tools'; 2 | import { version } from './package.json'; 3 | 4 | export default defineConfig({ 5 | plugins: [moduleTools()], 6 | buildPreset: 'npm-library', 7 | buildConfig: { 8 | input: { 9 | index: 'src/index.ts', 10 | utils: 'src/utils.ts', 11 | tree: 'src/tree.ts', 12 | 'ai-model': 'src/ai-model/index.ts', 13 | }, 14 | externals: ['langsmith'], 15 | target: 'es2020', 16 | define: { 17 | __VERSION__: version, 18 | }, 19 | splitting: true, 20 | sourceMap: true, 21 | dts: { 22 | respectExternal: true, 23 | }, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /packages/core/src/ai-model/index.ts: -------------------------------------------------------------------------------- 1 | export { callToGetJSONObject, call as callAi } from './service-caller/index'; 2 | export { systemPromptToLocateElement } from './prompt/llm-locator'; 3 | export { 4 | describeUserPage, 5 | elementByPositionWithElementInfo, 6 | } from './prompt/util'; 7 | export { generatePlaywrightTest } from './prompt/playwright-generator'; 8 | export { generateYamlTest } from './prompt/yaml-generator'; 9 | 10 | export type { ChatCompletionMessageParam } from 'openai/resources'; 11 | 12 | export { 13 | AiLocateElement, 14 | AiExtractElementInfo, 15 | AiAssert, 16 | AiLocateSection, 17 | } from './inspect'; 18 | 19 | export { plan } from './llm-planning'; 20 | export { callAiFn, adaptBboxToRect } from './common'; 21 | export { vlmPlanning, resizeImageForUiTars } from './ui-tars-planning'; 22 | 23 | export { AIActionType } from './common'; 24 | -------------------------------------------------------------------------------- /packages/core/src/ai-model/prompt/common.ts: -------------------------------------------------------------------------------- 1 | import type { vlLocateMode } from '@midscene/shared/env'; 2 | export function bboxDescription(vlMode: ReturnType<typeof vlLocateMode>) { 3 | if (vlMode === 'gemini') { 4 | return '2d bounding box as [ymin, xmin, ymax, xmax]'; 5 | } 6 | return '2d bounding box as [xmin, ymin, xmax, ymax]'; 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/ai-model/prompt/describe.ts: -------------------------------------------------------------------------------- 1 | import { getPreferredLanguage } from '@midscene/shared/env'; 2 | 3 | export const elementDescriberInstruction = () => { 4 | return `Describe the element in the red rectangle for precise identification. Use ${getPreferredLanguage()}. 5 | 6 | Rules: 7 | 1. Start with element type (button, input, link, etc.) 8 | 2. Include key identifiers: 9 | - Text content: "with text 'Submit'" 10 | - Visual features: "blue background", "icon only" 11 | - Position: "top-right", "below search bar" 12 | 3. Keep description under 20 words 13 | 4. Don't mention the red rectangle 14 | 15 | Return JSON: 16 | { 17 | "description": "brief element type with key identifiers", 18 | "error"?: "error message if any" 19 | }`; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/core/src/ai-model/service-caller/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'dirty-json'; 2 | -------------------------------------------------------------------------------- /packages/core/src/image/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | imageInfo, 3 | imageInfoOfBase64, 4 | base64Encoded, 5 | resizeImg, 6 | transformImgPathToBase64, 7 | saveBase64Image, 8 | zoomForGPT4o, 9 | } from '@midscene/shared/img'; 10 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Executor } from './ai-model/action-executor'; 2 | import Insight from './insight/index'; 3 | import { getVersion } from './utils'; 4 | 5 | export { 6 | plan, 7 | describeUserPage, 8 | AiLocateElement, 9 | AiAssert, 10 | } from './ai-model/index'; 11 | 12 | export { getAIConfig, MIDSCENE_MODEL_NAME } from '@midscene/shared/env'; 13 | 14 | export type * from './types'; 15 | export default Insight; 16 | export { Executor, Insight, getVersion }; 17 | 18 | export type { 19 | MidsceneYamlScript, 20 | MidsceneYamlTask, 21 | MidsceneYamlFlowItem, 22 | MidsceneYamlFlowItemAIRightClick, 23 | } from './yaml'; 24 | -------------------------------------------------------------------------------- /packages/core/src/insight/utils.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | DumpMeta, 3 | DumpSubscriber, 4 | InsightDump, 5 | PartialInsightDumpFromSDK, 6 | } from '@/types'; 7 | import { getVersion } from '@/utils'; 8 | import { MIDSCENE_MODEL_NAME, getAIConfig } from '@midscene/shared/env'; 9 | import { uuid } from '@midscene/shared/utils'; 10 | 11 | export function emitInsightDump( 12 | data: PartialInsightDumpFromSDK, 13 | dumpSubscriber?: DumpSubscriber, 14 | ) { 15 | const baseData: DumpMeta = { 16 | sdkVersion: getVersion(), 17 | logTime: Date.now(), 18 | model_name: getAIConfig(MIDSCENE_MODEL_NAME) || '', 19 | }; 20 | const finalData: InsightDump = { 21 | logId: uuid(), 22 | ...baseData, 23 | ...data, 24 | }; 25 | 26 | dumpSubscriber?.(finalData); 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/tree.ts: -------------------------------------------------------------------------------- 1 | export { 2 | truncateText, 3 | trimAttributes, 4 | descriptionOfTree, 5 | } from '@midscene/shared/extractor'; 6 | -------------------------------------------------------------------------------- /packages/core/tests/ai/assert/assert.test.ts: -------------------------------------------------------------------------------- 1 | import { AiAssert } from '@/ai-model'; 2 | import { getContextFromFixture } from 'tests/evaluation'; 3 | /* eslint-disable max-lines-per-function */ 4 | import { describe, expect, it, vi } from 'vitest'; 5 | 6 | vi.setConfig({ 7 | testTimeout: 180 * 1000, 8 | hookTimeout: 30 * 1000, 9 | }); 10 | 11 | describe('assert', () => { 12 | it('todo pass', async () => { 13 | const { context } = await getContextFromFixture('todo'); 14 | 15 | const { 16 | content: { pass }, 17 | } = await AiAssert({ 18 | assertion: 'Three tasks have been added', 19 | context, 20 | }); 21 | expect(pass).toBe(true); 22 | }); 23 | 24 | it('todo error', async () => { 25 | const { context } = await getContextFromFixture('todo'); 26 | 27 | const { 28 | content: { pass, thought }, 29 | } = await AiAssert({ 30 | assertion: 'There are four tasks in the task list', 31 | context, 32 | }); 33 | expect(pass).toBe(false); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/core/tests/ai/extract/__snapshots__/extract.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`extract > online order 1`] = ` 4 | { 5 | "data": [ 6 | { 7 | "name": "Very Tangerine Blast (Original)", 8 | "price": "6.82", 9 | }, 10 | { 11 | "name": "Mango Grapefruit Pops", 12 | "price": "6.54", 13 | }, 14 | ], 15 | "errors": [], 16 | } 17 | `; 18 | 19 | exports[`extract > todo obj 1`] = ` 20 | { 21 | "data": [ 22 | { 23 | "checked": false, 24 | "text": "Learn Python", 25 | }, 26 | { 27 | "checked": false, 28 | "text": "Learn Rust", 29 | }, 30 | { 31 | "checked": false, 32 | "text": "Learn AI", 33 | }, 34 | ], 35 | "errors": [], 36 | } 37 | `; 38 | -------------------------------------------------------------------------------- /packages/core/tests/ai/llm-inspect.test.ts: -------------------------------------------------------------------------------- 1 | import { AiLocateElement, AiLocateSection } from '@/ai-model'; 2 | import { getContextFromFixture } from 'tests/evaluation'; 3 | import { expect, test, vi } from 'vitest'; 4 | 5 | vi.setConfig({ 6 | testTimeout: 60 * 1000, 7 | }); 8 | 9 | test( 10 | 'basic inspect', 11 | async () => { 12 | const { context } = await getContextFromFixture('todo'); 13 | 14 | const startTime = Date.now(); 15 | const { parseResult } = await AiLocateElement({ 16 | context, 17 | targetElementDescription: 'input 输入框', 18 | }); 19 | expect(parseResult.elements.length).toBe(1); 20 | }, 21 | { 22 | timeout: 1000000, 23 | }, 24 | ); 25 | 26 | test('locate section', async () => { 27 | const { context } = await getContextFromFixture('todo'); 28 | const { rect } = await AiLocateSection({ 29 | context, 30 | sectionDescription: '搜索框', 31 | }); 32 | expect(rect).toBeDefined(); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/core/tests/ai/llm-planning/__snapshots__/basic.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`automation - llm planning > basic run 1`] = ` 4 | { 5 | "timeMs": 3500, 6 | } 7 | `; 8 | 9 | exports[`automation - llm planning > basic run 2`] = ` 10 | { 11 | "value": "Enter", 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /packages/core/tests/ai/llm-planning/__snapshots__/input.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`automation - planning input > input value 1`] = ` 4 | [ 5 | { 6 | "locate": { 7 | "id": "okgbn", 8 | "prompt": "", 9 | }, 10 | "param": { 11 | "value": "learning english", 12 | }, 13 | "thought": undefined, 14 | "type": "Input", 15 | }, 16 | ] 17 | `; 18 | 19 | exports[`automation - planning input > input value 2`] = ` 20 | [ 21 | { 22 | "locate": { 23 | "id": "okgbn", 24 | "prompt": "", 25 | }, 26 | "param": { 27 | "value": "learning english", 28 | }, 29 | "thought": undefined, 30 | "type": "Input", 31 | }, 32 | { 33 | "locate": null, 34 | "param": { 35 | "value": "Enter", 36 | }, 37 | "thought": undefined, 38 | "type": "KeyboardPress", 39 | }, 40 | ] 41 | `; 42 | 43 | exports[`automation - planning input > input value Add, delete, correct and check 1`] = ` 44 | [ 45 | { 46 | "locate": { 47 | "id": "okgbn", 48 | "prompt": "", 49 | }, 50 | "param": { 51 | "value": "Learn English tomorrow", 52 | }, 53 | "thought": undefined, 54 | "type": "Input", 55 | }, 56 | ] 57 | `; 58 | -------------------------------------------------------------------------------- /packages/core/tests/ai/llm-planning/__snapshots__/planning.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`automation - planning > basic run 1`] = ` 4 | { 5 | "timeMs": 3500, 6 | } 7 | `; 8 | 9 | exports[`automation - planning > basic run 2`] = ` 10 | { 11 | "value": "Enter", 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /packages/core/tests/ai/llm-section-locator.test.ts: -------------------------------------------------------------------------------- 1 | import { AiLocateElement } from '@/ai-model'; 2 | import { AiLocateSection } from '@/ai-model/inspect'; 3 | import { saveBase64Image } from '@/image'; 4 | import { getTmpFile } from '@/utils'; 5 | import { vlLocateMode } from '@midscene/shared/env'; 6 | import { getContextFromFixture } from 'tests/evaluation'; 7 | import { expect, test } from 'vitest'; 8 | 9 | test.skipIf(!vlLocateMode())( 10 | 'locate section', 11 | async () => { 12 | const { context } = await getContextFromFixture('antd-tooltip'); 13 | const { rect, imageBase64 } = await AiLocateSection({ 14 | context, 15 | sectionDescription: 'the version info on the top right corner', 16 | }); 17 | expect(rect).toBeDefined(); 18 | expect(imageBase64).toBeDefined(); 19 | 20 | const tmpFile = getTmpFile('jpg'); 21 | await saveBase64Image({ 22 | base64Data: imageBase64!, 23 | outputPath: tmpFile!, 24 | }); 25 | }, 26 | { 27 | timeout: 60 * 1000, 28 | }, 29 | ); 30 | -------------------------------------------------------------------------------- /packages/core/tests/ai/ui-tars-planning/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/core/tests/ai/ui-tars-planning/output.png -------------------------------------------------------------------------------- /packages/core/tests/fixtures/baidu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/core/tests/fixtures/baidu.png -------------------------------------------------------------------------------- /packages/core/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "../", 5 | "rootDir": "../" 6 | }, 7 | "include": ["**/*", "../src", "evaluation.ts"], 8 | "exclude": ["**/node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/tests/unit-test/__snapshots__/llm-planning.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`llm planning - build yaml flow > build yaml flow 1`] = ` 4 | [ 5 | { 6 | "aiInput": "hello", 7 | "locate": "The input box for adding a new todo", 8 | }, 9 | { 10 | "aiHover": "The second item 'Learn Rust' in the task list", 11 | }, 12 | { 13 | "aiTap": "The input box labeled 'What needs to be done?'", 14 | }, 15 | { 16 | "aiScroll": null, 17 | "direction": "down", 18 | "distance": 500, 19 | "locate": "some button", 20 | "scrollType": "once", 21 | }, 22 | ] 23 | `; 24 | -------------------------------------------------------------------------------- /packages/core/tests/unit-test/executor/__snapshots__/index.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`executor > insight - basic run 1`] = ` 4 | { 5 | "element": { 6 | "attributes": { 7 | "nodeType": "CONTAINER Node", 8 | }, 9 | "center": [ 10 | 250, 11 | 250, 12 | ], 13 | "id": "0", 14 | "indexId": undefined, 15 | "rect": { 16 | "height": 100, 17 | "left": 200, 18 | "top": 200, 19 | "width": 100, 20 | }, 21 | "xpaths": [], 22 | }, 23 | } 24 | `; 25 | -------------------------------------------------------------------------------- /packages/core/tests/unit-test/mocks/intl-mock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock file for Intl.DateTimeFormat 3 | * This mock ensures that getTimeZoneInfo always returns non-China timezone 4 | */ 5 | 6 | const originalIntl = global.Intl; 7 | 8 | export function mockNonChinaTimeZone() { 9 | const mockIntl = { 10 | DateTimeFormat: () => ({ 11 | resolvedOptions: () => ({ 12 | timeZone: 'America/New_York', // Using US timezone as non-China example 13 | }), 14 | }), 15 | }; 16 | 17 | // @ts-ignore - Overriding readonly property 18 | global.Intl = { ...originalIntl, ...mockIntl }; 19 | } 20 | 21 | export function restoreIntl() { 22 | // @ts-ignore - Restoring readonly property 23 | global.Intl = originalIntl; 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/tests/unit-test/prompt/__snapshots__/describe.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`elementDescriberInstruction > should return the correct instruction 1`] = ` 4 | "Describe the element in the red rectangle for precise identification. Use English. 5 | 6 | Rules: 7 | 1. Start with element type (button, input, link, etc.) 8 | 2. Include key identifiers: 9 | - Text content: "with text 'Submit'" 10 | - Visual features: "blue background", "icon only" 11 | - Position: "top-right", "below search bar" 12 | 3. Keep description under 20 words 13 | 4. Don't mention the red rectangle 14 | 15 | Return JSON: 16 | { 17 | "description": "brief element type with key identifiers", 18 | "error"?: "error message if any" 19 | }" 20 | `; 21 | -------------------------------------------------------------------------------- /packages/core/tests/unit-test/prompt/assertion.test.ts: -------------------------------------------------------------------------------- 1 | import { systemPromptToAssert } from '@/ai-model/prompt/assertion'; 2 | import { describe, expect, it, vi } from 'vitest'; 3 | 4 | describe('Assertion prompt', () => { 5 | vi.mock('@midscene/shared/env', () => ({ 6 | getPreferredLanguage: vi.fn().mockReturnValue('English'), 7 | })); 8 | 9 | it('return default when it is not UI-Tars', () => { 10 | const prompt = systemPromptToAssert({ isUITars: false }); 11 | expect(prompt).toMatchSnapshot(); 12 | }); 13 | 14 | it('return UI-Tars specific when it is UI-Tars', () => { 15 | const prompt = systemPromptToAssert({ isUITars: true }); 16 | expect(prompt).toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/core/tests/unit-test/prompt/describe.test.ts: -------------------------------------------------------------------------------- 1 | import { elementDescriberInstruction } from '@/ai-model/prompt/describe'; 2 | import { describe, expect, it, vi } from 'vitest'; 3 | 4 | describe('elementDescriberInstruction', () => { 5 | vi.mock('@midscene/shared/env', () => ({ 6 | getPreferredLanguage: vi.fn().mockReturnValue('English'), 7 | })); 8 | 9 | it('should return the correct instruction', () => { 10 | expect(elementDescriberInstruction()).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "sourceMap": true, 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "isolatedModules": true, 10 | "jsx": "preserve", 11 | "lib": ["DOM", "ESNext"], 12 | "moduleResolution": "node", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | }, 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "module": "ESNext", 21 | "target": "es2018" 22 | }, 23 | "exclude": ["**/node_modules"], 24 | "include": ["src", "report"] 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import dotenv from 'dotenv'; 3 | import { defineConfig } from 'vitest/config'; 4 | import { version } from './package.json'; 5 | 6 | /** 7 | * Read environment variables from file. 8 | * https://github.com/motdotla/dotenv 9 | */ 10 | dotenv.config({ 11 | path: path.join(__dirname, '../../.env'), 12 | override: true, 13 | debug: true, 14 | }); 15 | 16 | const enableAiTest = Boolean(process.env.AITEST); 17 | const basicTest = ['tests/unit-test/**/*.test.ts']; 18 | 19 | export default defineConfig({ 20 | test: { 21 | include: enableAiTest ? ['tests/ai/**/**.test.ts'] : basicTest, 22 | }, 23 | define: { 24 | __VERSION__: `'${version}'`, 25 | }, 26 | resolve: { 27 | alias: { 28 | '@': path.resolve(__dirname, 'src'), 29 | }, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /packages/evaluation/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Midscene.js dump files 3 | midscene_run/report 4 | midscene_run/tmp 5 | page-data/screenspot-v2 6 | -------------------------------------------------------------------------------- /packages/evaluation/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### ScreenSpot-v2 3 | 4 | ```bash 5 | pip install huggingface_hub 6 | ``` 7 | 8 | ```bash 9 | npm run download-screenspot-v2 10 | ``` -------------------------------------------------------------------------------- /packages/evaluation/data-generator/fixture.ts: -------------------------------------------------------------------------------- 1 | import type { PlayWrightAiFixtureType } from '@midscene/web'; 2 | import { PlaywrightAiFixture } from '@midscene/web/playwright'; 3 | import { test as base } from '@playwright/test'; 4 | 5 | export const test = base.extend<PlayWrightAiFixtureType>(PlaywrightAiFixture()); 6 | -------------------------------------------------------------------------------- /packages/evaluation/data-generator/generator-headed.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlaywrightWebPage } from '@midscene/web/playwright'; 2 | import { test } from './fixture'; 3 | import { generateExtractData, generateTestDataPath } from './utils'; 4 | 5 | function sleep(time: number) { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | resolve(0); 9 | }, time); 10 | }); 11 | } 12 | 13 | test('taobao', async ({ page, ai }) => { 14 | const playwrightPage = new PlaywrightWebPage(page); 15 | page.setViewportSize({ width: 1280, height: 800 }); 16 | 17 | await page.goto('https://www.taobao.com/'); 18 | 19 | // for --ui 20 | await sleep(5000); 21 | 22 | await generateExtractData(playwrightPage, generateTestDataPath('taobao')); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/evaluation/page-cases/assertion/online_order.json: -------------------------------------------------------------------------------- 1 | { 2 | "testDataPath": "online_order", 3 | "testCases": [ 4 | { 5 | "prompt": "there are three tabs in the page, named 'Menu', 'Reviews', 'Merchant'", 6 | "expected": true 7 | }, 8 | { 9 | "prompt": "there is a shopping bag icon on the top right of the page", 10 | "expected": true 11 | }, 12 | { 13 | "prompt": "the 'select option' button is blue", 14 | "expected": false 15 | }, 16 | { 17 | "prompt": "the tab name on the right of 'Reviews' is 'Merry'", 18 | "expected": false 19 | }, 20 | { 21 | "prompt": "there are three tabs in the page, named 'Home', 'Order', 'Profile'", 22 | "expected": false 23 | }, 24 | { 25 | "prompt": "The shopping bag icon on the top left of the page", 26 | "expected": false 27 | }, 28 | { 29 | "prompt": "There is a homepage icon on the top right of the page, instead of a shopping bag icon", 30 | "expected": false 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /packages/evaluation/page-cases/assertion/online_order_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "testDataPath": "online_order_list", 3 | "testCases": [ 4 | { 5 | "prompt": "左侧有个菜单写着‘要简单’", 6 | "expected": true 7 | }, 8 | { 9 | "prompt": "左侧有个菜单写着‘要米饭’", 10 | "expected": false 11 | }, 12 | { 13 | "prompt": "有一杯饮料的名字是多肉忙忙", 14 | "expected": false 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/evaluation/page-cases/inspect/antd-carousel.json-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/inspect/antd-carousel.json-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/inspect/aweme-login.json-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/inspect/aweme-login.json-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/inspect/aweme-play.json-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/inspect/aweme-play.json-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/inspect/online_order.json-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/inspect/online_order.json-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/inspect/online_order_list.json-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/inspect/online_order_list.json-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/inspect/taobao.json-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/inspect/taobao.json-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/inspect/todo.json-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/inspect/todo.json-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/planning/antd-form-vl.json-planning-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/planning/antd-form-vl.json-planning-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/planning/antd-tooltip-vl.json-planning-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/planning/antd-tooltip-vl.json-planning-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/planning/aweme-login-vl.json: -------------------------------------------------------------------------------- 1 | { 2 | "testDataPath": "aweme-login", 3 | "testCases": [ 4 | { 5 | "prompt": "type 'user' in the username input box, type '98' in the age input box", 6 | "log": "type 'user' in the username input box", 7 | "response_planning": { 8 | "error": "Failed to plan actions: The current screenshot does not show a username input box or an age input box. The visible fields are for phone number and verification code, which do not match the instruction requirements." 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/evaluation/page-cases/planning/todo-vl.json-planning-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/planning/todo-vl.json-planning-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-cases/section-locator/antd-tooltip.json: -------------------------------------------------------------------------------- 1 | { 2 | "testDataPath": "antd-tooltip", 3 | "testCases": [ 4 | { 5 | "prompt": "the version info on the top right corner", 6 | "annotation_index_id": 1, 7 | "response_rect": { 8 | "left": 1245, 9 | "top": 0, 10 | "width": 588, 11 | "height": 200 12 | } 13 | }, 14 | { 15 | "prompt": "‘位置有12个方向’上面的一圈按钮", 16 | "annotation_index_id": 2, 17 | "response_rect": { 18 | "left": 1049, 19 | "top": 526, 20 | "width": 639, 21 | "height": 386 22 | } 23 | }, 24 | { 25 | "prompt": "‘位置有12个方向’上面的 Top 按钮", 26 | "annotation_index_id": 3, 27 | "response_rect": { 28 | "left": 1049, 29 | "top": 532, 30 | "width": 464, 31 | "height": 380 32 | } 33 | }, 34 | { 35 | "prompt": "‘a series of buttons under 'show/hide' switch", 36 | "annotation_index_id": 4, 37 | "response_rect": { 38 | "left": 345, 39 | "top": 754, 40 | "width": 637, 41 | "height": 326 42 | } 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /packages/evaluation/page-cases/section-locator/antd-tooltip.json-coordinates-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-cases/section-locator/antd-tooltip.json-coordinates-annotated.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-carousel/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-carousel/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-carousel/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-carousel/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-carousel/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-carousel/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-carousel/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-carousel/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-form/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-form/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-form/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-form/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-form/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-form/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-form/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-form/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-pagination/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-pagination/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-pagination/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-pagination/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-pagination/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-pagination/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-pagination/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-pagination/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-tooltip/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-tooltip/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-tooltip/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-tooltip/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-tooltip/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-tooltip/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/antd-tooltip/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/antd-tooltip/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/aweme-login/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/aweme-login/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/aweme-login/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/aweme-login/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/aweme-login/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/aweme-login/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/aweme-login/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/aweme-login/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/aweme-play/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/aweme-play/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/aweme-play/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/aweme-play/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/aweme-play/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/aweme-play/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/aweme-play/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/aweme-play/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/githubstatus/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/githubstatus/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/githubstatus/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/githubstatus/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/githubstatus/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/githubstatus/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/githubstatus/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/githubstatus/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/image-only/.gitignore: -------------------------------------------------------------------------------- 1 | tmp.* 2 | input.* -------------------------------------------------------------------------------- /packages/evaluation/page-data/online_order/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/online_order/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/online_order/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/online_order/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/online_order/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/online_order/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/online_order/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/online_order/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/online_order_list/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/online_order_list/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/online_order_list/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/online_order_list/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/online_order_list/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/online_order_list/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/online_order_list/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/online_order_list/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/taobao/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/taobao/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/taobao/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/taobao/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/taobao/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/taobao/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/taobao/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/taobao/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/todo-input-with-value/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/todo-input-with-value/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/todo-input-with-value/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/todo-input-with-value/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/todo-input-with-value/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/todo-input-with-value/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/todo-input-with-value/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/todo-input-with-value/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/todo/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/todo/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/todo/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/todo/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/todo/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/todo/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/todo/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/todo/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/visualstudio/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/visualstudio/input.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/visualstudio/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/visualstudio/output.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/visualstudio/output_without_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/visualstudio/output_without_text.png -------------------------------------------------------------------------------- /packages/evaluation/page-data/visualstudio/resize-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/evaluation/page-data/visualstudio/resize-output.png -------------------------------------------------------------------------------- /packages/evaluation/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | /** 4 | * See https://playwright.dev/docs/test-configuration. 5 | */ 6 | export default defineConfig({ 7 | timeout: 90 * 1000, 8 | /* Run tests in files in parallel */ 9 | fullyParallel: false, 10 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 11 | forbidOnly: Boolean(process.env.CI), 12 | /* Retry on CI only */ 13 | retries: 0, //process.env.CI ? 1 : 0, 14 | /* Opt out of parallel tests on CI. */ 15 | workers: process.env.CI ? 1 : undefined, 16 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 17 | // reporter: 'html', 18 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 19 | use: { 20 | /* Base URL to use in actions like `await page.goto('/')`. */ 21 | // baseURL: 'http://127.0.0.1:3000', 22 | 23 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 24 | trace: 'on-first-retry', 25 | deviceScaleFactor: 1, // Device scaling factor 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /packages/evaluation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "isolatedModules": true, 9 | "jsx": "preserve", 10 | "lib": ["DOM", "ESNext"], 11 | "moduleResolution": "node", 12 | "paths": { 13 | "@/*": ["./src/*"] 14 | }, 15 | "target": "ESNext", 16 | "resolveJsonModule": true, 17 | "rootDir": "./", 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "module": "ESNext" 21 | }, 22 | "exclude": ["node_modules"], 23 | "include": ["src", "tests", "./playwright.config.ts", "./vitest.config"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/evaluation/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import dotenv from 'dotenv'; 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | /** 6 | * Read environment variables from file. 7 | * https://github.com/motdotla/dotenv 8 | */ 9 | dotenv.config({ 10 | path: path.join(__dirname, '../../.env'), 11 | override: true, 12 | debug: true, 13 | }); 14 | 15 | export default defineConfig({ 16 | test: { 17 | include: ['tests/**.test.ts'], 18 | }, 19 | resolve: { 20 | alias: { 21 | '@': path.resolve(__dirname, 'src'), 22 | }, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /packages/mcp/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Midscene.js dump files 3 | midscene_run/dump 4 | midscene_run/report 5 | midscene_run/tmp 6 | midscene_run/log 7 | src/inject-tp.js -------------------------------------------------------------------------------- /packages/mcp/README.md: -------------------------------------------------------------------------------- 1 | # Midscene MCP 2 | 3 | docs: https://midscenejs.com/mcp.html 4 | -------------------------------------------------------------------------------- /packages/mcp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@midscene/mcp", 3 | "version": "0.21.2", 4 | "type": "module", 5 | "exports": { 6 | ".": { 7 | "types": "./dist/index.d.ts", 8 | "import": "./dist/index.js", 9 | "require": "./dist/index.cjs" 10 | } 11 | }, 12 | "bin": "dist/index.cjs", 13 | "main": "./dist/index.cjs", 14 | "module": "./dist/index.js", 15 | "types": "./dist/index.d.ts", 16 | "files": ["dist"], 17 | "scripts": { 18 | "build": "rslib build", 19 | "dev": "rslib build --watch", 20 | "test": "vitest run", 21 | "inspect": "node scripts/inspect.mjs", 22 | "inspect2": "mcp-inspector node ./dist/test2.cjs" 23 | }, 24 | "devDependencies": { 25 | "@modelcontextprotocol/inspector": "0.9.0", 26 | "@rslib/core": "^0.8.0", 27 | "@types/node": "^18.0.0", 28 | "typescript": "^5.8.3", 29 | "vitest": "3.0.5", 30 | "dotenv": "16.4.5", 31 | "@midscene/web": "workspace:*", 32 | "@midscene/report": "workspace:*", 33 | "@midscene/core": "workspace:*", 34 | "@midscene/shared": "workspace:*", 35 | "@modelcontextprotocol/sdk": "1.10.2", 36 | "zod": "3.24.3" 37 | }, 38 | "dependencies": { 39 | "puppeteer": "24.2.0" 40 | }, 41 | "license": "MIT" 42 | } 43 | -------------------------------------------------------------------------------- /packages/mcp/src/prompts.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | import { PLAYWRIGHT_EXAMPLE_CODE } from '@midscene/shared/constants'; 4 | 5 | const apiText = fs.readFileSync(path.join(__dirname, 'API.mdx'), 'utf-8'); 6 | 7 | export const PROMPTS = { 8 | PLAYWRIGHT_CODE_EXAMPLE: PLAYWRIGHT_EXAMPLE_CODE, 9 | MIDSCENE_API_DOCS: apiText, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/mcp/src/utils.ts: -------------------------------------------------------------------------------- 1 | // Deep merge utility function 2 | export function deepMerge(target: any, source: any): any { 3 | const output = Object.assign({}, target); 4 | if (typeof target !== 'object' || typeof source !== 'object') return source; 5 | 6 | for (const key of Object.keys(source)) { 7 | const targetVal = target[key]; 8 | const sourceVal = source[key]; 9 | if (Array.isArray(targetVal) && Array.isArray(sourceVal)) { 10 | // Deduplicate args/ignoreDefaultArgs, prefer source values 11 | output[key] = Array.from( 12 | new Set([ 13 | ...(key === 'args' || key === 'ignoreDefaultArgs' 14 | ? targetVal.filter( 15 | (arg: string) => 16 | !sourceVal.some( 17 | (launchArg: string) => 18 | arg.startsWith('--') && 19 | launchArg.startsWith(arg.split('=')[0]), 20 | ), 21 | ) 22 | : targetVal), 23 | ...sourceVal, 24 | ]), 25 | ); 26 | } else if (sourceVal instanceof Object && key in target) { 27 | output[key] = deepMerge(targetVal, sourceVal); 28 | } else { 29 | output[key] = sourceVal; 30 | } 31 | } 32 | return output; 33 | } 34 | -------------------------------------------------------------------------------- /packages/mcp/tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | 3 | test('squared', () => { 4 | expect(4).toBe(4); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/mcp/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [".", "../vitest.setup.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/mcp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ES2021"], 4 | "module": "ESNext", 5 | "noEmit": true, 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "isolatedModules": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "bundler", 11 | "useDefineForClassFields": true, 12 | "allowImportingTsExtensions": true 13 | }, 14 | "include": ["src"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/mcp/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | // Configure Vitest (https://vitest.dev/config/) 5 | test: {}, 6 | }); 7 | -------------------------------------------------------------------------------- /packages/recorder/.gitignore: -------------------------------------------------------------------------------- 1 | # Local 2 | .DS_Store 3 | *.local 4 | *.log* 5 | 6 | # Dist 7 | node_modules 8 | dist/ 9 | 10 | # IDE 11 | .vscode/* 12 | !.vscode/extensions.json 13 | .idea 14 | -------------------------------------------------------------------------------- /packages/recorder/README.md: -------------------------------------------------------------------------------- 1 | # Rslib project 2 | 3 | ## Setup 4 | 5 | Install the dependencies: 6 | 7 | ```bash 8 | pnpm install 9 | ``` 10 | 11 | ## Get started 12 | 13 | Build the library: 14 | 15 | ```bash 16 | pnpm build 17 | ``` 18 | 19 | Build the library in watch mode: 20 | 21 | ```bash 22 | pnpm dev 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/recorder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@midscene/recorder", 3 | "version": "0.21.2", 4 | "type": "module", 5 | "exports": { 6 | ".": { 7 | "types": "./dist/index.d.ts", 8 | "import": "./dist/index.js" 9 | } 10 | }, 11 | "types": "./dist/index.d.ts", 12 | "files": ["dist"], 13 | "scripts": { 14 | "build": "rslib build", 15 | "dev": "rslib build --watch" 16 | }, 17 | "devDependencies": { 18 | "@rsbuild/plugin-react": "^1.3.1", 19 | "@rslib/core": "^0.8.0", 20 | "@types/react": "^18.3.1", 21 | "react": "18.3.1", 22 | "typescript": "^5.8.3" 23 | }, 24 | "dependencies": { 25 | "@ant-design/icons": "^5.3.1", 26 | "antd": "^5.21.6", 27 | "dayjs": "^1.11.11", 28 | "react-dom": "18.3.1", 29 | "@midscene/shared": "workspace:*" 30 | }, 31 | "peerDependencies": { 32 | "react": "18.3.1", 33 | "react-dom": "18.3.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/recorder/rslib.config.ts: -------------------------------------------------------------------------------- 1 | import { pluginReact } from '@rsbuild/plugin-react'; 2 | import { defineConfig } from '@rslib/core'; 3 | 4 | export default defineConfig({ 5 | lib: [ 6 | { 7 | bundle: false, 8 | dts: true, 9 | format: 'esm', 10 | source: { 11 | entry: { 12 | index: './src/**', 13 | }, 14 | }, 15 | }, 16 | { 17 | format: 'iife', 18 | dts: false, 19 | source: { 20 | entry: { 21 | 'recorder-iife': './src/recorder-iife-index.ts', 22 | }, 23 | }, 24 | }, 25 | ], 26 | output: { 27 | target: 'web', 28 | }, 29 | plugins: [pluginReact()], 30 | }); 31 | -------------------------------------------------------------------------------- /packages/recorder/src/Button.tsx: -------------------------------------------------------------------------------- 1 | import './button.css'; 2 | 3 | interface ButtonProps { 4 | primary?: boolean; 5 | backgroundColor?: string; 6 | size?: 'small' | 'medium' | 'large'; 7 | label: string; 8 | onClick?: () => void; 9 | } 10 | 11 | export const Button = ({ 12 | primary = false, 13 | size = 'medium', 14 | backgroundColor, 15 | label, 16 | ...props 17 | }: ButtonProps) => { 18 | const mode = primary ? 'demo-button--primary' : 'demo-button--secondary'; 19 | return ( 20 | <button 21 | type="button" 22 | className={['demo-button', `demo-button--${size}`, mode].join(' ')} 23 | style={{ backgroundColor }} 24 | {...props} 25 | > 26 | {label} 27 | </button> 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /packages/recorder/src/RecordTimeline.css: -------------------------------------------------------------------------------- 1 | .record-timeline-screenshot-thumbnail { 2 | transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; 3 | } 4 | 5 | .record-timeline-screenshot-thumbnail:hover { 6 | transform: scale(1.05); 7 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 8 | } 9 | 10 | .record-timeline-screenshot-popover { 11 | max-width: 600px; 12 | } 13 | 14 | .record-timeline-screenshot-popover .ant-popover-inner { 15 | padding: 8px; 16 | } 17 | 18 | .record-timeline-screenshot-full { 19 | border-radius: 4px; 20 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 21 | } 22 | -------------------------------------------------------------------------------- /packages/recorder/src/button.css: -------------------------------------------------------------------------------- 1 | .demo-button { 2 | font-weight: 700; 3 | border: 0; 4 | border-radius: 3em; 5 | cursor: pointer; 6 | display: inline-block; 7 | line-height: 1; 8 | } 9 | 10 | .demo-button--primary { 11 | color: white; 12 | background-color: #1ea7fd; 13 | } 14 | 15 | .demo-button--secondary { 16 | color: #333; 17 | background-color: transparent; 18 | box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; 19 | } 20 | 21 | .demo-button--small { 22 | font-size: 12px; 23 | padding: 10px 16px; 24 | } 25 | 26 | .demo-button--medium { 27 | font-size: 14px; 28 | padding: 11px 20px; 29 | } 30 | 31 | .demo-button--large { 32 | font-size: 16px; 33 | padding: 12px 24px; 34 | } 35 | -------------------------------------------------------------------------------- /packages/recorder/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { Button } from './Button'; 2 | export { 3 | EventRecorder, 4 | type RecordedEvent, 5 | type ChromeRecordedEvent, 6 | convertToChromeEvents, 7 | } from './recorder'; 8 | export { RecordTimeline } from './RecordTimeline'; 9 | -------------------------------------------------------------------------------- /packages/recorder/src/recorder-iife-index.ts: -------------------------------------------------------------------------------- 1 | import { EventRecorder } from './recorder'; 2 | 3 | declare global { 4 | interface Window { 5 | EventRecorder: typeof EventRecorder; 6 | } 7 | } 8 | 9 | window.EventRecorder = EventRecorder; 10 | // const eventRecorder = new EventRecorder((event) => { 11 | // console.log(event); 12 | // }); 13 | 14 | // eventRecorder.start(); 15 | -------------------------------------------------------------------------------- /packages/recorder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "ES2021"], 4 | "module": "ESNext", 5 | "jsx": "react-jsx", 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "isolatedModules": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "bundler", 11 | "useDefineForClassFields": true 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/shared/README.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | An AI-powered automation SDK can control the page, perform assertions, and extract data in JSON format using natural language. 4 | 5 | See https://midscenejs.com/ for details. 6 | 7 | ## License 8 | 9 | Midscene is MIT licensed. -------------------------------------------------------------------------------- /packages/shared/modern.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, moduleTools } from '@modern-js/module-tools'; 2 | 3 | export default defineConfig({ 4 | plugins: [moduleTools()], 5 | buildPreset: 'npm-library', 6 | buildConfig: { 7 | input: { 8 | index: './src/index.ts', 9 | img: './src/img/index.ts', 10 | constants: './src/constants/index.ts', 11 | extractor: './src/extractor/index.ts', 12 | 'extractor-debug': './src/extractor/debug.ts', 13 | fs: './src/node/fs.ts', 14 | utils: './src/utils.ts', 15 | logger: './src/logger.ts', 16 | common: './src/common.ts', 17 | 'us-keyboard-layout': './src/us-keyboard-layout.ts', 18 | env: './src/env.ts', 19 | types: './src/types/index.ts', 20 | }, 21 | target: 'es2020', 22 | dts: { 23 | respectExternal: true, 24 | }, 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /packages/shared/modern.inspect.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, moduleTools } from '@modern-js/module-tools'; 2 | 3 | // It was split into two configuration files because of a bug in the build config array 4 | export default defineConfig({ 5 | plugins: [moduleTools()], 6 | buildConfig: { 7 | platform: 'browser', 8 | buildType: 'bundle', 9 | format: 'iife', 10 | dts: false, 11 | input: { 12 | htmlElement: 'src/extractor/index.ts', 13 | htmlElementDebug: 'src/extractor/debug.ts', 14 | }, 15 | autoExternal: false, 16 | outDir: 'dist/script', 17 | esbuildOptions: (options) => { 18 | options.globalName = 'midscene_element_inspector'; 19 | return options; 20 | }, 21 | target: 'es6', 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /packages/shared/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const TEXT_SIZE_THRESHOLD = 9; 2 | 3 | export const TEXT_MAX_SIZE = 40; 4 | 5 | export const CONTAINER_MINI_HEIGHT = 3; 6 | export const CONTAINER_MINI_WIDTH = 3; 7 | 8 | export enum NodeType { 9 | CONTAINER = 'CONTAINER Node', 10 | FORM_ITEM = 'FORM_ITEM Node', 11 | BUTTON = 'BUTTON Node', 12 | A = 'Anchor Node', 13 | IMG = 'IMG Node', 14 | TEXT = 'TEXT Node', 15 | POSITION = 'POSITION Node', 16 | } 17 | 18 | export const PLAYGROUND_SERVER_PORT = 5800; 19 | export const SCRCPY_SERVER_PORT = 5700; 20 | 21 | export const DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT = 5000; 22 | export const DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT = 2000; 23 | export const DEFAULT_WAIT_FOR_NETWORK_IDLE_TIME = 300; 24 | export const DEFAULT_WAIT_FOR_NETWORK_IDLE_CONCURRENCY = 2; 25 | 26 | export { PLAYWRIGHT_EXAMPLE_CODE, YAML_EXAMPLE_CODE } from './example-code'; 27 | -------------------------------------------------------------------------------- /packages/shared/src/extractor/constants.ts: -------------------------------------------------------------------------------- 1 | export { 2 | NodeType, 3 | TEXT_MAX_SIZE, 4 | TEXT_SIZE_THRESHOLD, 5 | } from '../constants/index'; 6 | -------------------------------------------------------------------------------- /packages/shared/src/extractor/debug.ts: -------------------------------------------------------------------------------- 1 | import { webExtractTextWithPosition } from '.'; 2 | import { 3 | setExtractTextWithPositionOnWindow, 4 | setMidsceneVisibleRectOnWindow, 5 | } from './util'; 6 | 7 | console.log(webExtractTextWithPosition(document.body, true)); 8 | console.log(JSON.stringify(webExtractTextWithPosition(document.body, true))); 9 | setExtractTextWithPositionOnWindow(); 10 | setMidsceneVisibleRectOnWindow(); 11 | -------------------------------------------------------------------------------- /packages/shared/src/extractor/index.ts: -------------------------------------------------------------------------------- 1 | import type { NodeType } from '../constants/index'; 2 | 3 | export interface ElementInfo { 4 | id: string; 5 | indexId: number; 6 | nodeHashId: string; 7 | xpaths?: string[]; 8 | attributes: { 9 | nodeType: NodeType; 10 | [key: string]: string; 11 | }; 12 | nodeType: NodeType; 13 | content: string; 14 | rect: { left: number; top: number; width: number; height: number }; 15 | center: [number, number]; 16 | isVisible: boolean; 17 | } 18 | 19 | export interface ElementNode { 20 | node: ElementInfo | null; 21 | children: ElementNode[]; 22 | } 23 | 24 | export { 25 | descriptionOfTree, 26 | traverseTree, 27 | treeToList, 28 | truncateText, 29 | trimAttributes, 30 | } from './tree'; 31 | 32 | export { extractTextWithPosition as webExtractTextWithPosition } from './web-extractor'; 33 | 34 | export { extractTreeNode as webExtractNodeTree } from './web-extractor'; 35 | 36 | export { extractTreeNodeAsString as webExtractNodeTreeAsString } from './web-extractor'; 37 | 38 | export { setNodeHashCacheListOnWindow, getNodeFromCacheList } from './util'; 39 | 40 | export { 41 | getXpathsById, 42 | getNodeInfoByXpath, 43 | getElementInfoByXpath, 44 | } from './locator'; 45 | 46 | export { generateElementByPosition } from './dom-util'; 47 | -------------------------------------------------------------------------------- /packages/shared/src/img/get-jimp.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import type Jimp from 'jimp/browser/lib/jimp.js'; 3 | 4 | const ifInBrowser = typeof window !== 'undefined'; 5 | export default async function getJimp(): Promise<typeof Jimp> { 6 | if (ifInBrowser) { 7 | // @ts-ignore 8 | await import('jimp/browser/lib/jimp.js'); 9 | return (window as any).Jimp; 10 | } 11 | // return Jimp; 12 | // @ts-ignore 13 | return (await import('jimp')).default; 14 | } 15 | -------------------------------------------------------------------------------- /packages/shared/src/img/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | imageInfo, 3 | imageInfoOfBase64, 4 | bufferFromBase64, 5 | base64Encoded, 6 | isValidPNGImageBuffer, 7 | } from './info'; 8 | export { 9 | trimImage, 10 | resizeImg, 11 | resizeImgBase64, 12 | transformImgPathToBase64, 13 | zoomForGPT4o, 14 | saveBase64Image, 15 | paddingToMatchBlock, 16 | paddingToMatchBlockByBase64, 17 | cropByRect, 18 | jimpFromBase64, 19 | jimpToBase64, 20 | } from './transform'; 21 | export { processImageElementInfo, compositeElementInfoImg } from './box-select'; 22 | export { drawBoxOnImage, savePositionImg } from './draw-box'; 23 | -------------------------------------------------------------------------------- /packages/shared/src/img/jimp.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'jimp/browser/lib/jimp.js' { 2 | import Jimp from 'jimp'; 3 | export default Jimp; 4 | } 5 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /packages/shared/src/modern-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types='@modern-js/module-tools/types' /> 2 | -------------------------------------------------------------------------------- /packages/shared/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { NodeType } from '../constants'; 2 | import type { ElementInfo } from '../extractor'; 3 | 4 | export interface Point { 5 | left: number; 6 | top: number; 7 | } 8 | 9 | export interface Size { 10 | width: number; // device independent window size 11 | height: number; 12 | dpr?: number; // the scale factor of the screenshots 13 | } 14 | 15 | export type Rect = Point & Size & { zoom?: number }; 16 | 17 | export abstract class BaseElement { 18 | abstract id: string; 19 | 20 | abstract indexId?: number; // markerId for web 21 | 22 | abstract attributes: { 23 | nodeType: NodeType; 24 | [key: string]: string; 25 | }; 26 | 27 | abstract content: string; 28 | 29 | abstract rect: Rect; 30 | 31 | abstract center: [number, number]; 32 | 33 | abstract xpaths?: string[]; 34 | 35 | abstract isVisible: boolean; 36 | } 37 | 38 | export interface ElementTreeNode< 39 | ElementType extends BaseElement = BaseElement, 40 | > { 41 | node: ElementType | null; 42 | children: ElementTreeNode<ElementType>[]; 43 | } 44 | 45 | export interface WebElementInfo extends ElementInfo { 46 | zoom: number; 47 | } 48 | -------------------------------------------------------------------------------- /packages/shared/tests/fixtures/2x2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/shared/tests/fixtures/2x2.jpeg -------------------------------------------------------------------------------- /packages/shared/tests/fixtures/baidu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/shared/tests/fixtures/baidu.png -------------------------------------------------------------------------------- /packages/shared/tests/fixtures/colorful.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/shared/tests/fixtures/colorful.png -------------------------------------------------------------------------------- /packages/shared/tests/fixtures/heytea.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/shared/tests/fixtures/heytea.jpeg -------------------------------------------------------------------------------- /packages/shared/tests/fixtures/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/shared/tests/fixtures/icon.png -------------------------------------------------------------------------------- /packages/shared/tests/fixtures/long-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/shared/tests/fixtures/long-text.png -------------------------------------------------------------------------------- /packages/shared/tests/fixtures/reference-of-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/shared/tests/fixtures/reference-of-list.png -------------------------------------------------------------------------------- /packages/shared/tests/fixtures/reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/shared/tests/fixtures/reference.png -------------------------------------------------------------------------------- /packages/shared/tests/fixtures/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/shared/tests/fixtures/table.png -------------------------------------------------------------------------------- /packages/shared/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "../", 5 | "rootDir": "../" 6 | }, 7 | "include": ["**/*", "../src"], 8 | "exclude": ["**/node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/shared/tests/unit-test/fs.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { getRunningPkgInfo } from '../../src/node/fs'; 3 | 4 | describe('fs', () => { 5 | it('getRunningPkgInfo', () => { 6 | const info = getRunningPkgInfo(); 7 | expect(info).toBeDefined(); 8 | expect(info?.dir).toMatch(/shared$/); 9 | }); 10 | 11 | it('getRunningPkgInfo - no package.json', () => { 12 | const info = getRunningPkgInfo('/home'); 13 | expect(info?.dir).toEqual('/home'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/shared/tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | 3 | export function getFixture(name: string) { 4 | return join(__dirname, 'fixtures', name); 5 | } 6 | -------------------------------------------------------------------------------- /packages/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "isolatedModules": true, 10 | "jsx": "preserve", 11 | "lib": ["DOM", "ESNext"], 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "rootDir": "src", 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "module": "ES2020", 18 | "target": "es2020", 19 | "types": ["node"] 20 | }, 21 | "exclude": ["**/node_modules"], 22 | "include": ["src", "playwright.config.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/shared/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import dotenv from 'dotenv'; 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | /** 6 | * Read environment variables from file. 7 | * https://github.com/motdotla/dotenv 8 | */ 9 | dotenv.config({ 10 | path: path.join(__dirname, '../../.env'), 11 | }); 12 | 13 | const enableAiTest = Boolean(process.env.AITEST); 14 | const basicTest = ['tests/unit-test/**/*.test.ts']; 15 | 16 | export default defineConfig({ 17 | test: { 18 | include: enableAiTest ? ['tests/ai/**/*.test.ts', ...basicTest] : basicTest, 19 | }, 20 | resolve: { 21 | alias: { 22 | '@': path.resolve(__dirname, 'src'), 23 | }, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /packages/visualizer/.gitignore: -------------------------------------------------------------------------------- 1 | unpacked-extension/lib/ 2 | unpacked-extension/scripts/ 3 | unpacked-extension/pages/ 4 | 5 | 6 | # Midscene.js dump files 7 | midscene_run/report 8 | midscene_run/dump 9 | -------------------------------------------------------------------------------- /packages/visualizer/.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .pnp 4 | .pnp.js 5 | .evn.local 6 | .env.*.local 7 | .history 8 | .rts* 9 | *.log* 10 | *.pid 11 | *.pid.* 12 | *.report 13 | *.lcov 14 | lib-cov 15 | 16 | doc_build/ 17 | node_modules/ 18 | .npm 19 | .lock-wscript 20 | .yarn-integrity 21 | .node_repl_history 22 | .nyc_output 23 | *.tsbuildinfo 24 | .eslintcache 25 | .sonarlint 26 | 27 | coverage/ 28 | release/ 29 | output/ 30 | output_resource/ 31 | 32 | .vscode/**/* 33 | !.vscode/settings.json 34 | !.vscode/extensions.json 35 | .idea/ 36 | 37 | **/*/api/typings/auto-generated 38 | **/*/adapters/**/index.ts 39 | **/*/adapters/**/index.js 40 | -------------------------------------------------------------------------------- /packages/visualizer/README.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | An AI-powered automation SDK can control the page, perform assertions, and extract data in JSON format using natural language. 4 | 5 | See https://midscenejs.com/ for details. 6 | 7 | ## License 8 | 9 | Midscene is MIT licensed. -------------------------------------------------------------------------------- /packages/visualizer/src/blank_polyfill.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /packages/visualizer/src/component/blackboard.less: -------------------------------------------------------------------------------- 1 | @import './common.less'; 2 | 3 | .blackboard { 4 | .footer { 5 | color: #aaa; 6 | } 7 | 8 | ul { 9 | padding-left: 0px; 10 | } 11 | 12 | li { 13 | list-style: none; 14 | } 15 | 16 | .bottom-tip { 17 | height: 30px; 18 | } 19 | 20 | .bottom-tip-item { 21 | max-width: 500px; 22 | color: #AAA; 23 | text-overflow: ellipsis; 24 | word-wrap: break-word; 25 | } 26 | } 27 | 28 | .blackboard-filter { 29 | margin: 10px 0; 30 | } 31 | 32 | .blackboard-main-content { 33 | canvas { 34 | width: 100%; 35 | border: 1px solid @heavy-border-color; 36 | box-sizing: border-box; 37 | } 38 | } -------------------------------------------------------------------------------- /packages/visualizer/src/component/common.less: -------------------------------------------------------------------------------- 1 | @main-text: #3b3b3b; 2 | 3 | @primary-color: #2B83FF; 4 | @main-orange: #F9483E; 5 | 6 | @side-bg: #F8F8F8; 7 | @title-bg: @side-bg; 8 | @border-color: #E5E5E5; 9 | @heavy-border-color: #888; 10 | 11 | @selected-bg: #bfc4da80; 12 | @hover-bg: #dcdcdc80; 13 | 14 | @weak-bg: #F3F3F3; 15 | @weak-text: #777; 16 | @footer-text: #CCC; 17 | 18 | @toolbar-btn-bg: #E9E9E9; 19 | 20 | @layout-space: 20px; 21 | 22 | @side-horizontal-padding: 10px; 23 | @side-vertical-spacing: 10px; 24 | 25 | 26 | @layout-extension-space-horizontal: 20px; 27 | @layout-extension-space-vertical: 20px; -------------------------------------------------------------------------------- /packages/visualizer/src/component/describer.less: -------------------------------------------------------------------------------- 1 | .image-describer { 2 | position: relative; 3 | 4 | .describe-text { 5 | box-sizing: border-box; 6 | position: absolute; 7 | background: #000; 8 | width: 100%; 9 | height: 30px; 10 | left: 0; 11 | bottom: 0; 12 | color: #FFF; 13 | font-size: 12px; 14 | padding: 10px; 15 | } 16 | 17 | .describe-text.success { 18 | background: #047704; 19 | } 20 | 21 | .describe-text.error { 22 | background: #870707; 23 | } 24 | } -------------------------------------------------------------------------------- /packages/visualizer/src/component/github-star.less: -------------------------------------------------------------------------------- 1 | .github-star { 2 | height: 22px; 3 | } -------------------------------------------------------------------------------- /packages/visualizer/src/component/github-star.tsx: -------------------------------------------------------------------------------- 1 | import './github-star.less'; 2 | 3 | export const GithubStar = () => { 4 | return ( 5 | <a 6 | href="https://github.com/web-infra-dev/midscene" 7 | target="_blank" 8 | rel="noreferrer" 9 | style={{ display: 'flex', alignItems: 'center' }} 10 | > 11 | <img 12 | className="github-star" 13 | src="https://img.shields.io/github/stars/web-infra-dev/midscene?style=social" 14 | alt="Github star" 15 | style={{ display: 'block' }} 16 | /> 17 | </a> 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/visualizer/src/component/logo.less: -------------------------------------------------------------------------------- 1 | .logo img { 2 | height: 30px; 3 | line-height: 30px; 4 | vertical-align: baseline; 5 | vertical-align: -webkit-baseline-middle; 6 | } 7 | 8 | .logo-with-star-wrapper { 9 | display: flex; 10 | flex-direction: row; 11 | justify-content: space-between; 12 | } -------------------------------------------------------------------------------- /packages/visualizer/src/component/logo.tsx: -------------------------------------------------------------------------------- 1 | import './logo.less'; 2 | 3 | export const LogoUrl = 4 | 'https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/Midscene.png'; 5 | 6 | export const Logo = ({ hideLogo = false }: { hideLogo?: boolean }) => { 7 | if (hideLogo) { 8 | return null; 9 | } 10 | 11 | return ( 12 | <div className="logo"> 13 | <a href="https://midscenejs.com/" target="_blank" rel="noreferrer"> 14 | <img 15 | alt="Midscene_logo" 16 | src="https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/Midscene.png" 17 | /> 18 | </a> 19 | </div> 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/visualizer/src/component/pixi-loader.tsx: -------------------------------------------------------------------------------- 1 | import 'pixi.js/unsafe-eval'; 2 | import * as PIXI from 'pixi.js'; 3 | 4 | const globalTextureMap = new Map<string, PIXI.Texture>(); 5 | 6 | export const loadTexture = async (img: string) => { 7 | if (globalTextureMap.has(img)) return; 8 | return PIXI.Assets.load(img).then((texture) => { 9 | globalTextureMap.set(img, texture); 10 | }); 11 | }; 12 | 13 | export const getTextureFromCache = (name: string) => { 14 | return globalTextureMap.get(name); 15 | }; 16 | 17 | export const getTexture = async (name: string) => { 18 | if (globalTextureMap.has(name)) { 19 | return globalTextureMap.get(name); 20 | } 21 | 22 | await loadTexture(name); 23 | return globalTextureMap.get(name); 24 | }; 25 | -------------------------------------------------------------------------------- /packages/visualizer/src/component/playground/playground-types.ts: -------------------------------------------------------------------------------- 1 | import type { GroupedActionDump, UIContext } from '@midscene/core'; 2 | import type { ChromeExtensionProxyPageAgent } from '@midscene/web/chrome-extension'; 3 | import type { StaticPageAgent } from '@midscene/web/playground'; 4 | import type { WebUIContext } from '@midscene/web/utils'; 5 | 6 | // result type 7 | export interface PlaygroundResult { 8 | result: any; 9 | dump?: GroupedActionDump | null; 10 | reportHTML?: string | null; 11 | error: string | null; 12 | } 13 | 14 | // Playground component props type 15 | export interface PlaygroundProps { 16 | getAgent: ( 17 | forceSameTabNavigation?: boolean, 18 | ) => StaticPageAgent | ChromeExtensionProxyPageAgent | null; 19 | hideLogo?: boolean; 20 | showContextPreview?: boolean; 21 | dryMode?: boolean; 22 | } 23 | 24 | // static playground component props type 25 | export interface StaticPlaygroundProps { 26 | context: WebUIContext | null; 27 | } 28 | 29 | // service mode type 30 | export type ServiceModeType = 'Server' | 'In-Browser' | 'In-Browser-Extension'; 31 | 32 | // run type 33 | export type RunType = 'aiAction' | 'aiQuery' | 'aiAssert' | 'aiTap'; 34 | -------------------------------------------------------------------------------- /packages/visualizer/src/component/playground/useServerValid.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useEnvConfig } from '../store/store'; 3 | import { checkServerStatus } from './playground-utils'; 4 | 5 | export const useServerValid = (shouldRun = true) => { 6 | const [serverValid, setServerValid] = useState(true); 7 | const { serviceMode } = useEnvConfig(); 8 | 9 | useEffect(() => { 10 | let interruptFlag = false; 11 | if (!shouldRun) return; 12 | 13 | Promise.resolve( 14 | (async () => { 15 | while (!interruptFlag) { 16 | const status = await checkServerStatus(); 17 | if (status) { 18 | setServerValid(true); 19 | } else { 20 | setServerValid(false); 21 | } 22 | // sleep 1s 23 | await new Promise((resolve) => setTimeout(resolve, 1000)); 24 | } 25 | })(), 26 | ); 27 | 28 | return () => { 29 | interruptFlag = true; 30 | }; 31 | }, [serviceMode, shouldRun]); 32 | 33 | return serverValid; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/visualizer/src/component/playground/useStaticPageAgent.ts: -------------------------------------------------------------------------------- 1 | import type { StaticPageAgent } from '@midscene/web/playground'; 2 | import type { WebUIContext } from '@midscene/web/utils'; 3 | import { useMemo } from 'react'; 4 | import { staticAgentFromContext } from './playground-utils'; 5 | 6 | export { staticAgentFromContext }; 7 | 8 | export const useStaticPageAgent = ( 9 | context: WebUIContext | undefined | null, 10 | ): StaticPageAgent | null => { 11 | const agent = useMemo(() => { 12 | if (!context) return null; 13 | return staticAgentFromContext(context); 14 | }, [context]); 15 | 16 | return agent; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/visualizer/src/component/shiny-text.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react'; 2 | import './shiny-text.less'; 3 | 4 | type ColorTheme = 'blue' | 'purple' | 'green' | 'rainbow'; 5 | 6 | interface ShinyTextProps { 7 | text: string; 8 | disabled?: boolean; 9 | speed?: number; 10 | className?: string; 11 | colorTheme?: ColorTheme; 12 | } 13 | 14 | const ShinyText: React.FC<ShinyTextProps> = ({ 15 | text, 16 | disabled = false, 17 | speed = 5, 18 | className = '', 19 | }) => { 20 | const style = { 21 | '--animation-duration': `${speed}s`, 22 | } as React.CSSProperties; 23 | 24 | return ( 25 | <div 26 | className={`shiny-text ${disabled ? 'disabled' : ''} ${className}`} 27 | style={style} 28 | > 29 | {text} 30 | </div> 31 | ); 32 | }; 33 | 34 | export default ShinyText; 35 | -------------------------------------------------------------------------------- /packages/visualizer/src/extension/jimp.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'jimp/browser/lib/jimp.js' { 2 | import Jimp from 'jimp'; 3 | export default Jimp; 4 | } 5 | -------------------------------------------------------------------------------- /packages/visualizer/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | export const ReactComponent: React.FunctionComponent< 3 | React.SVGProps<SVGSVGElement> & { 4 | style?: React.CSSProperties; // 确保包含 style 属性 5 | } 6 | >; 7 | 8 | // const content: string; 9 | export default ReactComponent; 10 | } 11 | 12 | declare module '*.png' { 13 | export default string; 14 | } 15 | 16 | declare module '*.svg?react' { 17 | const ReactComponent: React.FunctionComponent< 18 | React.SVGProps<SVGSVGElement> & { 19 | style?: React.CSSProperties; // 确保包含 style 属性 20 | } 21 | >; 22 | export default ReactComponent; 23 | } 24 | -------------------------------------------------------------------------------- /packages/visualizer/src/icons/close.svg: -------------------------------------------------------------------------------- 1 | <svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M3.12354 2.66667L14.2863 13.3333" stroke="#333333" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> 3 | <path d="M3.12354 13.3333L14.2863 2.66667" stroke="#333333" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> 4 | </svg> 5 | -------------------------------------------------------------------------------- /packages/visualizer/src/icons/history.svg: -------------------------------------------------------------------------------- 1 | <svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M6.63002 9.02131C3.76823 15.1469 8.82714 19.522 12.6931 19.522C16.5591 19.522 19.6931 16.388 19.6931 12.522C19.6931 8.65598 16.5591 5.52197 12.6931 5.52197C10.1024 5.52197 7.84043 6.92936 6.63002 9.02131Z" stroke="black" stroke-opacity="0.85" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/> 3 | <path d="M12.6948 8.32198L12.6943 12.5251L15.6621 15.4928" stroke="black" stroke-opacity="0.85" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/> 4 | </svg> 5 | -------------------------------------------------------------------------------- /packages/visualizer/src/icons/magnifying-glass.svg: -------------------------------------------------------------------------------- 1 | <svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <g clip-path="url(#clip0_541_8555)"> 3 | <path d="M8.39697 14.2909C11.9178 14.2909 14.772 11.4367 14.772 7.91589C14.772 4.39509 11.9178 1.54089 8.39697 1.54089C4.87617 1.54089 2.02197 4.39509 2.02197 7.91589C2.02197 11.4367 4.87617 14.2909 8.39697 14.2909Z" stroke="black" stroke-opacity="0.65" stroke-width="1.5" stroke-linejoin="round"/> 4 | <path d="M10.5185 5.41956C9.97563 4.87667 9.22563 4.54089 8.39718 4.54089C7.56877 4.54089 6.81877 4.87667 6.27588 5.41956" stroke="black" stroke-opacity="0.65" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 5 | <path d="M12.98 12.499L16.162 15.681" stroke="black" stroke-opacity="0.65" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 6 | </g> 7 | <defs> 8 | <clipPath id="clip0_541_8555"> 9 | <rect width="18" height="18" fill="white" transform="translate(0.521973 0.0408936)"/> 10 | </clipPath> 11 | </defs> 12 | </svg> 13 | -------------------------------------------------------------------------------- /packages/visualizer/src/init.ts: -------------------------------------------------------------------------------- 1 | // biome-ignore lint/style/useNodejsImportProtocol: <explanation> 2 | import { Buffer } from 'buffer'; 3 | 4 | // To solve the '"global is not defined" in randomBytes 5 | // https://www.perplexity.ai/search/how-to-solve-global-is-not-def-xOrpDcfOSKqz_IXtwmK4_Q 6 | window.global ||= window; 7 | window.Buffer = Buffer; 8 | 9 | let sideEffect = 0; 10 | 11 | export const setSideEffect = () => { 12 | sideEffect++; 13 | return sideEffect; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/visualizer/src/types.d.ts: -------------------------------------------------------------------------------- 1 | // 扩展 Window 接口以包含 global 和 Buffer 属性 2 | interface Window { 3 | global: typeof globalThis; 4 | Buffer: any; 5 | } 6 | 7 | // 版本变量 8 | declare const __VERSION__: string; 9 | -------------------------------------------------------------------------------- /packages/visualizer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "sourceMap": true, 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "isolatedModules": true, 10 | "jsx": "preserve", 11 | "lib": ["DOM", "ESNext"], 12 | "moduleResolution": "node", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | }, 16 | "resolveJsonModule": true, 17 | "rootDir": "src", 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "target": "ES2022", 21 | "types": ["react"] 22 | }, 23 | "exclude": ["**/node_modules"], 24 | "include": ["src", "builder"] 25 | } 26 | -------------------------------------------------------------------------------- /packages/web-integration/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Midscene.js dump files 3 | midscene_run/report 4 | midscene_run/dump 5 | 6 | -------------------------------------------------------------------------------- /packages/web-integration/README.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | Automate UI actions, extract data, and perform assertions using AI. It offers JavaScript SDK, Chrome extension, and support for scripting in YAML. 4 | 5 | See https://midscenejs.com/ for details. 6 | 7 | ## License 8 | 9 | Midscene is MIT licensed. -------------------------------------------------------------------------------- /packages/web-integration/bin/midscene-playground: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../dist/lib/midscene-playground.js'); -------------------------------------------------------------------------------- /packages/web-integration/src/bridge-mode/browser.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionBridgePageBrowserSide } from '../bridge-mode/page-browser-side'; 2 | 3 | export { ExtensionBridgePageBrowserSide }; 4 | -------------------------------------------------------------------------------- /packages/web-integration/src/bridge-mode/index.ts: -------------------------------------------------------------------------------- 1 | import { AgentOverChromeBridge } from './agent-cli-side'; 2 | 3 | export { AgentOverChromeBridge }; 4 | 5 | export { overrideAIConfig, allConfigFromEnv } from '@midscene/shared/env'; 6 | 7 | export { killRunningServer } from './io-server'; 8 | -------------------------------------------------------------------------------- /packages/web-integration/src/chrome-extension/agent.ts: -------------------------------------------------------------------------------- 1 | import { PageAgent, type PageAgentOpt } from '@/common/agent'; 2 | import type ChromeExtensionProxyPage from './page'; 3 | 4 | export class ChromeExtensionProxyPageAgent extends PageAgent { 5 | // biome-ignore lint/complexity/noUselessConstructor: <explanation> 6 | constructor(page: ChromeExtensionProxyPage, opts?: PageAgentOpt) { 7 | super(page, opts); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/web-integration/src/chrome-extension/index.ts: -------------------------------------------------------------------------------- 1 | import { ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED } from '../common/utils'; 2 | import { ChromeExtensionProxyPageAgent } from './agent'; 3 | import ChromeExtensionProxyPage from './page'; 4 | 5 | export { overrideAIConfig } from '@midscene/shared/env'; 6 | 7 | export { 8 | ChromeExtensionProxyPage, 9 | ChromeExtensionProxyPageAgent, 10 | ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/web-integration/src/index.ts: -------------------------------------------------------------------------------- 1 | export { PlaywrightAiFixture } from './playwright'; 2 | export type { PlayWrightAiFixtureType } from './playwright'; 3 | export type { 4 | WebPage, 5 | AndroidDevicePage, 6 | AndroidDeviceInputOpt, 7 | } from './common/page'; 8 | export type { AbstractPage } from './page'; 9 | 10 | export { PageAgent, type PageAgentOpt } from './common/agent'; 11 | export { PuppeteerAgent } from './puppeteer'; 12 | export { PlaywrightAgent } from './playwright'; 13 | export { StaticPageAgent } from './playground/agent'; 14 | 15 | export { ScriptPlayer, parseYamlScript } from './yaml'; 16 | export { parseContextFromWebPage } from './common/utils'; 17 | -------------------------------------------------------------------------------- /packages/web-integration/src/playground/agent.ts: -------------------------------------------------------------------------------- 1 | import { PageAgent } from '@/common/agent'; 2 | import type StaticPage from './static-page'; 3 | 4 | export class StaticPageAgent extends PageAgent { 5 | constructor(page: StaticPage) { 6 | super(page, {}); 7 | this.dryMode = true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/web-integration/src/playground/bin.ts: -------------------------------------------------------------------------------- 1 | import { StaticPage, StaticPageAgent } from './'; 2 | import PlaygroundServer from './server'; 3 | 4 | const server = new PlaygroundServer(StaticPage, StaticPageAgent); 5 | Promise.resolve() 6 | .then(() => server.launch()) 7 | .then(() => { 8 | console.log( 9 | `Midscene playground server is running on http://localhost:${server.port}`, 10 | ); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/web-integration/src/playground/index.ts: -------------------------------------------------------------------------------- 1 | import { ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED } from '../common/utils'; 2 | import { StaticPageAgent } from './agent'; 3 | import StaticPage from './static-page'; 4 | 5 | export { StaticPageAgent, StaticPage, ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED }; 6 | -------------------------------------------------------------------------------- /packages/web-integration/src/playwright/index.ts: -------------------------------------------------------------------------------- 1 | import { PageAgent, type WebPageAgentOpt } from '@/common/agent'; 2 | import type { Page as PlaywrightPage } from 'playwright'; 3 | import { WebPage as PlaywrightWebPage } from './page'; 4 | 5 | export type { PlayWrightAiFixtureType } from './ai-fixture'; 6 | export { PlaywrightAiFixture } from './ai-fixture'; 7 | export { overrideAIConfig } from '@midscene/shared/env'; 8 | export { WebPage as PlaywrightWebPage } from './page'; 9 | import { forceClosePopup } from '@/common/utils'; 10 | import { getDebug } from '@midscene/shared/logger'; 11 | 12 | const debug = getDebug('playwright:agent'); 13 | 14 | export class PlaywrightAgent extends PageAgent<PlaywrightWebPage> { 15 | constructor(page: PlaywrightPage, opts?: WebPageAgentOpt) { 16 | const webPage = new PlaywrightWebPage(page, opts); 17 | super(webPage, opts); 18 | 19 | const { forceSameTabNavigation = true } = opts ?? {}; 20 | 21 | if (forceSameTabNavigation) { 22 | forceClosePopup(page, debug); 23 | } 24 | } 25 | 26 | async waitForNetworkIdle(timeout = 1000) { 27 | await this.page.underlyingPage.waitForLoadState('networkidle', { timeout }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/web-integration/src/playwright/page.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT, 3 | DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT, 4 | } from '@midscene/shared/constants'; 5 | import type { Page as PlaywrightPageType } from 'playwright'; 6 | import type { WebPageOpt } from '../common/agent'; 7 | import { Page as BasePage } from '../puppeteer/base-page'; 8 | 9 | export class WebPage extends BasePage<'playwright', PlaywrightPageType> { 10 | waitForNavigationTimeout: number; 11 | waitForNetworkIdleTimeout: number; 12 | 13 | constructor(page: PlaywrightPageType, opts?: WebPageOpt) { 14 | super(page, 'playwright', opts); 15 | const { 16 | waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT, 17 | waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT, 18 | } = opts ?? {}; 19 | this.waitForNavigationTimeout = waitForNavigationTimeout; 20 | this.waitForNetworkIdleTimeout = waitForNetworkIdleTimeout; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/web-integration/src/puppeteer/index.ts: -------------------------------------------------------------------------------- 1 | import { PageAgent, type WebPageAgentOpt } from '@/common/agent'; 2 | import { forceClosePopup } from '@/common/utils'; 3 | import { getDebug } from '@midscene/shared/logger'; 4 | import type { Page as PuppeteerPage } from 'puppeteer'; 5 | import type { AndroidDeviceInputOpt } from '../common/page'; 6 | import { WebPage as PuppeteerWebPage } from './page'; 7 | 8 | const debug = getDebug('puppeteer:agent'); 9 | 10 | export { WebPage as PuppeteerWebPage } from './page'; 11 | export type { AndroidDeviceInputOpt }; 12 | 13 | export class PuppeteerAgent extends PageAgent<PuppeteerWebPage> { 14 | constructor(page: PuppeteerPage, opts?: WebPageAgentOpt) { 15 | const webPage = new PuppeteerWebPage(page, opts); 16 | super(webPage, opts); 17 | 18 | const { forceSameTabNavigation = true } = opts ?? {}; 19 | 20 | if (forceSameTabNavigation) { 21 | forceClosePopup(page, debug); 22 | } 23 | } 24 | } 25 | 26 | export { overrideAIConfig } from '@midscene/shared/env'; 27 | 28 | // Do NOT export this since it requires puppeteer 29 | // export { puppeteerAgentForTarget } from './agent-launcher'; 30 | -------------------------------------------------------------------------------- /packages/web-integration/src/yaml/builder.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | MidsceneYamlScript, 3 | MidsceneYamlScriptWebEnv, 4 | MidsceneYamlTask, 5 | } from '@midscene/core'; 6 | import yaml from 'js-yaml'; 7 | 8 | export function buildYaml( 9 | env: MidsceneYamlScriptWebEnv, 10 | tasks: MidsceneYamlTask[], 11 | ) { 12 | const result: MidsceneYamlScript = { 13 | target: env, 14 | tasks, 15 | }; 16 | 17 | return yaml.dump(result, { 18 | indent: 2, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/web-integration/src/yaml/index.ts: -------------------------------------------------------------------------------- 1 | export * from './player'; 2 | export * from './builder'; 3 | export * from './utils'; 4 | -------------------------------------------------------------------------------- /packages/web-integration/tests/ai/bridge/keyboard-event.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AgentOverChromeBridge, 3 | getBridgePageInCliSide, 4 | } from '@/bridge-mode/agent-cli-side'; 5 | import { describe, expect, it, vi } from 'vitest'; 6 | 7 | vi.setConfig({ 8 | testTimeout: 3 * 60 * 1000, 9 | }); 10 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 11 | 12 | const describeIf = process.env.BRIDGE_MODE ? describe : describe.skip; 13 | 14 | describeIf( 15 | 'keyboard event in bridge mode', 16 | { 17 | timeout: 3 * 60 * 10, 18 | }, 19 | () => { 20 | it('page in cli side scroll down', async () => { 21 | const agent = new AgentOverChromeBridge(); 22 | await agent.connectNewTabWithUrl('https://www.baidu.com'); 23 | 24 | await agent.aiAction('type "midscene" and hit Enter and scroll down'); 25 | // sleep 3s 26 | await sleep(3000); 27 | 28 | await agent.destroy(); 29 | }); 30 | 31 | it('page in cli side select all text', async () => { 32 | const agent = new AgentOverChromeBridge(); 33 | await agent.connectNewTabWithUrl('https://www.baidu.com'); 34 | 35 | await agent.aiAction('type "Midscene" and hit Enter and select all text'); 36 | // sleep 3s 37 | await sleep(3000); 38 | 39 | await agent.destroy(); 40 | }); 41 | }, 42 | ); 43 | -------------------------------------------------------------------------------- /packages/web-integration/tests/ai/bridge/open-new-tab.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AgentOverChromeBridge, 3 | getBridgePageInCliSide, 4 | } from '@/bridge-mode/agent-cli-side'; 5 | import { sleep } from '@midscene/core/utils'; 6 | import { describe, expect, it, test, vi } from 'vitest'; 7 | 8 | vi.setConfig({ 9 | testTimeout: 300 * 1000, 10 | }); 11 | 12 | const describeIf = process.env.BRIDGE_MODE ? describe : describe.skip; 13 | 14 | describeIf('open new tab in bridge mode', () => { 15 | it( 16 | 'open new tab', 17 | { 18 | timeout: 3 * 60 * 1000, 19 | }, 20 | async () => { 21 | const agent = new AgentOverChromeBridge(); 22 | await agent.connectNewTabWithUrl('https://www.google.com'); 23 | 24 | await agent.aiAction( 25 | 'search "midscene github" and open the first result', 26 | ); 27 | 28 | // sleep 3s 29 | await sleep(5000); 30 | 31 | await agent.aiAssert('the page is "midscene github"'); 32 | 33 | await agent.destroy(); 34 | }, 35 | ); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/web-integration/tests/ai/bridge/temp.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AgentOverChromeBridge, 3 | getBridgePageInCliSide, 4 | } from '@/bridge-mode/agent-cli-side'; 5 | import { sleep } from '@midscene/core/utils'; 6 | import { describe, expect, it, vi } from 'vitest'; 7 | 8 | vi.setConfig({ 9 | testTimeout: 300 * 1000, 10 | }); 11 | 12 | const describeIf = process.env.BRIDGE_MODE ? describe : describe.skip; 13 | 14 | describeIf('drag event', () => { 15 | it('agent in cli side, current tab', async () => { 16 | const agent = new AgentOverChromeBridge({ 17 | cacheId: 'finish-form-and-submit', 18 | }); 19 | await agent.connectCurrentTab(); 20 | 21 | await sleep(2000); 22 | 23 | await agent.aiAction( 24 | 'Use the test data to complete the form,Comply with the following restrictions: 1. The Captcha code is not required 2. No need to click the register button', 25 | ); 26 | 27 | await agent.destroy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/web-integration/tests/ai/web/playwright/ai-shop.spec.ts: -------------------------------------------------------------------------------- 1 | import { test } from './fixture'; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto('https://www.saucedemo.com/'); 5 | await page.setViewportSize({ width: 1920, height: 1080 }); 6 | }); 7 | 8 | const CACHE_TIME_OUT = process.env.MIDSCENE_CACHE; 9 | 10 | test('ai shop', async ({ 11 | ai, 12 | aiInput, 13 | aiAssert, 14 | aiQuery, 15 | aiTap, 16 | agentForPage, 17 | page, 18 | }) => { 19 | if (CACHE_TIME_OUT) { 20 | test.setTimeout(1000 * 1000); 21 | } 22 | // login 23 | const agent = await agentForPage(page); 24 | await aiInput('standard_user', 'in user name input'); 25 | await aiInput('secret_sauce', 'in password input'); 26 | await agent.aiTap('Login Button'); 27 | 28 | // check the login success 29 | await aiAssert('the page title is "Swag Labs"'); 30 | 31 | // add to cart 32 | await aiTap('"add to cart" for black t-shirt products'); 33 | 34 | await aiTap('click right top cart icon', { 35 | deepThink: true, 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/web-integration/tests/ai/web/playwright/fixture.ts: -------------------------------------------------------------------------------- 1 | import type { PlayWrightAiFixtureType } from '@/playwright/ai-fixture'; 2 | import { PlaywrightAiFixture } from '@/playwright/ai-fixture'; 3 | import { test as base } from '@playwright/test'; 4 | 5 | export const test = base.extend<PlayWrightAiFixtureType>( 6 | PlaywrightAiFixture({ 7 | waitForNetworkIdleTimeout: 10000, 8 | }), 9 | ); 10 | -------------------------------------------------------------------------------- /packages/web-integration/tests/ai/web/playwright/open-new-tab.spec.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from '@midscene/core/utils'; 2 | import { test } from './fixture'; 3 | 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('https://cn.bing.com'); 6 | }); 7 | 8 | const CACHE_TIME_OUT = process.env.MIDSCENE_CACHE; 9 | 10 | test('test open new tab', async ({ page, ai, aiAssert, aiQuery }) => { 11 | if (CACHE_TIME_OUT) { 12 | test.setTimeout(200 * 1000); 13 | } 14 | await ai( 15 | 'type "midscene github" in search box, hit Enter, sleep 5s, and open the github page in result list', 16 | ); 17 | 18 | await sleep(5000); 19 | await aiAssert('the page is "midscene github"'); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/web-integration/tests/ai/web/puppeteer/utils.ts: -------------------------------------------------------------------------------- 1 | import { PuppeteerWebPage } from '@/puppeteer'; 2 | import { launchPuppeteerPage } from '@/puppeteer/agent-launcher'; 3 | import type { Viewport } from 'puppeteer'; 4 | 5 | export async function launchPage( 6 | url: string, 7 | opt?: { 8 | viewport?: Viewport; 9 | headless?: boolean; 10 | }, 11 | ) { 12 | const { page, freeFn } = await launchPuppeteerPage( 13 | { 14 | url, 15 | viewportWidth: opt?.viewport?.width, 16 | viewportHeight: opt?.viewport?.height, 17 | viewportScale: opt?.viewport?.deviceScaleFactor, 18 | }, 19 | { 20 | headed: typeof opt?.headless === 'boolean' ? !opt.headless : false, 21 | }, 22 | ); 23 | 24 | const originPage = page; 25 | const midscenePage = new PuppeteerWebPage(originPage); 26 | 27 | return { 28 | page: midscenePage, 29 | originPage, 30 | reset: async () => { 31 | for (const fn of freeFn) { 32 | await fn.fn(); 33 | } 34 | }, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /packages/web-integration/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "../", 5 | "rootDir": "../" 6 | }, 7 | "include": [ 8 | "**/*", 9 | "../src", 10 | "playwright.config.ts", 11 | "../../android/tests/setting.test.ts", 12 | "../../android/tests/todo.test.ts" 13 | ], 14 | "exclude": ["**/node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/__snapshots__/yaml.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`yaml > basic load 1`] = ` 4 | { 5 | "target": { 6 | "url": "https://www.baidu.com", 7 | "waitForNetworkIdle": { 8 | "continueOnNetworkIdleError": true, 9 | "timeout": 1000, 10 | }, 11 | }, 12 | "tasks": [ 13 | { 14 | "flow": [ 15 | { 16 | "action": "type 'hello' in search box, hit enter", 17 | }, 18 | ], 19 | "name": "search", 20 | }, 21 | ], 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/cookie/httpbin.dev_cookies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "domain": "httpbin.dev", 4 | "hostOnly": true, 5 | "httpOnly": false, 6 | "name": "midscene_foo", 7 | "path": "/", 8 | "sameSite": "unspecified", 9 | "secure": false, 10 | "session": true, 11 | "storeId": "0", 12 | "value": "bar" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/extractor/child.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | 4 | <head> 5 | <meta charset="UTF-8"> 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 | <title>Child HTML Page</title> 8 | <style> 9 | body { 10 | font-family: Arial, sans-serif; 11 | margin: 20px; 12 | } 13 | 14 | table { 15 | border-collapse: collapse; 16 | width: 50%; 17 | margin-top: 20px; 18 | } 19 | 20 | th, 21 | td { 22 | border: 1px solid #000; 23 | padding: 8px; 24 | text-align: center; 25 | } 26 | 27 | th { 28 | background-color: #f2f2f2; 29 | } 30 | </style> 31 | </head> 32 | 33 | <body> 34 | <h1>Yet Another Data Record</h1> 35 | <h2>2021-07-15 00:00:00</h2> 36 | <h2>User Name: Guess Who</h2> 37 | <table> 38 | <thead> 39 | <tr> 40 | <th>ID</th> 41 | <th>Field 2</th> 42 | <th>Field 3</th> 43 | <th>Field 4</th> 44 | <th>Field 5</th> 45 | </tr> 46 | </thead> 47 | <tbody> 48 | <tr> 49 | <td>30S</td> 50 | <td>Kace Cervantes</td> 51 | <td>Aylin Sawyer</td> 52 | <td>Jefferson Kirby</td> 53 | <td>Skyla Jefferson</td> 54 | </tr> 55 | </tbody> 56 | </table> 57 | </body> 58 | 59 | </html> -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/extractor/scroll/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/web-integration/tests/unit-test/fixtures/extractor/scroll/input.png -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/extractor/scroll/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/web-integration/tests/unit-test/fixtures/extractor/scroll/output.png -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/web-extractor/assets/search-dark.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1737511467416" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4171" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M690.272 475.424q0-105.728-75.136-180.864t-180.864-75.136-180.864 75.136-75.136 180.864 75.136 180.864 180.864 75.136 180.864-75.136 75.136-180.864zM982.848 950.848q0 29.728-21.728 51.424t-51.424 21.728q-30.848 0-51.424-21.728l-196-195.424q-102.272 70.848-228 70.848-81.728 0-156.288-31.712t-128.576-85.728-85.728-128.576-31.712-156.288 31.712-156.288 85.728-128.576 128.576-85.728 156.288-31.712 156.288 31.712 128.576 85.728 85.728 128.576 31.712 156.288q0 125.728-70.848 228l196 196q21.152 21.152 21.152 51.424z" p-id="4172"></path></svg> -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/web-extractor/assets/search.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1737511467416" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4171" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M690.272 475.424q0-105.728-75.136-180.864t-180.864-75.136-180.864 75.136-75.136 180.864 75.136 180.864 180.864 75.136 180.864-75.136 75.136-180.864zM982.848 950.848q0 29.728-21.728 51.424t-51.424 21.728q-30.848 0-51.424-21.728l-196-195.424q-102.272 70.848-228 70.848-81.728 0-156.288-31.712t-128.576-85.728-85.728-128.576-31.712-156.288 31.712-156.288 85.728-128.576 128.576-85.728 156.288-31.712 156.288 31.712 128.576 85.728 85.728 128.576 31.712 156.288q0 125.728-70.848 228l196 196q21.152 21.152 21.152 51.424z" p-id="4172" fill="#ffffff"></path></svg> -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/web-extractor/child.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | 4 | <head> 5 | <meta charset="UTF-8"> 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 | <title>Child Page</title> 8 | </head> 9 | 10 | <body> 11 | <h1>Child Page</h1> 12 | <p>This is a child page.</p> 13 | <button>Click me</button> 14 | <div style="width: 100px; height: 200px; background-color: #ccc;"></div> 15 | <p>Something beyong 200px</p> 16 | </body> 17 | 18 | </html> -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/web-extractor/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/web-integration/tests/unit-test/fixtures/web-extractor/input.png -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/web-extractor/merge-rects.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="UTF-8" /> 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 | <title>Document</title> 7 | </head> 8 | <body> 9 | <div style="width: 100px; height: 100px"> 10 | <button style="width: 20px; height: 20px"> 11 | <span>Click Me(span)</span> 12 | </button> 13 | </div> 14 | <div style="width: 100px; height: 100px"> 15 | <button style="width: 20px; height: 20px">Click Me(text)</button> 16 | </div> 17 | </body> 18 | </html> 19 | -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/web-extractor/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/web-integration/tests/unit-test/fixtures/web-extractor/output.png -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/web-extractor/scroll/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/web-integration/tests/unit-test/fixtures/web-extractor/scroll/input.png -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/fixtures/web-extractor/scroll/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-infra-dev/midscene/3d1bfb4b169dcf6aa1f413b6a57bc892253eb443/packages/web-integration/tests/unit-test/fixtures/web-extractor/scroll/output.png -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/http-server.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'http-server' { 2 | export function createServer(options: http.ServerOptions): { 3 | server: http.Server; 4 | listen: (port: number, host: string, callback: () => void) => void; 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/yaml/__snapshots__/player.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`yaml utils > basic build && load 1`] = ` 4 | "target: 5 | url: https://bing.com 6 | waitForNetworkIdle: 7 | timeout: 1000 8 | continueOnNetworkIdleError: true 9 | tasks: 10 | - name: search 11 | flow: 12 | - aiAction: type "hello" in search box, hit enter 13 | " 14 | `; 15 | 16 | exports[`yaml utils > basic build && load 2`] = ` 17 | { 18 | "target": { 19 | "url": "https://bing.com", 20 | "waitForNetworkIdle": { 21 | "continueOnNetworkIdleError": true, 22 | "timeout": 1000, 23 | }, 24 | }, 25 | "tasks": [ 26 | { 27 | "flow": [ 28 | { 29 | "aiAction": "type "hello" in search box, hit enter", 30 | }, 31 | ], 32 | "name": "search", 33 | }, 34 | ], 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/yaml/__snapshots__/utils.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`utils > build yaml 1`] = ` 4 | "target: 5 | url: https://www.example.com 6 | tasks: [] 7 | " 8 | `; 9 | 10 | exports[`utils > parseYamlScript > aiRightClick 1`] = ` 11 | { 12 | "target": { 13 | "url": "sample_url", 14 | }, 15 | "tasks": [ 16 | { 17 | "sleep": 1000, 18 | }, 19 | { 20 | "aiTap": "sample_button", 21 | }, 22 | { 23 | "aiRightClick": "context menu trigger", 24 | }, 25 | { 26 | "aiInput": { 27 | "aiInput": "test@example.com", 28 | "locate": "email input", 29 | }, 30 | }, 31 | ], 32 | } 33 | `; 34 | 35 | exports[`utils > parseYamlScript > interpolates environment variables 1`] = ` 36 | { 37 | "target": { 38 | "url": "sample_url", 39 | }, 40 | "tasks": [ 41 | { 42 | "sleep": 1000, 43 | }, 44 | { 45 | "aiTap": "sample_button", 46 | }, 47 | { 48 | "aiInput": "sample_input", 49 | "locate": "input description", 50 | }, 51 | { 52 | "aiInput": null, 53 | "locate": "input description", 54 | }, 55 | ], 56 | } 57 | `; 58 | -------------------------------------------------------------------------------- /packages/web-integration/tests/unit-test/yaml/server_root/index.html: -------------------------------------------------------------------------------- 1 | <h1>My App</h1> 2 | <p>This is a test page</p> 3 | <div>Width:</div> 4 | <div id="j_width"></div> 5 | <div>Height:</div> 6 | <div id="j_height"></div> 7 | <script> 8 | document.getElementById('j_width').innerText = window.innerWidth; 9 | document.getElementById('j_height').innerText = window.innerHeight; 10 | </script> 11 | </body> -------------------------------------------------------------------------------- /packages/web-integration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "isolatedModules": true, 9 | "jsx": "preserve", 10 | "lib": ["DOM", "ESNext"], 11 | "moduleResolution": "node", 12 | "paths": { 13 | "@/*": ["./src/*"] 14 | }, 15 | "target": "es6", 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "module": "ESNext" 21 | }, 22 | "exclude": ["node_modules"], 23 | "include": ["src", "./vitest.config"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/web-integration/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import dotenv from 'dotenv'; 3 | import { defineConfig } from 'vitest/config'; 4 | import { version } from './package.json'; 5 | 6 | /** 7 | * Read environment variables from file. 8 | * https://github.com/motdotla/dotenv 9 | */ 10 | dotenv.config({ 11 | path: path.join(__dirname, '../../.env'), 12 | }); 13 | 14 | const aiTestType = process.env.AI_TEST_TYPE; 15 | const unitTests = ['tests/unit-test/**/*.test.ts']; 16 | const aiWebTests = [ 17 | 'tests/ai/web/**/*.test.ts', 18 | 'tests/ai/bridge/**/*.test.ts', 19 | ]; 20 | 21 | const testFiles = (() => { 22 | switch (aiTestType) { 23 | case 'web': 24 | return [...aiWebTests]; 25 | default: 26 | return unitTests; 27 | } 28 | })(); 29 | 30 | export default defineConfig({ 31 | resolve: { 32 | alias: { 33 | '@': path.resolve(__dirname, 'src'), 34 | }, 35 | }, 36 | test: { 37 | include: testFiles, 38 | testTimeout: 3 * 60 * 1000, // Global timeout set to 3 minutes 39 | dangerouslyIgnoreUnhandledErrors: !!process.env.CI, // showcase.test.ts is not stable 40 | }, 41 | define: { 42 | __VERSION__: `'${version}'`, 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - apps/* 3 | - packages/* 4 | --------------------------------------------------------------------------------