From a5abb6f5eb10901ffa9a37480660236848f9b439 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Thu, 30 Jul 2026 15:12:04 +0800 Subject: [PATCH 01/15] ci(workflow): use internal cache on self-hosted runners --- .github/actions/cache/action.yml | 40 +++++++++++++++++++ .github/actions/pnpm-cache/action.yml | 21 ++++++++++ .github/workflows/ai-unit-test.yml | 4 +- .github/workflows/ai.yml | 12 ++++-- .github/workflows/android-emulator.yml | 4 +- .github/workflows/ci.yml | 2 +- .github/workflows/headless-linux.yml | 24 ++++++++--- .github/workflows/ios-simulator.yml | 4 +- .github/workflows/lint.yml | 4 +- .github/workflows/macos-desktop.yml | 4 +- .github/workflows/pr-title.yml | 4 +- .github/workflows/release.yml | 10 +++-- .github/workflows/studio-headless-linux.yml | 4 +- .../workflows/studio-package-validation.yml | 4 +- .github/workflows/windows-desktop.yml | 4 +- 15 files changed, 122 insertions(+), 23 deletions(-) create mode 100644 .github/actions/cache/action.yml create mode 100644 .github/actions/pnpm-cache/action.yml diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/action.yml new file mode 100644 index 0000000000..2489b70adb --- /dev/null +++ b/.github/actions/cache/action.yml @@ -0,0 +1,40 @@ +name: Cache + +description: Route caches to GitHub storage or the self-hosted internal cache service + +inputs: + key: + description: An explicit key for restoring and saving the cache + required: true + path: + description: Files, directories, and wildcard patterns to cache + required: true + restore-keys: + description: Ordered prefixes used to restore stale caches + required: false + +outputs: + cache-hit: + description: Whether the primary cache key was restored + value: ${{ steps.github-cache.outputs.cache-hit == 'true' || steps.internal-cache.outputs.cache-hit == 'true' }} + +runs: + using: composite + steps: + - name: Cache with GitHub storage + id: github-cache + if: ${{ runner.environment == 'github-hosted' }} + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6 + with: + key: ${{ inputs.key }} + path: ${{ inputs.path }} + restore-keys: ${{ inputs.restore-keys }} + + - name: Cache with internal storage + id: internal-cache + if: ${{ runner.environment == 'self-hosted' }} + uses: lynx-infra/cache@d6bd70bf2182b228ff5cfa9539e5f61b0929a1ba # main + with: + key: ${{ inputs.key }} + path: ${{ inputs.path }} + restore-keys: ${{ inputs.restore-keys }} diff --git a/.github/actions/pnpm-cache/action.yml b/.github/actions/pnpm-cache/action.yml new file mode 100644 index 0000000000..90bbb57c94 --- /dev/null +++ b/.github/actions/pnpm-cache/action.yml @@ -0,0 +1,21 @@ +name: Cache pnpm store + +description: Cache the pnpm content-addressable store on the runner-appropriate backend + +runs: + using: composite + steps: + - name: Resolve pnpm store + id: pnpm-store + shell: bash + run: | + echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" + echo "version=$(pnpm --version)" >> "$GITHUB_OUTPUT" + + - name: Cache pnpm store + uses: ./.github/actions/cache + with: + path: ${{ steps.pnpm-store.outputs.path }} + key: midscene-pnpm-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.pnpm-store.outputs.version }}-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + midscene-pnpm-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.pnpm-store.outputs.version }}- diff --git a/.github/workflows/ai-unit-test.yml b/.github/workflows/ai-unit-test.yml index df7d834980..64bf019d4c 100644 --- a/.github/workflows/ai-unit-test.yml +++ b/.github/workflows/ai-unit-test.yml @@ -48,7 +48,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/ai.yml b/.github/workflows/ai.yml index a8b053dc5e..890c4c45ff 100644 --- a/.github/workflows/ai.yml +++ b/.github/workflows/ai.yml @@ -45,10 +45,12 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Cache Playwright dependencies - uses: actions/cache@6f8efc29b200d32929f49075959781ed54ec270c # v3.5.0 + uses: ./.github/actions/cache id: cache-playwright with: path: ~/.cache/ms-playwright @@ -129,10 +131,12 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Cache Playwright dependencies - uses: actions/cache@6f8efc29b200d32929f49075959781ed54ec270c # v3.5.0 + uses: ./.github/actions/cache id: cache-playwright with: path: ~/.cache/ms-playwright diff --git a/.github/workflows/android-emulator.yml b/.github/workflows/android-emulator.yml index 9699881bbd..60cc937f2e 100644 --- a/.github/workflows/android-emulator.yml +++ b/.github/workflows/android-emulator.yml @@ -74,7 +74,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7402465bfb..033cf39c7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: node-version: '24.13.0' - name: Cache Puppeteer dependencies - uses: actions/cache@6f8efc29b200d32929f49075959781ed54ec270c # v3.5.0 + uses: ./.github/actions/cache id: cache-puppeteer with: path: ~/.cache/puppeteer diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 229149810b..784994185e 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -56,7 +56,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install system dependencies run: | @@ -146,7 +148,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install system dependencies run: | @@ -221,7 +225,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install system dependencies run: | @@ -295,7 +301,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install system dependencies run: | @@ -369,7 +377,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install system dependencies run: | @@ -443,7 +453,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install system dependencies run: | diff --git a/.github/workflows/ios-simulator.yml b/.github/workflows/ios-simulator.yml index 971b0a74eb..e53f0a5c29 100644 --- a/.github/workflows/ios-simulator.yml +++ b/.github/workflows/ios-simulator.yml @@ -82,7 +82,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e86a544fca..1a23e035f0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -35,7 +35,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install Dependencies run: pnpm install --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/macos-desktop.yml b/.github/workflows/macos-desktop.yml index 0f777f20ad..9b84944dad 100644 --- a/.github/workflows/macos-desktop.yml +++ b/.github/workflows/macos-desktop.yml @@ -83,7 +83,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 2efd83998b..c797407290 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -24,7 +24,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c5983af99..b7c0a0083c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,11 +62,13 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' registry-url: 'https://registry.npmjs.org' + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache + - name: Cache Puppeteer - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: ./.github/actions/cache with: path: ~/.cache/puppeteer key: ${{ runner.os }}-puppeteer-${{ hashFiles('**/package-lock.json') }} @@ -233,7 +235,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '22.11.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Prepare macOS codesigning keychain if: ${{ matrix.platform == 'darwin' }} diff --git a/.github/workflows/studio-headless-linux.yml b/.github/workflows/studio-headless-linux.yml index 3bea7f6194..4a56eb6d46 100644 --- a/.github/workflows/studio-headless-linux.yml +++ b/.github/workflows/studio-headless-linux.yml @@ -44,7 +44,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install system dependencies run: | diff --git a/.github/workflows/studio-package-validation.yml b/.github/workflows/studio-package-validation.yml index a457c25cab..9cb0d45c23 100644 --- a/.github/workflows/studio-package-validation.yml +++ b/.github/workflows/studio-package-validation.yml @@ -52,7 +52,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '22.11.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Prepare macOS codesigning keychain if: ${{ matrix.platform == 'darwin' }} diff --git a/.github/workflows/windows-desktop.yml b/.github/workflows/windows-desktop.yml index c381501b42..8d51569a89 100644 --- a/.github/workflows/windows-desktop.yml +++ b/.github/workflows/windows-desktop.yml @@ -169,7 +169,9 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24.13.0' - cache: 'pnpm' + + - name: Cache pnpm store + uses: ./.github/actions/pnpm-cache - name: Install dependencies shell: pwsh From 2d8957149e89cda3099119f2d6dba22d3d2f1b91 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Thu, 30 Jul 2026 15:21:16 +0800 Subject: [PATCH 02/15] fix(workflow): save internal caches explicitly --- .../actions/cache/{ => restore}/action.yml | 16 +++---- .github/actions/cache/save/action.yml | 28 +++++++++++++ .../pnpm-cache/{ => restore}/action.yml | 14 +++++-- .github/actions/pnpm-cache/save/action.yml | 19 +++++++++ .github/workflows/ai-unit-test.yml | 7 +++- .github/workflows/ai.yml | 32 ++++++++++++-- .github/workflows/android-emulator.yml | 7 +++- .github/workflows/ci.yml | 9 +++- .github/workflows/headless-linux.yml | 42 ++++++++++++++++--- .github/workflows/ios-simulator.yml | 7 +++- .github/workflows/lint.yml | 7 +++- .github/workflows/macos-desktop.yml | 7 +++- .github/workflows/pr-title.yml | 7 +++- .github/workflows/release.yml | 24 +++++++++-- .github/workflows/studio-headless-linux.yml | 7 +++- .../workflows/studio-package-validation.yml | 7 +++- .github/workflows/windows-desktop.yml | 7 +++- 17 files changed, 212 insertions(+), 35 deletions(-) rename .github/actions/cache/{ => restore}/action.yml (61%) create mode 100644 .github/actions/cache/save/action.yml rename .github/actions/pnpm-cache/{ => restore}/action.yml (62%) create mode 100644 .github/actions/pnpm-cache/save/action.yml diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/restore/action.yml similarity index 61% rename from .github/actions/cache/action.yml rename to .github/actions/cache/restore/action.yml index 2489b70adb..1ab78a0f42 100644 --- a/.github/actions/cache/action.yml +++ b/.github/actions/cache/restore/action.yml @@ -1,13 +1,13 @@ -name: Cache +name: Restore cache -description: Route caches to GitHub storage or the self-hosted internal cache service +description: Restore a cache from GitHub storage or the self-hosted internal cache service inputs: key: - description: An explicit key for restoring and saving the cache + description: An explicit key for restoring the cache required: true path: - description: Files, directories, and wildcard patterns to cache + description: Files, directories, and wildcard patterns to restore required: true restore-keys: description: Ordered prefixes used to restore stale caches @@ -21,19 +21,19 @@ outputs: runs: using: composite steps: - - name: Cache with GitHub storage + - name: Restore cache from GitHub storage id: github-cache if: ${{ runner.environment == 'github-hosted' }} - uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6 + uses: actions/cache/restore@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6 with: key: ${{ inputs.key }} path: ${{ inputs.path }} restore-keys: ${{ inputs.restore-keys }} - - name: Cache with internal storage + - name: Restore cache from internal storage id: internal-cache if: ${{ runner.environment == 'self-hosted' }} - uses: lynx-infra/cache@d6bd70bf2182b228ff5cfa9539e5f61b0929a1ba # main + uses: lynx-infra/cache/restore@d6bd70bf2182b228ff5cfa9539e5f61b0929a1ba # main with: key: ${{ inputs.key }} path: ${{ inputs.path }} diff --git a/.github/actions/cache/save/action.yml b/.github/actions/cache/save/action.yml new file mode 100644 index 0000000000..a19209ec26 --- /dev/null +++ b/.github/actions/cache/save/action.yml @@ -0,0 +1,28 @@ +name: Save cache + +description: Save a cache to GitHub storage or the self-hosted internal cache service + +inputs: + key: + description: An explicit key for saving the cache + required: true + path: + description: Files, directories, and wildcard patterns to save + required: true + +runs: + using: composite + steps: + - name: Save cache to GitHub storage + if: ${{ runner.environment == 'github-hosted' }} + uses: actions/cache/save@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6 + with: + key: ${{ inputs.key }} + path: ${{ inputs.path }} + + - name: Save cache to internal storage + if: ${{ runner.environment == 'self-hosted' }} + uses: lynx-infra/cache/save@d6bd70bf2182b228ff5cfa9539e5f61b0929a1ba # main + with: + key: ${{ inputs.key }} + path: ${{ inputs.path }} diff --git a/.github/actions/pnpm-cache/action.yml b/.github/actions/pnpm-cache/restore/action.yml similarity index 62% rename from .github/actions/pnpm-cache/action.yml rename to .github/actions/pnpm-cache/restore/action.yml index 90bbb57c94..72eed2259e 100644 --- a/.github/actions/pnpm-cache/action.yml +++ b/.github/actions/pnpm-cache/restore/action.yml @@ -1,6 +1,11 @@ -name: Cache pnpm store +name: Restore pnpm store -description: Cache the pnpm content-addressable store on the runner-appropriate backend +description: Restore the pnpm content-addressable store from the runner-appropriate backend + +outputs: + cache-hit: + description: Whether the primary pnpm cache key was restored + value: ${{ steps.cache.outputs.cache-hit }} runs: using: composite @@ -12,8 +17,9 @@ runs: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" echo "version=$(pnpm --version)" >> "$GITHUB_OUTPUT" - - name: Cache pnpm store - uses: ./.github/actions/cache + - name: Restore pnpm store + id: cache + uses: ./.github/actions/cache/restore with: path: ${{ steps.pnpm-store.outputs.path }} key: midscene-pnpm-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.pnpm-store.outputs.version }}-${{ hashFiles('pnpm-lock.yaml') }} diff --git a/.github/actions/pnpm-cache/save/action.yml b/.github/actions/pnpm-cache/save/action.yml new file mode 100644 index 0000000000..9dbf0058a3 --- /dev/null +++ b/.github/actions/pnpm-cache/save/action.yml @@ -0,0 +1,19 @@ +name: Save pnpm store + +description: Save the pnpm content-addressable store to the runner-appropriate backend + +runs: + using: composite + steps: + - name: Resolve pnpm store + id: pnpm-store + shell: bash + run: | + echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" + echo "version=$(pnpm --version)" >> "$GITHUB_OUTPUT" + + - name: Save pnpm store + uses: ./.github/actions/cache/save + with: + path: ${{ steps.pnpm-store.outputs.path }} + key: midscene-pnpm-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.pnpm-store.outputs.version }}-${{ hashFiles('pnpm-lock.yaml') }} diff --git a/.github/workflows/ai-unit-test.yml b/.github/workflows/ai-unit-test.yml index 64bf019d4c..f11d83e0ed 100644 --- a/.github/workflows/ai-unit-test.yml +++ b/.github/workflows/ai-unit-test.yml @@ -50,11 +50,16 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Install puppeteer dependencies run: | cd packages/web-integration diff --git a/.github/workflows/ai.yml b/.github/workflows/ai.yml index 890c4c45ff..2aacfee530 100644 --- a/.github/workflows/ai.yml +++ b/.github/workflows/ai.yml @@ -47,10 +47,11 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Cache Playwright dependencies - uses: ./.github/actions/cache + uses: ./.github/actions/cache/restore id: cache-playwright with: path: ~/.cache/ms-playwright @@ -61,6 +62,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Install Playwright dependencies run: | cd packages/web-integration @@ -70,6 +75,13 @@ jobs: npx playwright install-deps && npx playwright install fi + - name: Save Playwright dependencies + if: steps.cache-playwright.outputs.cache-hit != 'true' + uses: ./.github/actions/cache/save + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} + - name: Install puppeteer dependencies run: | cd packages/web-integration @@ -133,10 +145,11 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Cache Playwright dependencies - uses: ./.github/actions/cache + uses: ./.github/actions/cache/restore id: cache-playwright with: path: ~/.cache/ms-playwright @@ -147,6 +160,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Install Playwright dependencies run: | cd packages/web-integration @@ -156,6 +173,13 @@ jobs: npx playwright install-deps && npx playwright install fi + - name: Save Playwright dependencies + if: steps.cache-playwright.outputs.cache-hit != 'true' + uses: ./.github/actions/cache/save + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} + - name: Install puppeteer dependencies run: | cd packages/web-integration diff --git a/.github/workflows/android-emulator.yml b/.github/workflows/android-emulator.yml index 60cc937f2e..4bc039dad3 100644 --- a/.github/workflows/android-emulator.yml +++ b/.github/workflows/android-emulator.yml @@ -76,11 +76,16 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build report and Android packages id: build continue-on-error: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 033cf39c7c..da6516fcd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: node-version: '24.13.0' - name: Cache Puppeteer dependencies - uses: ./.github/actions/cache + uses: ./.github/actions/cache/restore id: cache-puppeteer with: path: ~/.cache/puppeteer @@ -54,6 +54,13 @@ jobs: cd packages/web-integration npx puppeteer browsers install chrome + - name: Save Puppeteer cache + if: steps.cache-puppeteer.outputs.cache-hit != 'true' + uses: ./.github/actions/cache/save + with: + path: ~/.cache/puppeteer + key: ${{ runner.os }}-puppeteer-${{ hashFiles('pnpm-lock.yaml') }} + - name: Check tsconfig references run: pnpm run check:references diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 784994185e..3b4cf870d2 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -58,7 +58,8 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install system dependencies run: | @@ -84,6 +85,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build project run: pnpm run build:skip-cache @@ -150,7 +155,8 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install system dependencies run: | @@ -176,6 +182,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build project run: pnpm run build:skip-cache @@ -227,7 +237,8 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install system dependencies run: | @@ -252,6 +263,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build project run: pnpm run build:skip-cache @@ -303,7 +318,8 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install system dependencies run: | @@ -328,6 +344,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build project run: pnpm run build:skip-cache @@ -379,7 +399,8 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install system dependencies run: | @@ -404,6 +425,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build project run: pnpm run build:skip-cache @@ -455,7 +480,8 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install system dependencies run: | @@ -480,6 +506,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build project run: pnpm run build:skip-cache diff --git a/.github/workflows/ios-simulator.yml b/.github/workflows/ios-simulator.yml index e53f0a5c29..46ffef7e6f 100644 --- a/.github/workflows/ios-simulator.yml +++ b/.github/workflows/ios-simulator.yml @@ -84,11 +84,16 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build report and iOS packages id: build continue-on-error: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1a23e035f0..9460e261ed 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -37,11 +37,16 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install Dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Check Dependency Version run: pnpm run check-dependency-version diff --git a/.github/workflows/macos-desktop.yml b/.github/workflows/macos-desktop.yml index 9b84944dad..86c4070a9e 100644 --- a/.github/workflows/macos-desktop.yml +++ b/.github/workflows/macos-desktop.yml @@ -85,11 +85,16 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build report and computer packages id: build continue-on-error: true diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index c797407290..8c9a3c1bcb 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -26,11 +26,16 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Validate PR title env: PR_TITLE: ${{ github.event.pull_request.title }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7c0a0083c..fe729104db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,10 +65,12 @@ jobs: registry-url: 'https://registry.npmjs.org' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Cache Puppeteer - uses: ./.github/actions/cache + id: cache-puppeteer + uses: ./.github/actions/cache/restore with: path: ~/.cache/puppeteer key: ${{ runner.os }}-puppeteer-${{ hashFiles('**/package-lock.json') }} @@ -78,9 +80,20 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Install Puppeteer browser run: cd packages/web-integration && npx puppeteer browsers install chrome + - name: Save Puppeteer cache + if: steps.cache-puppeteer.outputs.cache-hit != 'true' + uses: ./.github/actions/cache/save + with: + path: ~/.cache/puppeteer + key: ${{ runner.os }}-puppeteer-${{ hashFiles('**/package-lock.json') }} + - name: Install @midscene/computer native build deps (linux/x64) # The prepublishOnly hook of @midscene/computer compiles # bin/linux/rdp-helper on the publishing runner so the linux helper @@ -237,7 +250,8 @@ jobs: node-version: '22.11.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Prepare macOS codesigning keychain if: ${{ matrix.platform == 'darwin' }} @@ -294,6 +308,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Install @midscene/computer native build deps (macOS) if: ${{ matrix.platform == 'darwin' }} run: brew install cmake pkg-config freerdp diff --git a/.github/workflows/studio-headless-linux.yml b/.github/workflows/studio-headless-linux.yml index 4a56eb6d46..9c52f35600 100644 --- a/.github/workflows/studio-headless-linux.yml +++ b/.github/workflows/studio-headless-linux.yml @@ -46,7 +46,8 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install system dependencies run: | @@ -79,6 +80,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Install Puppeteer browser binary # `pnpm install` normally triggers puppeteer's `postinstall` to fetch # the Chrome binary into `~/.cache/puppeteer`, but the added diff --git a/.github/workflows/studio-package-validation.yml b/.github/workflows/studio-package-validation.yml index 9cb0d45c23..987524a4e6 100644 --- a/.github/workflows/studio-package-validation.yml +++ b/.github/workflows/studio-package-validation.yml @@ -54,7 +54,8 @@ jobs: node-version: '22.11.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Prepare macOS codesigning keychain if: ${{ matrix.platform == 'darwin' }} @@ -111,6 +112,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Install @midscene/computer native build deps (macOS) if: ${{ matrix.platform == 'darwin' }} run: brew install cmake pkg-config freerdp diff --git a/.github/workflows/windows-desktop.yml b/.github/workflows/windows-desktop.yml index 8d51569a89..d32c88bba6 100644 --- a/.github/workflows/windows-desktop.yml +++ b/.github/workflows/windows-desktop.yml @@ -171,12 +171,17 @@ jobs: node-version: '24.13.0' - name: Cache pnpm store - uses: ./.github/actions/pnpm-cache + id: pnpm-cache + uses: ./.github/actions/pnpm-cache/restore - name: Install dependencies shell: pwsh run: pnpm install --frozen-lockfile --ignore-scripts + - name: Save pnpm store + if: steps.pnpm-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/pnpm-cache/save + - name: Build report and computer packages id: build continue-on-error: true From 8dd37e006792425f16274801a7fa0d59d8a8a974 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Thu, 30 Jul 2026 16:58:03 +0800 Subject: [PATCH 03/15] fix(workflow): align bridge AI timeout budgets --- .github/workflows/headless-linux.yml | 2 +- packages/computer/tests/ai/chrome-extension-bridge.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 3b4cf870d2..3d49141c4d 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -272,7 +272,7 @@ jobs: - name: Run Bridge mode start/stop test run: AI_TEST_TYPE=computer npx nx test @midscene/computer -- tests/ai/chrome-extension-bridge.test.ts - timeout-minutes: 25 + timeout-minutes: 40 id: test-bridge continue-on-error: true diff --git a/packages/computer/tests/ai/chrome-extension-bridge.test.ts b/packages/computer/tests/ai/chrome-extension-bridge.test.ts index 9c24979c19..b539560060 100644 --- a/packages/computer/tests/ai/chrome-extension-bridge.test.ts +++ b/packages/computer/tests/ai/chrome-extension-bridge.test.ts @@ -207,7 +207,7 @@ describe('chrome extension bridge mode start/stop (#2119)', () => { ); } }, - { timeout: 12 * 60 * 1000, retry: 0 }, + { timeout: 20 * 60 * 1000, retry: 0 }, ); // ── Test: bridge connects to a real server ──────────────────────────── From 47a289a8aaaf0bb457b542b84d244202f46962a0 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Thu, 30 Jul 2026 17:14:26 +0800 Subject: [PATCH 04/15] perf(workflow): use shallow checkout in CI jobs --- .github/workflows/ai-unit-test.yml | 1 - .github/workflows/ai.yml | 2 -- .github/workflows/ci.yml | 2 -- .github/workflows/lint.yml | 5 ----- 4 files changed, 10 deletions(-) diff --git a/.github/workflows/ai-unit-test.yml b/.github/workflows/ai-unit-test.yml index f11d83e0ed..d34e91ad4b 100644 --- a/.github/workflows/ai-unit-test.yml +++ b/.github/workflows/ai-unit-test.yml @@ -36,7 +36,6 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: - fetch-depth: 0 ref: ${{ github.event.inputs.branch || github.ref }} - name: Setup pnpm diff --git a/.github/workflows/ai.yml b/.github/workflows/ai.yml index 2aacfee530..7dbeefa252 100644 --- a/.github/workflows/ai.yml +++ b/.github/workflows/ai.yml @@ -33,7 +33,6 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: - fetch-depth: 0 ref: ${{ github.event.inputs.branch || github.ref }} - name: Setup pnpm @@ -131,7 +130,6 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: - fetch-depth: 0 ref: ${{ github.event.inputs.branch || github.ref }} - name: Setup pnpm diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da6516fcd7..15a8b695fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,8 +24,6 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - fetch-depth: 0 - name: Setup pnpm uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9460e261ed..f09d0247c5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,11 +20,6 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - fetch-depth: 0 - - - name: Fetch all branches - run: git fetch --all - name: Setup pnpm uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 From 5f43070ab94f42b0d87c10ee84e4084fbf086981 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Thu, 30 Jul 2026 17:22:05 +0800 Subject: [PATCH 05/15] perf(workflow): cancel stale self-hosted PR runs --- .github/workflows/ai-unit-test.yml | 4 ++++ .github/workflows/ai.yml | 4 ++++ .github/workflows/headless-linux.yml | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/.github/workflows/ai-unit-test.yml b/.github/workflows/ai-unit-test.yml index d34e91ad4b..dd72a1077d 100644 --- a/.github/workflows/ai-unit-test.yml +++ b/.github/workflows/ai-unit-test.yml @@ -15,6 +15,10 @@ on: permissions: contents: read +concurrency: + group: ai-unit-test-${{ github.event.pull_request.number || inputs.branch || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: main: runs-on: ${{ fromJSON(vars.LINUX_SELF_HOSTED_RUNNER_LABELS || '"ubuntu-22.04"') }} diff --git a/.github/workflows/ai.yml b/.github/workflows/ai.yml index 7dbeefa252..66880ecadd 100644 --- a/.github/workflows/ai.yml +++ b/.github/workflows/ai.yml @@ -15,6 +15,10 @@ on: permissions: contents: read +concurrency: + group: ai-playwright-e2e-${{ github.event.pull_request.number || inputs.branch || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + env: MIDSCENE_MODEL_API_KEY: ${{ secrets.MIDSCENE_MODEL_API_KEY }} MIDSCENE_MODEL_BASE_URL: ${{ vars.MIDSCENE_MODEL_BASE_URL }} diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 3d49141c4d..1922586fd2 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -25,6 +25,10 @@ on: permissions: contents: read +concurrency: + group: headless-linux-${{ github.event.pull_request.number || inputs.branch || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + env: ELECTRON_SKIP_BINARY_DOWNLOAD: "1" From b705ac52b3a6ddca14de4ca6149a3589f3def8ee Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Thu, 30 Jul 2026 18:31:35 +0800 Subject: [PATCH 06/15] fix(workflow): skip duplicate headless prepare builds --- .github/workflows/headless-linux.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 1922586fd2..1d8d9878d2 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -87,7 +87,7 @@ jobs: sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile --ignore-scripts - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' @@ -184,7 +184,7 @@ jobs: sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile --ignore-scripts - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' @@ -265,7 +265,7 @@ jobs: sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile --ignore-scripts - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' @@ -346,7 +346,7 @@ jobs: sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile --ignore-scripts - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' @@ -427,7 +427,7 @@ jobs: sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile --ignore-scripts - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' @@ -508,7 +508,7 @@ jobs: sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile --ignore-scripts - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' From a975fbb05e2e82f1c9725830e190ff4cd150578e Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Thu, 30 Jul 2026 18:44:22 +0800 Subject: [PATCH 07/15] fix(workflow): install headless browser explicitly --- .github/workflows/headless-linux.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 1d8d9878d2..559e9eb85e 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -186,6 +186,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Install Chrome for Testing + run: pnpm exec puppeteer browsers install chrome + - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save @@ -267,6 +270,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Install Chrome for Testing + run: pnpm exec puppeteer browsers install chrome + - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save @@ -348,6 +354,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Install Chrome for Testing + run: pnpm exec puppeteer browsers install chrome + - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save @@ -429,6 +438,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Install Chrome for Testing + run: pnpm exec puppeteer browsers install chrome + - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save @@ -510,6 +522,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts + - name: Install Chrome for Testing + run: pnpm exec puppeteer browsers install chrome + - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save From 93814a1b1992d7246c73168cfd2dc54bf358d352 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Thu, 30 Jul 2026 18:55:57 +0800 Subject: [PATCH 08/15] fix(workflow): run Puppeteer CLI from its package --- .github/workflows/headless-linux.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 559e9eb85e..7bc13152fd 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -187,7 +187,7 @@ jobs: run: pnpm install --frozen-lockfile --ignore-scripts - name: Install Chrome for Testing - run: pnpm exec puppeteer browsers install chrome + run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' @@ -271,7 +271,7 @@ jobs: run: pnpm install --frozen-lockfile --ignore-scripts - name: Install Chrome for Testing - run: pnpm exec puppeteer browsers install chrome + run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' @@ -355,7 +355,7 @@ jobs: run: pnpm install --frozen-lockfile --ignore-scripts - name: Install Chrome for Testing - run: pnpm exec puppeteer browsers install chrome + run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' @@ -439,7 +439,7 @@ jobs: run: pnpm install --frozen-lockfile --ignore-scripts - name: Install Chrome for Testing - run: pnpm exec puppeteer browsers install chrome + run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' @@ -523,7 +523,7 @@ jobs: run: pnpm install --frozen-lockfile --ignore-scripts - name: Install Chrome for Testing - run: pnpm exec puppeteer browsers install chrome + run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' From bde14f2b2431ad4591ad7ddcfe2a7cb66cebc737 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Fri, 31 Jul 2026 10:34:53 +0800 Subject: [PATCH 09/15] perf(workflow): reduce self-hosted headless setup --- .../actions/setup-headless-browser/action.yml | 80 +++++++ .github/actions/setup-node-pnpm/action.yml | 57 +++++ .../setup-puppeteer-browser/action.yml | 73 ++++++ .github/workflows/ai-unit-test.yml | 17 +- .github/workflows/ai.yml | 34 +-- .github/workflows/headless-linux.yml | 223 +++--------------- .../ai/chrome-extension-playground.test.ts | 17 ++ .../tests/ai/chrome-extension.test.ts | 136 +---------- 8 files changed, 274 insertions(+), 363 deletions(-) create mode 100644 .github/actions/setup-headless-browser/action.yml create mode 100644 .github/actions/setup-node-pnpm/action.yml create mode 100644 .github/actions/setup-puppeteer-browser/action.yml diff --git a/.github/actions/setup-headless-browser/action.yml b/.github/actions/setup-headless-browser/action.yml new file mode 100644 index 0000000000..e425e7a70c --- /dev/null +++ b/.github/actions/setup-headless-browser/action.yml @@ -0,0 +1,80 @@ +name: Setup headless browser + +description: Install missing Linux desktop libraries and restore a Puppeteer Chrome binary + +inputs: + package-directory: + description: Workspace package that provides the Puppeteer CLI + required: false + default: packages/web-integration + +outputs: + browser-path: + description: Absolute path to the Chrome for Testing executable + value: ${{ steps.browser.outputs.browser-path }} + browser-cache-hit: + description: Whether the Puppeteer browser cache was restored + value: ${{ steps.browser.outputs.browser-cache-hit }} + +runs: + using: composite + steps: + - name: Install missing system dependencies + shell: bash + run: | + set -euo pipefail + + packages=( + xvfb + x11-xserver-utils + imagemagick + fonts-liberation + libasound2 + libatk-bridge2.0-0 + libdrm2 + libgbm1 + libgtk-3-0 + libnss3 + libpango-1.0-0 + libvulkan1 + libx11-6 + libx11-xcb1 + libxcomposite1 + libxdamage1 + libxfixes3 + libxinerama1 + libxkbcommon-x11-0 + libxkbcommon0 + libxrandr2 + libxshmfence1 + libxss1 + libxtst6 + xdg-utils + ) + missing=() + + for package in "${packages[@]}"; do + status="$(dpkg-query -W -f='${Status}' "$package" 2>/dev/null || true)" + if [ "$status" != "install ok installed" ]; then + missing+=("$package") + fi + done + + if [ "${#missing[@]}" -eq 0 ]; then + echo "All headless browser system dependencies are preinstalled" + exit 0 + fi + + echo "Installing missing packages: ${missing[*]}" + sudo apt-get update + sudo env DEBIAN_FRONTEND=noninteractive apt-get install \ + --yes \ + --no-install-recommends \ + "${missing[@]}" + + - name: Setup Puppeteer browser + id: browser + uses: ./.github/actions/setup-puppeteer-browser + with: + package-directory: ${{ inputs.package-directory }} + expose-as-chromium: "true" diff --git a/.github/actions/setup-node-pnpm/action.yml b/.github/actions/setup-node-pnpm/action.yml new file mode 100644 index 0000000000..f9b9f3d84d --- /dev/null +++ b/.github/actions/setup-node-pnpm/action.yml @@ -0,0 +1,57 @@ +name: Setup Node.js and pnpm + +description: Restore the Node.js toolcache on self-hosted runners, then set up Node.js and pnpm + +inputs: + node-version: + description: Node.js version to install + required: false + default: 24.13.0 + pnpm-version: + description: pnpm version to install + required: false + default: 9.3.0 + +outputs: + node-cache-hit: + description: Whether the self-hosted Node.js toolcache was restored + value: ${{ steps.node-cache.outputs.cache-hit }} + +runs: + using: composite + steps: + - name: Resolve Node.js toolcache + id: node-toolcache + if: ${{ runner.environment == 'self-hosted' }} + shell: bash + run: | + if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then + echo "::error::RUNNER_TOOL_CACHE is not configured" + exit 1 + fi + echo "path=$RUNNER_TOOL_CACHE/node/${{ inputs.node-version }}" >> "$GITHUB_OUTPUT" + + - name: Restore Node.js toolcache + id: node-cache + if: ${{ runner.environment == 'self-hosted' }} + uses: ./.github/actions/cache/restore + with: + path: ${{ steps.node-toolcache.outputs.path }} + key: midscene-node-toolcache-v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.node-version }} + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ inputs.node-version }} + + - name: Save Node.js toolcache + if: ${{ runner.environment == 'self-hosted' && steps.node-cache.outputs.cache-hit != 'true' }} + uses: ./.github/actions/cache/save + with: + path: ${{ steps.node-toolcache.outputs.path }} + key: midscene-node-toolcache-v1-${{ runner.os }}-${{ runner.arch }}-${{ inputs.node-version }} + + - name: Setup pnpm + uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 + with: + version: ${{ inputs.pnpm-version }} diff --git a/.github/actions/setup-puppeteer-browser/action.yml b/.github/actions/setup-puppeteer-browser/action.yml new file mode 100644 index 0000000000..69899c2151 --- /dev/null +++ b/.github/actions/setup-puppeteer-browser/action.yml @@ -0,0 +1,73 @@ +name: Setup Puppeteer browser + +description: Restore and install the Puppeteer Chrome for Testing binary + +inputs: + package-directory: + description: Workspace package that provides the Puppeteer CLI + required: false + default: packages/web-integration + expose-as-chromium: + description: Add a chromium alias for tests that discover browsers from PATH + required: false + default: "false" + +outputs: + browser-path: + description: Absolute path to the Chrome for Testing executable + value: ${{ steps.browser.outputs.path }} + browser-cache-hit: + description: Whether the Puppeteer browser cache was restored + value: ${{ steps.browser-cache.outputs.cache-hit }} + +runs: + using: composite + steps: + - name: Restore Puppeteer browser + id: browser-cache + uses: ./.github/actions/cache/restore + with: + path: ~/.cache/puppeteer + key: midscene-puppeteer-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + midscene-puppeteer-v1-${{ runner.os }}-${{ runner.arch }}- + + - name: Install Chrome for Testing + shell: bash + run: pnpm --dir "${{ inputs.package-directory }}" exec puppeteer browsers install chrome + + - name: Save Puppeteer browser + if: ${{ steps.browser-cache.outputs.cache-hit != 'true' }} + uses: ./.github/actions/cache/save + with: + path: ~/.cache/puppeteer + key: midscene-puppeteer-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Resolve Chrome for Testing + id: browser + shell: bash + run: | + set -euo pipefail + + browser_path="$( + find "$HOME/.cache/puppeteer/chrome" \ + -type f \ + -path '*/chrome-linux64/chrome' \ + -perm -u+x \ + -print \ + -quit + )" + if [ -z "$browser_path" ]; then + echo "::error::Chrome for Testing was not found in the Puppeteer cache" + exit 1 + fi + + if [ "${{ inputs.expose-as-chromium }}" = "true" ]; then + bin_dir="$RUNNER_TEMP/midscene-browser-bin" + mkdir -p "$bin_dir" + ln -sfn "$browser_path" "$bin_dir/chromium" + echo "$bin_dir" >> "$GITHUB_PATH" + fi + + echo "path=$browser_path" >> "$GITHUB_OUTPUT" + "$browser_path" --version diff --git a/.github/workflows/ai-unit-test.yml b/.github/workflows/ai-unit-test.yml index dd72a1077d..2e45966d64 100644 --- a/.github/workflows/ai-unit-test.yml +++ b/.github/workflows/ai-unit-test.yml @@ -42,15 +42,8 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache @@ -63,10 +56,8 @@ jobs: if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save - - name: Install puppeteer dependencies - run: | - cd packages/web-integration - npx puppeteer browsers install chrome + - name: Setup Puppeteer browser + uses: ./.github/actions/setup-puppeteer-browser - name: Build project run: pnpm run build diff --git a/.github/workflows/ai.yml b/.github/workflows/ai.yml index 66880ecadd..bcd80eb942 100644 --- a/.github/workflows/ai.yml +++ b/.github/workflows/ai.yml @@ -39,15 +39,8 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache @@ -85,10 +78,8 @@ jobs: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} - - name: Install puppeteer dependencies - run: | - cd packages/web-integration - npx puppeteer browsers install chrome + - name: Setup Puppeteer browser + uses: ./.github/actions/setup-puppeteer-browser - name: Build project run: pnpm run build @@ -136,15 +127,8 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache @@ -182,10 +166,8 @@ jobs: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} - - name: Install puppeteer dependencies - run: | - cd packages/web-integration - npx puppeteer browsers install chrome + - name: Setup Puppeteer browser + uses: ./.github/actions/setup-puppeteer-browser - name: Build project run: pnpm run build diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 7bc13152fd..47df6cb1b3 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -51,41 +51,13 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache uses: ./.github/actions/pnpm-cache/restore - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - xvfb \ - x11-xserver-utils \ - imagemagick \ - libxtst6 \ - libxinerama1 \ - libx11-6 \ - libxkbcommon-x11-0 \ - libpng16-16 \ - libnss3 \ - libatk-bridge2.0-0 \ - libdrm2 \ - libgbm1 \ - libasound2 - # Install Google Chrome (not snap chromium which can't access Xvfb) - wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts @@ -93,6 +65,9 @@ jobs: if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save + - name: Setup headless browser + uses: ./.github/actions/setup-headless-browser + - name: Build project run: pnpm run build:skip-cache @@ -105,7 +80,7 @@ jobs: echo "--- import (imagemagick) ---" which import && echo "import: OK" echo "--- Browser ---" - google-chrome-stable --version || echo "Chrome not found" + chromium --version || echo "Chrome not found" echo "--- DISPLAY ---" echo "DISPLAY=${DISPLAY:-(not set)}" @@ -148,51 +123,23 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache uses: ./.github/actions/pnpm-cache/restore - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - xvfb \ - x11-xserver-utils \ - imagemagick \ - libxtst6 \ - libxinerama1 \ - libx11-6 \ - libxkbcommon-x11-0 \ - libpng16-16 \ - libnss3 \ - libatk-bridge2.0-0 \ - libdrm2 \ - libgbm1 \ - libasound2 - # Install Google Chrome (not snap chromium which can't access Xvfb) - wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts - - name: Install Chrome for Testing - run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save + - name: Setup headless browser + uses: ./.github/actions/setup-headless-browser + - name: Build project run: pnpm run build:skip-cache @@ -233,50 +180,23 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache uses: ./.github/actions/pnpm-cache/restore - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - xvfb \ - x11-xserver-utils \ - imagemagick \ - libxtst6 \ - libxinerama1 \ - libx11-6 \ - libxkbcommon-x11-0 \ - libpng16-16 \ - libnss3 \ - libatk-bridge2.0-0 \ - libdrm2 \ - libgbm1 \ - libasound2 - wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts - - name: Install Chrome for Testing - run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save + - name: Setup headless browser + uses: ./.github/actions/setup-headless-browser + - name: Build project run: pnpm run build:skip-cache @@ -317,50 +237,23 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache uses: ./.github/actions/pnpm-cache/restore - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - xvfb \ - x11-xserver-utils \ - imagemagick \ - libxtst6 \ - libxinerama1 \ - libx11-6 \ - libxkbcommon-x11-0 \ - libpng16-16 \ - libnss3 \ - libatk-bridge2.0-0 \ - libdrm2 \ - libgbm1 \ - libasound2 - wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts - - name: Install Chrome for Testing - run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save + - name: Setup headless browser + uses: ./.github/actions/setup-headless-browser + - name: Build project run: pnpm run build:skip-cache @@ -401,50 +294,23 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache uses: ./.github/actions/pnpm-cache/restore - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - xvfb \ - x11-xserver-utils \ - imagemagick \ - libxtst6 \ - libxinerama1 \ - libx11-6 \ - libxkbcommon-x11-0 \ - libpng16-16 \ - libnss3 \ - libatk-bridge2.0-0 \ - libdrm2 \ - libgbm1 \ - libasound2 - wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts - - name: Install Chrome for Testing - run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save + - name: Setup headless browser + uses: ./.github/actions/setup-headless-browser + - name: Build project run: pnpm run build:skip-cache @@ -485,50 +351,23 @@ jobs: with: ref: ${{ github.event.inputs.branch || github.ref }} - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache uses: ./.github/actions/pnpm-cache/restore - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - xvfb \ - x11-xserver-utils \ - imagemagick \ - libxtst6 \ - libxinerama1 \ - libx11-6 \ - libxkbcommon-x11-0 \ - libpng16-16 \ - libnss3 \ - libatk-bridge2.0-0 \ - libdrm2 \ - libgbm1 \ - libasound2 - wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y - - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts - - name: Install Chrome for Testing - run: pnpm --dir packages/web-integration exec puppeteer browsers install chrome - - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save + - name: Setup headless browser + uses: ./.github/actions/setup-headless-browser + - name: Build project run: pnpm run build:skip-cache diff --git a/packages/computer/tests/ai/chrome-extension-playground.test.ts b/packages/computer/tests/ai/chrome-extension-playground.test.ts index 03f5e5b90c..608bea7ff8 100644 --- a/packages/computer/tests/ai/chrome-extension-playground.test.ts +++ b/packages/computer/tests/ai/chrome-extension-playground.test.ts @@ -74,6 +74,23 @@ describe('chrome extension playground advanced tests', () => { } }); + it('action type switching changes the composer placeholder', async () => { + await agent.aiAct(`Click the "Query" button in ${SIDE_PANEL}`); + await sleep(500); + await agent.aiAssert( + `${SIDE_PANEL} shows an input area with placeholder text containing "query"`, + ); + + await agent.aiAct(`Click the "Assert" button in ${SIDE_PANEL}`); + await sleep(500); + await agent.aiAssert( + `${SIDE_PANEL} shows an input area with placeholder text containing "assert"`, + ); + + await agent.aiAct(`Click the "Action" button in ${SIDE_PANEL}`); + await sleep(500); + }); + it('aiQuery: extract page title and verify result', async () => { await agent.aiAct(`Click the "aiQuery" button in ${SIDE_PANEL}`); await sleep(500); diff --git a/packages/computer/tests/ai/chrome-extension.test.ts b/packages/computer/tests/ai/chrome-extension.test.ts index 7f0e82b4f6..b6bb25c4f1 100644 --- a/packages/computer/tests/ai/chrome-extension.test.ts +++ b/packages/computer/tests/ai/chrome-extension.test.ts @@ -1,3 +1,7 @@ +/** + * Basic extension launch smoke test. Playground, Bridge, Recorder, and + * Settings behavior lives in their dedicated parallel suites. + */ import path from 'node:path'; import { sleep } from '@midscene/core/utils'; import { beforeAll, describe, it, vi } from 'vitest'; @@ -12,9 +16,6 @@ import { vi.setConfig({ testTimeout: 360 * 1000 }); -const SIDE_PANEL = - 'the Midscene side panel on the right side of the browser window'; - describe('chrome extension smoke test', () => { let agent: ComputerAgent; let extId: string; @@ -36,8 +37,6 @@ describe('chrome extension smoke test', () => { console.log('Extension ID:', extId); }); - // ── 1. Side Panel Launch ────────────────────────────────────────────── - it('open side panel via extension icon', async () => { await agent.aiAct( 'Click the puzzle piece icon (Extensions button) in the top-right area of the Chrome toolbar', @@ -59,131 +58,4 @@ describe('chrome extension smoke test', () => { await sleep(3000); } }); - - // ── 2. Playground UI Elements ─────────────────────────────────────── - - it('playground: UI elements are rendered correctly', async () => { - await agent.aiAssert( - `${SIDE_PANEL} shows: (1) action type buttons like "Tap" and "Query", (2) a text input area with a "Run" button, (3) a gear/settings icon`, - ); - }); - - // ── 3. Action Type Switching ────────────────────────────────────────── - - it('playground: action type switching changes placeholder', async () => { - await agent.aiAct(`Click the "Query" button in ${SIDE_PANEL}`); - await sleep(500); - await agent.aiAssert( - `${SIDE_PANEL} shows an input area with placeholder text containing "query"`, - ); - - await agent.aiAct(`Click the "Assert" button in ${SIDE_PANEL}`); - await sleep(500); - await agent.aiAssert( - `${SIDE_PANEL} shows an input area with placeholder text containing "assert"`, - ); - - // Switch back to Tap mode for the next test. - await agent.aiAct(`Click the "Tap" button in ${SIDE_PANEL}`); - await sleep(500); - }); - - // ── 4. Run aiAct Task ───────────────────────────────────────────────── - - it('playground: run aiAct to add a todo item', async () => { - await agent.aiAct(`Click the "Action" button in ${SIDE_PANEL}`); - await sleep(500); - - await agent.aiAct( - `In ${SIDE_PANEL}, click the text input area and type: Enter "Learn JS today" in the task box, then press Enter`, - ); - await sleep(500); - - await agent.aiAct(`Click the "Run" button in ${SIDE_PANEL}`); - await sleep(20000); - - await agent.aiAssert( - 'The TodoMVC page on the left shows a todo item containing "Learn JS today" OR the side panel shows execution progress/result', - ); - }); - - // ── 5. Mode Switching ────────────────────────────────────────────────── - - it( - 'mode switching: switch to Bridge and back', - { timeout: 12 * 60 * 1000, retry: 0 }, - async () => { - // Switch to Bridge Mode with retry - the dropdown menu can be flaky in headless - const switchToBridge = async () => { - await agent.aiAct( - `In ${SIDE_PANEL}, find and click the hamburger menu icon (three horizontal lines "≡") at the top-left corner. It should open a dropdown menu.`, - ); - await sleep(2000); - await agent.aiAct( - 'In the dropdown menu that just appeared, click the menu item labeled "Bridge Mode" which has an API icon next to it', - ); - await sleep(3000); - }; - - // First attempt - await switchToBridge(); - - // Verify or retry once if needed - try { - await agent.aiAssert( - `${SIDE_PANEL} shows Bridge mode UI with text containing "Listening" or "Disconnected" or "Bridge" connection status`, - ); - } catch { - // Retry: the dropdown click may not have registered - console.log('Bridge mode switch failed, retrying...'); - await switchToBridge(); - await agent.aiAssert( - `${SIDE_PANEL} shows Bridge mode UI with text containing "Listening" or "Disconnected" or "Bridge" connection status`, - ); - } - - // Switch back to Playground - await agent.aiAct( - `In ${SIDE_PANEL}, find and click the hamburger menu icon (three horizontal lines "≡") at the top-left corner`, - ); - await sleep(2000); - await agent.aiAct( - 'In the dropdown menu that just appeared, click the menu item labeled "Playground"', - ); - await sleep(3000); - }, - ); - - // ── 6. Settings Modal ───────────────────────────────────────────────── - - it('settings: open and close env config modal', async () => { - const openEnvConfigModal = async () => { - await agent.aiAct( - `Click the settings icon in the top-right header area of ${SIDE_PANEL}, next to the GitHub and help icons. Do not click the run configuration icon inside the prompt composer.`, - ); - await sleep(1500); - }; - - await openEnvConfigModal(); - - try { - await agent.aiAssert( - 'A modal titled "Model Env Config" is visible, and it contains a large text area for environment variable configuration.', - ); - } catch { - await openEnvConfigModal(); - await agent.aiAssert( - 'A modal titled "Model Env Config" is visible, and it contains a large text area for environment variable configuration.', - ); - } - - await agent.aiAct( - 'Click the X close button in the top-right corner of the "Model Env Config" modal.', - ); - await sleep(1500); - - await agent.aiAssert( - `The "Model Env Config" modal is closed, and ${SIDE_PANEL} is visible showing Playground UI without any modal overlay.`, - ); - }); }); From 8658a7deb14c1c2506df45ffa9e9b0214f8d3c15 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Fri, 31 Jul 2026 11:57:25 +0800 Subject: [PATCH 10/15] perf(workflow): reduce CI critical path --- .github/workflows/ai-unit-test.yml | 14 ++++++- .github/workflows/ai.yml | 42 ++++++--------------- .github/workflows/ci.yml | 10 +++++ .github/workflows/headless-linux.yml | 24 ++++++------ .github/workflows/ios-simulator.yml | 11 +----- .github/workflows/issue-labeled.yml | 3 +- .github/workflows/lint.yml | 11 +----- .github/workflows/pr-title.yml | 14 ++----- .github/workflows/studio-headless-linux.yml | 33 ++++++++++------ apps/report/e2e/detail-panel.yaml | 10 ++--- apps/report/e2e/report-single.yaml | 13 ------- apps/report/e2e/timeline-interaction.yaml | 2 +- apps/report/package.json | 2 +- 13 files changed, 84 insertions(+), 105 deletions(-) diff --git a/.github/workflows/ai-unit-test.yml b/.github/workflows/ai-unit-test.yml index 2e45966d64..445a87e285 100644 --- a/.github/workflows/ai-unit-test.yml +++ b/.github/workflows/ai-unit-test.yml @@ -3,7 +3,17 @@ on: push: branches: - main + paths-ignore: + - '**/*.md' + - 'apps/site/**' + - '.changeset/**' + - 'docs/**' pull_request: + paths-ignore: + - '**/*.md' + - 'apps/site/**' + - '.changeset/**' + - 'docs/**' workflow_dispatch: inputs: branch: @@ -59,8 +69,8 @@ jobs: - name: Setup Puppeteer browser uses: ./.github/actions/setup-puppeteer-browser - - name: Build project - run: pnpm run build + - name: Build AI unit test projects + run: pnpm exec nx run-many --target=build --projects="@midscene/report,@midscene/core,@midscene/web,@midscene/cli" - name: Run tests with coverage # Keep model request concurrency comparable to the former 4-core hosted runner. diff --git a/.github/workflows/ai.yml b/.github/workflows/ai.yml index bcd80eb942..9fad4c7afb 100644 --- a/.github/workflows/ai.yml +++ b/.github/workflows/ai.yml @@ -3,7 +3,17 @@ on: push: branches: - main + paths-ignore: + - '**/*.md' + - 'apps/site/**' + - '.changeset/**' + - 'docs/**' pull_request: + paths-ignore: + - '**/*.md' + - 'apps/site/**' + - '.changeset/**' + - 'docs/**' workflow_dispatch: inputs: branch: @@ -81,8 +91,8 @@ jobs: - name: Setup Puppeteer browser uses: ./.github/actions/setup-puppeteer-browser - - name: Build project - run: pnpm run build + - name: Build web e2e projects + run: pnpm exec nx run-many --target=build --projects="@midscene/report,@midscene/web,@midscene/cli" # Cache behavior uses explicit fixture configuration in the standard suite. # MIDSCENE_CACHE does not override those options, so a second full run is redundant. @@ -134,15 +144,6 @@ jobs: id: pnpm-cache uses: ./.github/actions/pnpm-cache/restore - - name: Cache Playwright dependencies - uses: ./.github/actions/cache/restore - id: cache-playwright - with: - path: ~/.cache/ms-playwright - key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-playwright- - - name: Install dependencies run: pnpm install --frozen-lockfile --ignore-scripts @@ -150,28 +151,9 @@ jobs: if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save - - name: Install Playwright dependencies - run: | - cd packages/web-integration - if [ "${{ steps.cache-playwright.outputs.cache-hit }}" != "true" ]; then - npx playwright install --with-deps - else - npx playwright install-deps && npx playwright install - fi - - - name: Save Playwright dependencies - if: steps.cache-playwright.outputs.cache-hit != 'true' - uses: ./.github/actions/cache/save - with: - path: ~/.cache/ms-playwright - key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} - - name: Setup Puppeteer browser uses: ./.github/actions/setup-puppeteer-browser - - name: Build project - run: pnpm run build - - name: Run apps/report e2e tests run: cd apps/report && pnpm run e2e id: e2e-tests-app-report diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15a8b695fa..12376f2406 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,17 @@ on: push: branches: - main + paths-ignore: + - '**/*.md' + - 'apps/site/**' + - '.changeset/**' + - 'docs/**' pull_request: + paths-ignore: + - '**/*.md' + - 'apps/site/**' + - '.changeset/**' + - 'docs/**' workflow_dispatch: permissions: diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 47df6cb1b3..7d377d7890 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -68,8 +68,8 @@ jobs: - name: Setup headless browser uses: ./.github/actions/setup-headless-browser - - name: Build project - run: pnpm run build:skip-cache + - name: Build AI todo projects + run: pnpm exec nx run-many --target=build --projects="@midscene/report,@midscene/computer" --skip-nx-cache - name: Verify system setup run: | @@ -140,8 +140,8 @@ jobs: - name: Setup headless browser uses: ./.github/actions/setup-headless-browser - - name: Build project - run: pnpm run build:skip-cache + - name: Build Chrome extension projects + run: pnpm exec nx run-many --target=build --projects="@midscene/report,chrome-extension" --skip-nx-cache - name: Run Chrome extension test run: AI_TEST_TYPE=computer npx nx test @midscene/computer -- tests/ai/chrome-extension.test.ts @@ -197,8 +197,8 @@ jobs: - name: Setup headless browser uses: ./.github/actions/setup-headless-browser - - name: Build project - run: pnpm run build:skip-cache + - name: Build Chrome extension projects + run: pnpm exec nx run-many --target=build --projects="@midscene/report,chrome-extension" --skip-nx-cache - name: Run Bridge mode start/stop test run: AI_TEST_TYPE=computer npx nx test @midscene/computer -- tests/ai/chrome-extension-bridge.test.ts @@ -254,8 +254,8 @@ jobs: - name: Setup headless browser uses: ./.github/actions/setup-headless-browser - - name: Build project - run: pnpm run build:skip-cache + - name: Build Chrome extension projects + run: pnpm exec nx run-many --target=build --projects="@midscene/report,chrome-extension" --skip-nx-cache - name: Run Playground advanced tests run: AI_TEST_TYPE=computer npx nx test @midscene/computer -- tests/ai/chrome-extension-playground.test.ts @@ -311,8 +311,8 @@ jobs: - name: Setup headless browser uses: ./.github/actions/setup-headless-browser - - name: Build project - run: pnpm run build:skip-cache + - name: Build Chrome extension projects + run: pnpm exec nx run-many --target=build --projects="@midscene/report,chrome-extension" --skip-nx-cache - name: Run Recorder mode tests run: AI_TEST_TYPE=computer npx nx test @midscene/computer -- tests/ai/chrome-extension-recorder.test.ts @@ -368,8 +368,8 @@ jobs: - name: Setup headless browser uses: ./.github/actions/setup-headless-browser - - name: Build project - run: pnpm run build:skip-cache + - name: Build Chrome extension projects + run: pnpm exec nx run-many --target=build --projects="@midscene/report,chrome-extension" --skip-nx-cache - name: Run Settings and cross-mode tests run: AI_TEST_TYPE=computer npx nx test @midscene/computer -- tests/ai/chrome-extension-settings.test.ts diff --git a/.github/workflows/ios-simulator.yml b/.github/workflows/ios-simulator.yml index 46ffef7e6f..3f282e2728 100644 --- a/.github/workflows/ios-simulator.yml +++ b/.github/workflows/ios-simulator.yml @@ -73,15 +73,8 @@ jobs: xcrun simctl list devices available } 2>&1 | tee "$MIDSCENE_IOS_DIAGNOSTICS_DIR/runner-preflight.log" - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index 5393745442..45b7d0feba 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -8,12 +8,11 @@ jobs: reply-labeled: name: Reply need reproduction runs-on: ${{ fromJSON(vars.CI_LINUX_MINI_RUNNER || '"ubuntu-latest"') }} - if: github.repository == 'web-infra-dev/midscene' + if: github.repository == 'web-infra-dev/midscene' && github.event.label.name == 'need reproduction' permissions: issues: write steps: - name: need reproduction - if: github.event.label.name == 'need reproduction' env: GH_TOKEN: ${{ github.token }} ISSUE_NUMBER: ${{ github.event.issue.number }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f09d0247c5..91ddbf4105 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,15 +21,8 @@ jobs: steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 8c9a3c1bcb..f002a48c92 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -2,7 +2,7 @@ name: PR Title on: pull_request: - types: [opened, edited, synchronize, reopened] + types: [opened, edited, reopened] permissions: contents: read @@ -10,20 +10,14 @@ permissions: jobs: commitlint: name: Validate title + if: github.event.action != 'edited' || github.event.changes.title runs-on: ${{ fromJSON(vars.CI_LINUX_MINI_RUNNER || '"ubuntu-latest"') }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Setup pnpm - uses: pnpm/action-setup@eae0cfeb286e66ffb5155f1a79b90583a127a68b # v2.4.1 - with: - version: 9.3.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '24.13.0' + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm - name: Cache pnpm store id: pnpm-cache diff --git a/.github/workflows/studio-headless-linux.yml b/.github/workflows/studio-headless-linux.yml index 9c52f35600..c1e08dd1c4 100644 --- a/.github/workflows/studio-headless-linux.yml +++ b/.github/workflows/studio-headless-linux.yml @@ -49,6 +49,15 @@ jobs: id: pnpm-cache uses: ./.github/actions/pnpm-cache/restore + - name: Cache Electron binary + id: electron-cache + uses: ./.github/actions/cache/restore + with: + path: ~/.cache/electron + key: midscene-electron-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + midscene-electron-v1-${{ runner.os }}-${{ runner.arch }}- + - name: Install system dependencies run: | sudo apt-get update @@ -79,22 +88,24 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + env: + PUPPETEER_SKIP_DOWNLOAD: 'true' - name: Save pnpm store if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save - - name: Install Puppeteer browser binary - # `pnpm install` normally triggers puppeteer's `postinstall` to fetch - # the Chrome binary into `~/.cache/puppeteer`, but the added - # `optionalDependencies` (appdmg) shifts the install layout enough - # that the postinstall sometimes does not fire on the headless Linux - # runner. The Studio Web playground session needs that Chrome to - # launch its puppeteer page; without it `createSession` throws - # "Could not find Chrome" and the web preview e2e asserts on a - # permanently disconnected device. Install it explicitly so the test - # environment is deterministic. - run: pnpm --dir apps/studio exec puppeteer browsers install chrome + - name: Save Electron binary + if: steps.electron-cache.outputs.cache-hit != 'true' + uses: ./.github/actions/cache/save + with: + path: ~/.cache/electron + key: midscene-electron-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Setup Puppeteer browser + uses: ./.github/actions/setup-puppeteer-browser + with: + package-directory: apps/studio - name: Build Studio run: npx nx run-many --target=build --projects=studio,@midscene/report diff --git a/apps/report/e2e/detail-panel.yaml b/apps/report/e2e/detail-panel.yaml index 07340e72b0..46630bcd15 100644 --- a/apps/report/e2e/detail-panel.yaml +++ b/apps/report/e2e/detail-panel.yaml @@ -5,21 +5,21 @@ tasks: - name: view screenshots tab flow: - sleep: 2000 - - ai: Click on the first task row in the left sidebar that has type "Planning" or "Insight" or "Locate" + - aiTap: the first task row in the left sidebar that has type "Planning" or "Insight" or "Locate" - sleep: 2000 - - ai: In the center task detail Replay/Screenshots/JSON View switcher, select "Screenshots" (not Replay or JSON View) and wait until screenshot thumbnails are displayed + - aiTap: the "Screenshots" option in the center task detail Replay/Screenshots/JSON View switcher - sleep: 1500 - aiAssert: In the center task detail panel, the selected tab among Replay, Screenshots, and JSON View is Screenshots. The content below the tabs shows UI screenshot images for the selected task. - name: view JSON data tab flow: - - ai: In the center task detail area, click the "JSON View" option in the Replay/Screenshots/JSON View switcher and wait until the content changes to the text data view + - aiTap: the "JSON View" option in the center task detail Replay/Screenshots/JSON View switcher - sleep: 1000 - aiAssert: In the center task detail panel, the selected tab among Replay, Screenshots, and JSON View is JSON View. The content below the tabs shows structured task details as JSON text instead of UI screenshot images. - name: inspect detail side sections flow: - - ai: Click on the first Planning task row in the left sidebar + - aiTap: the first Planning task row in the left sidebar - sleep: 1000 - aiAssert: The right detail side panel shows collapsible section headers named Param and Output, and the Param section is expanded with task input data # Collapse both Param and the Output/Element section so the Meta section @@ -35,6 +35,6 @@ tasks: - name: open in playground button flow: - - ai: Click on a task row in the sidebar that shows "Planning" type + - aiTap: a task row in the sidebar that shows "Planning" type - sleep: 2000 - aiAssert: There is a "Open in Playground" button visible on the page diff --git a/apps/report/e2e/report-single.yaml b/apps/report/e2e/report-single.yaml index 8ebee7af3d..f026e14c66 100644 --- a/apps/report/e2e/report-single.yaml +++ b/apps/report/e2e/report-single.yaml @@ -27,16 +27,3 @@ tasks: deepLocate: true - sleep: 1000 - aiAssert: The player settings popup is open above the bottom-right control bar, showing options such as "Focus on cursor", "Subtitle", "Playback speed", and speed choices like "0.5x", "1x", "1.5x", or "2x". - - # --- Dark mode --- - - name: toggle dark mode - flow: - - aiTap: theme toggle button in the top right header - - sleep: 2000 - - aiAssert: The report UI is in dark theme; the top header, left sidebar, and page chrome are dark charcoal or black rather than white - - - name: toggle back to light mode - flow: - - aiTap: theme toggle button in the top right header - - sleep: 2000 - - aiAssert: The report UI is in light theme; the top header, left sidebar, and page chrome are light or white rather than dark charcoal or black diff --git a/apps/report/e2e/timeline-interaction.yaml b/apps/report/e2e/timeline-interaction.yaml index 3880ec13e4..f41b12d408 100644 --- a/apps/report/e2e/timeline-interaction.yaml +++ b/apps/report/e2e/timeline-interaction.yaml @@ -9,6 +9,6 @@ tasks: - name: click timeline screenshot to navigate flow: - - ai: Click on a screenshot thumbnail in the timeline area + - aiTap: a screenshot thumbnail in the timeline area - sleep: 2000 - aiAssert: The sidebar highlights a task row and the detail panel shows information for the selected task diff --git a/apps/report/package.json b/apps/report/package.json index 703be9717c..7fa73af88f 100644 --- a/apps/report/package.json +++ b/apps/report/package.json @@ -11,7 +11,7 @@ "test": "vitest --run", "test:watch": "vitest", "generate-demo": "node scripts/generate-demo-report.mjs", - "e2e": "pnpm --dir ../.. exec nx build @midscene/report --verbose && node scripts/inject-report-template.mjs && node scripts/generate-demo-report.mjs && node ../../packages/cli/bin/midscene ./e2e/" + "e2e": "pnpm --dir ../.. exec nx run-many --target=build --projects=\"@midscene/report,@midscene/cli\" --verbose && node scripts/inject-report-template.mjs && node scripts/generate-demo-report.mjs && node ../../packages/cli/bin/midscene ./e2e/ --concurrent 2" }, "dependencies": { "@ant-design/icons": "^5.3.1", From c149bbd26e8a50e8907cacc7b206f1a6c4951d24 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Fri, 31 Jul 2026 12:16:54 +0800 Subject: [PATCH 11/15] perf(workflow): reserve capacity for AI unit tests --- .github/workflows/headless-linux.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 7d377d7890..3bcaf739eb 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -162,6 +162,8 @@ jobs: run: exit 1 chrome-extension-bridge: + needs: chrome-extension + if: ${{ !cancelled() }} runs-on: ${{ fromJSON(vars.LINUX_SELF_HOSTED_RUNNER_LABELS || '"ubuntu-22.04"') }} env: MIDSCENE_MODEL_API_KEY: ${{ secrets.MIDSCENE_MODEL_API_KEY }} From 8a391c742847e2b3e0ca091159044752ec90e991 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Fri, 31 Jul 2026 12:21:09 +0800 Subject: [PATCH 12/15] perf(workflow): skip repeated Playwright system setup --- .github/workflows/ai.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ai.yml b/.github/workflows/ai.yml index 9fad4c7afb..933d049f93 100644 --- a/.github/workflows/ai.yml +++ b/.github/workflows/ai.yml @@ -72,14 +72,11 @@ jobs: if: steps.pnpm-cache.outputs.cache-hit != 'true' uses: ./.github/actions/pnpm-cache/save - - name: Install Playwright dependencies - run: | - cd packages/web-integration - if [ "${{ steps.cache-playwright.outputs.cache-hit }}" != "true" ]; then - npx playwright install --with-deps - else - npx playwright install-deps && npx playwright install - fi + - name: Setup headless browser + uses: ./.github/actions/setup-headless-browser + + - name: Install Playwright Chromium + run: pnpm --dir packages/web-integration exec playwright install chromium - name: Save Playwright dependencies if: steps.cache-playwright.outputs.cache-hit != 'true' @@ -88,9 +85,6 @@ jobs: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} - - name: Setup Puppeteer browser - uses: ./.github/actions/setup-puppeteer-browser - - name: Build web e2e projects run: pnpm exec nx run-many --target=build --projects="@midscene/report,@midscene/web,@midscene/cli" From 65db2ceb9672cc64dc68c7659257beb6560efbdd Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Fri, 31 Jul 2026 13:14:43 +0800 Subject: [PATCH 13/15] fix(web-integration): validate successful report retry --- .../web/playwright-reporter-test/todo-report.spec.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/web-integration/tests/ai/web/playwright-reporter-test/todo-report.spec.ts b/packages/web-integration/tests/ai/web/playwright-reporter-test/todo-report.spec.ts index 41e11104d9..ee42a1b7e1 100644 --- a/packages/web-integration/tests/ai/web/playwright-reporter-test/todo-report.spec.ts +++ b/packages/web-integration/tests/ai/web/playwright-reporter-test/todo-report.spec.ts @@ -18,9 +18,14 @@ test('ai report', async ({ page, aiAssert, aiQuery }, testInfo) => { const options = page.locator('.selector-content .option-item'); await expect(options.first()).toBeVisible(); - const aiTodoOption = options.filter({ hasText: /ai todo/i }); - if ((await aiTodoOption.count()) > 0) { - await aiTodoOption.first().click(); + const aiTodoOptions = options.filter({ hasText: /ai todo/i }); + const successfulAiTodoOptions = aiTodoOptions.filter({ + has: page.locator('[aria-label="check"]'), + }); + if ((await successfulAiTodoOptions.count()) > 0) { + await successfulAiTodoOptions.last().click(); + } else if ((await aiTodoOptions.count()) > 0) { + await aiTodoOptions.last().click(); } else { await options.first().click(); } From 7474a3c8dbed1c9b46cef835067f5868104e37d5 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Fri, 31 Jul 2026 13:33:50 +0800 Subject: [PATCH 14/15] perf(workflow): limit concurrent AI workloads --- .github/workflows/android-emulator.yml | 3 +++ .github/workflows/headless-linux.yml | 4 ++++ .github/workflows/ios-simulator.yml | 3 +++ .github/workflows/macos-desktop.yml | 3 +++ .github/workflows/windows-desktop.yml | 3 +++ 5 files changed, 16 insertions(+) diff --git a/.github/workflows/android-emulator.yml b/.github/workflows/android-emulator.yml index 4bc039dad3..76bf9d7024 100644 --- a/.github/workflows/android-emulator.yml +++ b/.github/workflows/android-emulator.yml @@ -30,6 +30,9 @@ concurrency: jobs: android-emulator: + concurrency: + group: ai-mobile-${{ github.event.pull_request.number || inputs.branch || github.ref }} + cancel-in-progress: false # The self-hosted Linux pool does not guarantee KVM passthrough. runs-on: ubuntu-22.04 timeout-minutes: 55 diff --git a/.github/workflows/headless-linux.yml b/.github/workflows/headless-linux.yml index 3bcaf739eb..c8a6a1cb89 100644 --- a/.github/workflows/headless-linux.yml +++ b/.github/workflows/headless-linux.yml @@ -221,6 +221,8 @@ jobs: run: exit 1 chrome-extension-playground: + needs: chrome-extension-bridge + if: ${{ !cancelled() }} runs-on: ${{ fromJSON(vars.LINUX_SELF_HOSTED_RUNNER_LABELS || '"ubuntu-22.04"') }} env: MIDSCENE_MODEL_API_KEY: ${{ secrets.MIDSCENE_MODEL_API_KEY }} @@ -335,6 +337,8 @@ jobs: run: exit 1 chrome-extension-settings: + needs: chrome-extension-recorder + if: ${{ !cancelled() }} runs-on: ${{ fromJSON(vars.LINUX_SELF_HOSTED_RUNNER_LABELS || '"ubuntu-22.04"') }} env: MIDSCENE_MODEL_API_KEY: ${{ secrets.MIDSCENE_MODEL_API_KEY }} diff --git a/.github/workflows/ios-simulator.yml b/.github/workflows/ios-simulator.yml index 3f282e2728..d507a6ef67 100644 --- a/.github/workflows/ios-simulator.yml +++ b/.github/workflows/ios-simulator.yml @@ -31,6 +31,9 @@ concurrency: jobs: ios-simulator: + concurrency: + group: ai-mobile-${{ github.event.pull_request.number || inputs.branch || github.ref }} + cancel-in-progress: false runs-on: ${{ fromJSON(vars.MAC_SELF_HOSTED_RUNNER_LABELS || '"macos-latest"') }} timeout-minutes: 70 env: diff --git a/.github/workflows/macos-desktop.yml b/.github/workflows/macos-desktop.yml index 86c4070a9e..f9935623f7 100644 --- a/.github/workflows/macos-desktop.yml +++ b/.github/workflows/macos-desktop.yml @@ -30,6 +30,9 @@ concurrency: jobs: macos-desktop: + concurrency: + group: ai-desktop-${{ github.event.pull_request.number || inputs.branch || github.ref }} + cancel-in-progress: false # The self-hosted macOS runner is ARM and has no interactive GUI session. runs-on: macos-15-intel timeout-minutes: 50 diff --git a/.github/workflows/windows-desktop.yml b/.github/workflows/windows-desktop.yml index d32c88bba6..11aedcba15 100644 --- a/.github/workflows/windows-desktop.yml +++ b/.github/workflows/windows-desktop.yml @@ -30,6 +30,9 @@ concurrency: jobs: windows-desktop: + concurrency: + group: ai-desktop-${{ github.event.pull_request.number || inputs.branch || github.ref }} + cancel-in-progress: false # The self-hosted Windows runner runs as a non-interactive service. runs-on: windows-2022 timeout-minutes: 50 From 4058f8cb37b52bb591e1f4c6c4105cdf1c0e2591 Mon Sep 17 00:00:00 2001 From: quanruzhuoxiu Date: Fri, 31 Jul 2026 16:47:39 +0800 Subject: [PATCH 15/15] fix(workflow): preserve release and browser contracts --- .github/actions/setup-puppeteer-browser/action.yml | 14 ++++++-------- .github/workflows/release.yml | 3 ++- .../computer/tests/ai/chrome-extension-helpers.ts | 10 ++++++++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/actions/setup-puppeteer-browser/action.yml b/.github/actions/setup-puppeteer-browser/action.yml index 69899c2151..0188be69a5 100644 --- a/.github/actions/setup-puppeteer-browser/action.yml +++ b/.github/actions/setup-puppeteer-browser/action.yml @@ -50,18 +50,16 @@ runs: set -euo pipefail browser_path="$( - find "$HOME/.cache/puppeteer/chrome" \ - -type f \ - -path '*/chrome-linux64/chrome' \ - -perm -u+x \ - -print \ - -quit + pnpm --dir "${{ inputs.package-directory }}" exec node -e \ + 'process.stdout.write(require("puppeteer").executablePath())' )" - if [ -z "$browser_path" ]; then - echo "::error::Chrome for Testing was not found in the Puppeteer cache" + if [ ! -x "$browser_path" ]; then + echo "::error::Puppeteer resolved a missing or non-executable browser: $browser_path" exit 1 fi + echo "PUPPETEER_EXECUTABLE_PATH=$browser_path" >> "$GITHUB_ENV" + if [ "${{ inputs.expose-as-chromium }}" = "true" ]; then bin_dir="$RUNNER_TEMP/midscene-browser-bin" mkdir -p "$bin_dir" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe729104db..d3b37649c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,8 @@ jobs: outputs: version: ${{ steps.get_version.outputs.version }} is_prerelease: ${{ steps.get_version.outputs.is_prerelease }} - runs-on: ${{ fromJSON(vars.LINUX_SELF_HOSTED_RUNNER_LABELS || '"ubuntu-22.04"') }} + # npm Trusted Publishing currently requires a GitHub-hosted runner. + runs-on: ubuntu-22.04 strategy: matrix: node-version: [24.13.0] diff --git a/packages/computer/tests/ai/chrome-extension-helpers.ts b/packages/computer/tests/ai/chrome-extension-helpers.ts index bdb7553a06..d52d29258f 100644 --- a/packages/computer/tests/ai/chrome-extension-helpers.ts +++ b/packages/computer/tests/ai/chrome-extension-helpers.ts @@ -45,6 +45,16 @@ const EXTENSION_ENV_KEYS = [ // ─── Browser Helpers ──────────────────────────────────────────────────────── function findExtensionCapableBrowser(): string { + const configuredBrowser = process.env.PUPPETEER_EXECUTABLE_PATH; + if (configuredBrowser) { + if (!fs.existsSync(configuredBrowser)) { + throw new Error( + `PUPPETEER_EXECUTABLE_PATH does not exist: ${configuredBrowser}`, + ); + } + return configuredBrowser; + } + // Check puppeteer cache first (Chrome for Testing supports --load-extension) const puppeteerBase = path.join( process.env.HOME ?? '~',