Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1da3d27
test(midscene): 接入 Dashboard 声明式冒烟测试
quanru Sep 21, 2026
aa68d8e
fix(deps): 修正锁文件为公共 npm 源
quanru Sep 21, 2026
9151d87
refactor(e2e): migrate Feishu scenarios to Midscene Test
quanru Sep 21, 2026
0357174
test(cli): allow statusline watchdog process overhead
quanru Sep 21, 2026
3f45331
ci(midscene): publish reports and run summaries
quanru Sep 21, 2026
a9fd38b
ci(midscene): use current artifact uploader
quanru Sep 21, 2026
40d6343
ci(midscene): skip live cases without credentials
quanru Sep 21, 2026
5759a97
fix(ci): fail when Midscene summary is empty
quanru Sep 21, 2026
7988b40
test(midscene): publish a real Dashboard report
quanru Sep 21, 2026
8f93677
fix(midscene): make Dashboard navigation reliable
quanru Sep 21, 2026
846aa8c
ci(midscene): list skipped Feishu cases
quanru Sep 21, 2026
e2343d9
Merge remote-tracking branch 'origin/master' into feat/midscene-test
quanru Sep 22, 2026
c9b7f81
fix(ci): configure Pages only for deployments
quanru Sep 22, 2026
477b35e
test: make Kimi effort polling Bun-compatible
quanru Sep 22, 2026
b2d0100
feat(e2e): publish case-level Midscene evidence
quanru Sep 22, 2026
8ab3a1f
fix(ci): keep Midscene projects independent
quanru Sep 22, 2026
19cb20e
fix(ci): skip report deploy for fork examples
quanru Sep 22, 2026
3e84818
fix(e2e): stabilize authenticated Feishu runs
quanru Sep 22, 2026
5b86e54
chore(deps): update Midscene to 1.13.1
quanru Sep 22, 2026
d9d904f
fix(ci): preserve Midscene evidence on timeout
quanru Sep 23, 2026
9b40c11
fix(e2e): validate Feishu fixture and publish real results
quanru Sep 23, 2026
975475c
Merge origin/master into feat/midscene-test
quanru Sep 23, 2026
6a0db95
fix(ci): Midscene 证据站发布前校验落地页与 manifest 完整
deepcoldy Sep 23, 2026
598f8d5
test(e2e): 默认只跑 Claude/Codex 核心飞书用例
deepcoldy Sep 24, 2026
cc7a724
fix(e2e): Messenger 就绪检测兼容英文 UI
deepcoldy Sep 24, 2026
e058bdc
fix(e2e): 话题筛选入口指令兼容英文飞书 UI
deepcoldy Sep 24, 2026
a5afed5
fix(e2e): openChat 选 [Botmux] 机器人会话,排除同名原生智能体
deepcoldy Sep 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ MIDSCENE_MODEL_API_KEY=your-api-key
MIDSCENE_MODEL_BASE_URL=https://your-endpoint
MIDSCENE_MODEL_FAMILY=your-model-family

# Run the migrated Midscene Test suite with: bun run test:e2e-browser
# Reports are written below the gitignored midscene_run/runs/ directory.

# (可选)普通群聊名称(多机器人),用于群聊 @mention 路由和话题回复测试
# FEISHU_TEST_GROUP_CHAT_NAME=普通群聊

Expand Down
287 changes: 287 additions & 0 deletions .github/workflows/midscene-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
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: Verify evidence site is complete
run: |
# The prepare step can fail (e.g. an unreadable report) after copying
# a partial directory. Never publish a site without a landing page or
# manifest, otherwise the previous good report at this SITE_KEY is
# replaced by a broken, partial one.
test -f "$RUNNER_TEMP/midscene-site/index.html"
test -f "$RUNNER_TEMP/midscene-site/manifest.json"

- 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ docs/superpowers/
docs/specs/
storageState.json
midscene_run/
.midscene/
.codex
.claude/
.antigravitycli/
Expand Down
25 changes: 23 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ bun run daemon:logs
> (see `InstallKind` in `src/utils/install-diagnostics.ts`), so don't "convert"
> those to bun.

## Midscene Test · Feishu browser E2E

The existing Feishu browser scenarios in `test/e2e-browser/` run through
`@midscene/test`. Their Playwright and Midscene Web assertions are registered as
native YAML cases, preserving the original scenario behavior while adding
Midscene Test selection, retries, summaries, and unified reports.

Create `storageState.json` and provide the Feishu and `MIDSCENE_MODEL_*`
variables shown in `.env.example`, then run:

```bash
bun run test:midscene:typecheck
bun run test:e2e-browser
```

Each invocation writes its replayable report below the gitignored
`midscene_run/runs/<run-id>/` directory.

## Architecture

```
Expand Down Expand Up @@ -184,13 +202,16 @@ Tests are split into two Vitest projects with different execution profiles
Runs with **file parallelism on** (one process per file). This is what
`bun run test` runs, so the default is fast (~10s) and needs no real CLI binary
or browser.
- **`e2e`** (`*.e2e.ts`) — spawns real CLIs / drives the Feishu web UI through a
shared daemon, so files run **sequentially**. Opt-in only.
- **`e2e`** (`*.e2e.ts`, excluding `test/e2e-browser/`) — spawns real CLIs and
runs sequentially. Opt-in only.
- **Feishu browser E2E** (`test/e2e-browser/cases/*.yaml`) — runs through
`@midscene/test` against a shared daemon and logged-in browser session.

```bash
bun run test # Unit tests only — parallel, ~10s (default)
bun run test:all # Unit + E2E (needs real CLIs / browser session)
bun run test:e2e # All *.e2e.ts (sequential)
bun run test:e2e-browser # Feishu browser cases through Midscene Test
bun run test:codex # Codex input E2E
bun run test:gemini # Gemini CLI input E2E
bun run test:bench # Benchmark the unit suite (see docs/test-benchmark.md)
Expand Down
Loading
Loading