refactor(e2e): migrate Feishu scenarios to Midscene Test #23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Midscene E2E | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| report_site_key: | |
| description: Published report directory (for example, pr-1512) | |
| required: false | |
| default: manual | |
| pull_request: | |
| paths: | |
| - ".github/workflows/midscene-e2e.yml" | |
| - "package.json" | |
| - "bun.lock" | |
| - "scripts/prepare-midscene-pages.mjs" | |
| - "scripts/midscene-report-evidence.mjs" | |
| - "scripts/render-midscene-summary.mjs" | |
| - "scripts/run-e2e.ts" | |
| - "src/dashboard/**" | |
| - "src/index-dashboard.ts" | |
| - "test/e2e-browser/**" | |
| - "test/midscene/**" | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: midscene-e2e-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| midscene-report: | |
| name: Run Midscene and build report | |
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 330 | |
| permissions: | |
| contents: read | |
| outputs: | |
| report-site-key: ${{ steps.report_target.outputs.site-key }} | |
| report-site-url: ${{ steps.report_target.outputs.site-url }} | |
| env: | |
| CI: "true" | |
| FEISHU_TEST_GROUP_URL: ${{ secrets.FEISHU_TEST_GROUP_URL }} | |
| FEISHU_TEST_GROUP_CHAT_NAME: ${{ secrets.FEISHU_TEST_GROUP_CHAT_NAME }} | |
| MIDSCENE_MODEL_API_KEY: ${{ secrets.MIDSCENE_MODEL_API_KEY }} | |
| MIDSCENE_MODEL_NAME: ${{ secrets.MIDSCENE_MODEL_NAME }} | |
| MIDSCENE_MODEL_BASE_URL: ${{ secrets.MIDSCENE_MODEL_BASE_URL }} | |
| MIDSCENE_MODEL_FAMILY: ${{ secrets.MIDSCENE_MODEL_FAMILY }} | |
| MIDSCENE_MODEL_REASONING_ENABLED: ${{ secrets.MIDSCENE_MODEL_REASONING_ENABLED }} | |
| MIDSCENE_MODEL_RETRY_COUNT: "3" | |
| MIDSCENE_MODEL_RETRY_INTERVAL: "65000" | |
| NODE_OPTIONS: "--dns-result-order=ipv4first --no-network-family-autoselection" | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Resolve published report target | |
| id: report_target | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| DISPATCH_SITE_KEY: ${{ inputs.report_site_key }} | |
| run: | | |
| if [[ -n "$PR_NUMBER" ]]; then | |
| site_key="pr-$PR_NUMBER" | |
| else | |
| site_key="${DISPATCH_SITE_KEY:-manual}" | |
| fi | |
| echo "site-key=$site_key" >> "$GITHUB_OUTPUT" | |
| echo "site-url=https://deepcoldy.github.io/botmux-midscene/$site_key/" >> "$GITHUB_OUTPUT" | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 1.4.2 | |
| - name: Require Midscene model configuration | |
| run: | | |
| test -n "$MIDSCENE_MODEL_API_KEY" | |
| test -n "$MIDSCENE_MODEL_NAME" | |
| test -n "$MIDSCENE_MODEL_BASE_URL" | |
| test -n "$MIDSCENE_MODEL_FAMILY" | |
| - name: Check Midscene model connectivity | |
| run: | | |
| node --input-type=module <<'NODE' | |
| const baseUrl = process.env.MIDSCENE_MODEL_BASE_URL.replace(/\/+$/, ''); | |
| const response = await fetch(`${baseUrl}/chat/completions`, { | |
| method: 'POST', | |
| headers: { | |
| Authorization: `Bearer ${process.env.MIDSCENE_MODEL_API_KEY}`, | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| model: process.env.MIDSCENE_MODEL_NAME, | |
| messages: [{ role: 'user', content: 'Reply OK.' }], | |
| max_tokens: 1, | |
| }), | |
| signal: AbortSignal.timeout(90_000), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Model preflight returned HTTP ${response.status}`); | |
| } | |
| await response.arrayBuffer(); | |
| NODE | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Validate Midscene projects | |
| run: | | |
| bun run test:midscene:typecheck | |
| bun run test:midscene:nodes | |
| bun run test -- test/midscene-suite.test.ts | |
| - name: Install Chromium | |
| run: | | |
| bun x playwright install --with-deps chromium | |
| sudo apt-get install -y fonts-noto-cjk | |
| - name: Build Botmux | |
| run: bun run build | |
| - name: Run Dashboard Midscene project | |
| id: dashboard_midscene | |
| env: | |
| MIDSCENE_RUN_DIR: ${{ github.workspace }}/midscene_run/dashboard | |
| run: bun run test:midscene | |
| - name: Detect Feishu live-test configuration | |
| id: feishu_config | |
| if: always() && !cancelled() | |
| env: | |
| FEISHU_STORAGE_STATE_BASE64: ${{ secrets.FEISHU_STORAGE_STATE_BASE64 }} | |
| FEISHU_STORAGE_STATE_GZIP_BASE64: ${{ secrets.FEISHU_STORAGE_STATE_GZIP_BASE64 }} | |
| run: | | |
| missing=() | |
| [[ -n "${FEISHU_TEST_GROUP_URL:-}" ]] || missing+=("FEISHU_TEST_GROUP_URL") | |
| if [[ -z "${FEISHU_STORAGE_STATE_GZIP_BASE64:-}" && -z "${FEISHU_STORAGE_STATE_BASE64:-}" ]]; then | |
| missing+=("FEISHU_STORAGE_STATE_GZIP_BASE64 or FEISHU_STORAGE_STATE_BASE64") | |
| fi | |
| if (( ${#missing[@]} > 0 )); then | |
| echo "ready=false" >> "$GITHUB_OUTPUT" | |
| echo "Feishu live cases skipped; unavailable Actions secrets: ${missing[*]}" | |
| exit 0 | |
| fi | |
| if [[ -n "${FEISHU_STORAGE_STATE_GZIP_BASE64:-}" ]]; then | |
| printf '%s' "$FEISHU_STORAGE_STATE_GZIP_BASE64" | base64 --decode | gzip --decompress > storageState.json | |
| else | |
| printf '%s' "$FEISHU_STORAGE_STATE_BASE64" | base64 --decode > storageState.json | |
| fi | |
| node -e "const fs=require('node:fs'); const value=JSON.parse(fs.readFileSync('storageState.json','utf8')); if (!Array.isArray(value.cookies) || value.cookies.length === 0) throw new Error('The Feishu Playwright storage state has no cookies')" | |
| echo "ready=true" >> "$GITHUB_OUTPUT" | |
| - name: Run migrated Feishu Midscene project | |
| id: feishu_midscene | |
| timeout-minutes: 300 | |
| # The credential-free smoke case and the authenticated Feishu suite are | |
| # independent report producers. A failure in either must not hide the | |
| # other suite's evidence. | |
| if: always() && !cancelled() && steps.feishu_config.outputs.ready == 'true' | |
| env: | |
| MIDSCENE_RUN_DIR: ${{ github.workspace }}/midscene_run/feishu | |
| run: bun run test:midscene:feishu | |
| - name: Prepare linked Midscene evidence site | |
| if: always() && !cancelled() | |
| env: | |
| FEISHU_READY: ${{ steps.feishu_config.outputs.ready }} | |
| FEISHU_OUTCOME: ${{ steps.feishu_midscene.outcome }} | |
| run: | | |
| feishu_outcome="${FEISHU_OUTCOME:-not-run}" | |
| if [[ "$FEISHU_READY" != "true" ]]; then | |
| feishu_outcome="skipped" | |
| fi | |
| arguments=( | |
| --dashboard-report-root test/midscene/midscene_run | |
| --skipped-cases-dir test/e2e-browser/cases | |
| --feishu-outcome "$feishu_outcome" | |
| --output "$RUNNER_TEMP/midscene-site" | |
| ) | |
| if [[ "$feishu_outcome" != "skipped" ]]; then | |
| arguments+=(--feishu-report-root test/e2e-browser/midscene_run) | |
| fi | |
| node scripts/prepare-midscene-pages.mjs "${arguments[@]}" | |
| - name: Write Midscene run summary | |
| if: always() && !cancelled() | |
| env: | |
| TEST_OUTCOME: ${{ steps.dashboard_midscene.outcome }} | |
| FEISHU_READY: ${{ steps.feishu_config.outputs.ready }} | |
| FEISHU_OUTCOME: ${{ steps.feishu_midscene.outcome }} | |
| REPORT_SITE_URL: ${{ steps.report_target.outputs.site-url }} | |
| run: | | |
| feishu_outcome="${FEISHU_OUTCOME:-not-run}" | |
| if [[ "$FEISHU_READY" != "true" ]]; then | |
| feishu_outcome="skipped" | |
| fi | |
| node scripts/render-midscene-summary.mjs \ | |
| --results midscene_run/dashboard/results \ | |
| --feishu-results midscene_run/runs \ | |
| --site-manifest "$RUNNER_TEMP/midscene-site/manifest.json" \ | |
| --artifact-name botmux-midscene-report \ | |
| --test-outcome "${TEST_OUTCOME:-not-run}" \ | |
| --feishu-outcome "$feishu_outcome" \ | |
| --skipped-cases-dir test/e2e-browser/cases \ | |
| --pages-url "$REPORT_SITE_URL" \ | |
| --output "$GITHUB_STEP_SUMMARY" | |
| test -s "$GITHUB_STEP_SUMMARY" | |
| cat "$GITHUB_STEP_SUMMARY" | |
| - name: Upload complete Midscene report data | |
| if: always() && !cancelled() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: botmux-midscene-report | |
| path: | | |
| midscene_run/ | |
| test/midscene/midscene_run/ | |
| test/e2e-browser/midscene_run/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| overwrite: true | |
| - name: Upload linked Midscene evidence site | |
| if: always() && !cancelled() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: botmux-midscene-site | |
| path: ${{ runner.temp }}/midscene-site | |
| if-no-files-found: error | |
| retention-days: 7 | |
| overwrite: true | |
| deploy-report: | |
| name: Publish Midscene HTML report | |
| needs: midscene-report | |
| # Failed cases are exactly when the published screenshots and native replay | |
| # are most useful, so publish whenever the producer reached artifact upload. | |
| if: always() && !cancelled() && needs.midscene-report.result != 'cancelled' && needs.midscene-report.result != 'skipped' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Download linked evidence site | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: botmux-midscene-site | |
| path: ${{ runner.temp }}/midscene-site | |
| - name: Publish report under deepcoldy.github.io | |
| env: | |
| DEPLOY_KEY: ${{ secrets.PAGES_DEPLOY_KEY }} | |
| SITE_KEY: ${{ needs.midscene-report.outputs.report-site-key }} | |
| run: | | |
| set -euo pipefail | |
| test -n "$DEPLOY_KEY" | |
| test -n "$SITE_KEY" | |
| mkdir -p ~/.ssh | |
| printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519 | |
| chmod 600 ~/.ssh/id_ed25519 | |
| ssh-keyscan -t ed25519,rsa github.com >> ~/.ssh/known_hosts 2>/dev/null | |
| export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes" | |
| git clone --depth 1 --branch gh-pages git@github.com:deepcoldy/deepcoldy.github.io.git pages | |
| target="pages/botmux-midscene/$SITE_KEY" | |
| rm -rf "$target" | |
| mkdir -p "$target" | |
| cp -a "$RUNNER_TEMP/midscene-site/." "$target/" | |
| cd pages | |
| git config user.name "botmux-midscene-bot" | |
| git config user.email "midscene-bot@users.noreply.github.com" | |
| git add -A | |
| git commit -m "test: publish Botmux Midscene report ($SITE_KEY @ ${GITHUB_SHA::7})" | |
| git push origin gh-pages |