Skip to content

refactor(e2e): migrate Feishu scenarios to Midscene Test #23

refactor(e2e): migrate Feishu scenarios to Midscene Test

refactor(e2e): migrate Feishu scenarios to Midscene Test #23

Workflow file for this run

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