From c5d054789ed594f0f0bb5a35a2ce103a72f9c5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E7=94=B7?= Date: Sun, 26 Apr 2026 17:01:52 +0800 Subject: [PATCH] feat: Playwright against deployed music-room (shell + optional unionid chain) No Rust/Compose; GitHub Actions with MOICEN_E2E_UNIONID secret; dotenv .env.e2e; proxy install script. Made-with: Cursor --- .env.e2e.example | 3 + .github/workflows/playwright-music-room.yml | 52 ++++++++++++ .gitignore | 6 ++ README.md | 25 ++++++ package-lock.json | 90 +++++++++++++++++++++ package.json | 12 +++ playwright.config.ts | 17 ++++ scripts/install-with-proxy.sh | 17 ++++ tests/home-shell.spec.ts | 28 +++++++ tests/moicen-unionid-chain.spec.ts | 27 +++++++ 10 files changed, 277 insertions(+) create mode 100644 .env.e2e.example create mode 100644 .github/workflows/playwright-music-room.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 playwright.config.ts create mode 100755 scripts/install-with-proxy.sh create mode 100644 tests/home-shell.spec.ts create mode 100644 tests/moicen-unionid-chain.spec.ts diff --git a/.env.e2e.example b/.env.e2e.example new file mode 100644 index 0000000..11647b4 --- /dev/null +++ b/.env.e2e.example @@ -0,0 +1,3 @@ +# 复制为 .env.e2e(已 gitignore) +MOICEN_E2E_UNIONID= +HUIKE_FRONT_BASE_URL=https://music-room.moicen.com diff --git a/.github/workflows/playwright-music-room.yml b/.github/workflows/playwright-music-room.yml new file mode 100644 index 0000000..653cff4 --- /dev/null +++ b/.github/workflows/playwright-music-room.yml @@ -0,0 +1,52 @@ +# 仅 npm + Playwright,不 build Rust / 不 Compose;测已部署 music-room H5。 +name: music-room Playwright + +on: + push: + branches: [master, main] + pull_request: + workflow_dispatch: + inputs: + base_url: + description: H5 基址(含协议,无末尾斜杠) + required: false + default: "https://music-room.moicen.com" + type: string + schedule: + - cron: "30 6 * * 2" + +concurrency: + group: huike-e2e-moicen-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + playwright: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + + - name: 解析 H5 基址 + run: | + URL="${{ inputs.base_url }}" + if [ -z "$URL" ]; then URL="https://music-room.moicen.com"; fi + echo "HUIKE_FRONT_BASE_URL=$URL" >> "$GITHUB_ENV" + echo "Using HUIKE_FRONT_BASE_URL=$URL" + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: 依赖与 Chromium + run: | + npm ci + npx playwright install chromium --with-deps + + - name: Playwright + env: + MOICEN_E2E_UNIONID: ${{ secrets.MOICEN_E2E_UNIONID }} + run: npx playwright test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3850c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +playwright-report/ +test-results/ +.env.e2e +*.log +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6ae37c --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# huike-e2e-moicen + +针对 **已部署** 的 `music-room.moicen.com`(或任意 `HUIKE_FRONT_BASE_URL`)跑 **Playwright**,不 clone `huike-front`、不起 Postgres、不编译 Rust。全栈本地 E2E 见 **[alchemy-studio/huike-e2e](https://github.com/alchemy-studio/huike-e2e)**。 + +## 本机 + +```bash +npm ci +npx playwright install chromium +npx playwright test +``` + +走代理安装(默认 `http://localhost:7890`):`npm run install:with-proxy` + +可选:复制 `.env.e2e.example` → `.env.e2e`,填写 `MOICEN_E2E_UNIONID`(勿提交 `.env.e2e`)。未配置时「真实 unionid」用例自动 skip。 + +## GitHub Actions + +在仓库 **Settings → Secrets → Actions** 配置 **`MOICEN_E2E_UNIONID`**(测试用户 `union_id`)后,全链路用例才会执行;否则仅跑壳层与伪造 unionid 用例。 + +`workflow_dispatch` 可改目标 `base_url`;默认定时见 workflow 文件。 + +## 与 moicen 运维文档 + +浏览器联调、在 UC 库查 `union_id` 等:见团队内 **`plan_skills/moicen/moicen-music-room-browser-test-runbook.md`**(与 huiwing-migration / 运维仓同路径即可)。 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8bf7ecf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,90 @@ +{ + "name": "huike-e2e-moicen", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "huike-e2e-moicen", + "devDependencies": { + "@playwright/test": "^1.49.0", + "dotenv": "^16.4.5" + } + }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..facfdbc --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "huike-e2e-moicen", + "private": true, + "scripts": { + "test": "playwright test", + "install:with-proxy": "bash scripts/install-with-proxy.sh" + }, + "devDependencies": { + "@playwright/test": "^1.49.0", + "dotenv": "^16.4.5" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..bec7864 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,17 @@ +import path from 'node:path'; +import { config as loadEnv } from 'dotenv'; +import { defineConfig } from '@playwright/test'; + +loadEnv({ path: path.join(__dirname, '.env.e2e') }); + +export default defineConfig({ + testDir: './tests', + timeout: 120_000, + expect: { timeout: 30_000 }, + use: { + baseURL: + process.env.HUIKE_FRONT_BASE_URL || 'https://music-room.moicen.com', + trace: 'on-first-retry', + }, + reporter: [['list']], +}); diff --git a/scripts/install-with-proxy.sh b/scripts/install-with-proxy.sh new file mode 100755 index 0000000..08d85e6 --- /dev/null +++ b/scripts/install-with-proxy.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# 经本机 HTTP 代理安装 npm 与 Playwright Chromium(默认 http://localhost:7890) +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +PROXY_URL="${HTTP_PROXY:-${HTTPS_PROXY:-http://localhost:7890}}" + +export HTTP_PROXY="$PROXY_URL" +export HTTPS_PROXY="$PROXY_URL" +export ALL_PROXY="${ALL_PROXY:-$PROXY_URL}" +export NO_PROXY="${NO_PROXY:-127.0.0.1,localhost}" + +echo "Using proxy: $HTTP_PROXY" + +cd "$ROOT" +npm ci +npx playwright install chromium --with-deps diff --git a/tests/home-shell.spec.ts b/tests/home-shell.spec.ts new file mode 100644 index 0000000..fbf5627 --- /dev/null +++ b/tests/home-shell.spec.ts @@ -0,0 +1,28 @@ +import { test, expect } from '@playwright/test'; + +// 对已部署 H5:匿名、伪造 unionid、page_path 净化(与 huike-front main.ts 一致) +test.describe('music-room shell', () => { + test('根路径挂载 Vue 根节点', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('#app')).toBeVisible(); + }); + + test('带伪造 unionid/status 的入口不应导致白屏', async ({ page }) => { + await page.goto('/?unionid=fake-wx-unionid-e2e&status=2', { + waitUntil: 'domcontentloaded', + timeout: 30_000, + }); + await expect(page.locator('#app')).toBeVisible({ timeout: 30_000 }); + }); + + test('page_path 内嵌他人 unionid 时应被剥离(最终 URL 不含该串)', async ({ page }) => { + const poison = 'attacker-unionid-e2e-marker'; + const pagePath = encodeURIComponent(`/?unionid=${poison}&status=2`); + await page.goto(`/?page_path=${pagePath}`, { + waitUntil: 'domcontentloaded', + timeout: 30_000, + }); + await page.waitForURL((u) => !u.toString().includes(poison), { timeout: 30_000 }); + await expect(page.locator('#app')).toBeVisible(); + }); +}); diff --git a/tests/moicen-unionid-chain.spec.ts b/tests/moicen-unionid-chain.spec.ts new file mode 100644 index 0000000..53459c1 --- /dev/null +++ b/tests/moicen-unionid-chain.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from '@playwright/test'; + +/** + * 依赖目标站上已存在的用户与 `login2_with_unionid`。 + * 未设 MOICEN_E2E_UNIONID 时 skip(CI 无 Secret 不失败)。 + */ +const moicenUnionid = process.env.MOICEN_E2E_UNIONID?.trim(); + +test.describe('全链路(真实 unionid → UC 换 session)', () => { + test('带 status=2 进入后应出现已登录侧 UI(多角色选身份 / 单角色欢迎语)', async ({ + page, + }) => { + test.skip(!moicenUnionid, '未设置 MOICEN_E2E_UNIONID(本地 .env.e2e 或 GitHub Secret)'); + const q = new URLSearchParams({ + unionid: moicenUnionid!, + status: '2', + }); + await page.goto(`/?${q.toString()}`, { + waitUntil: 'domcontentloaded', + timeout: 30_000, + }); + await expect(page.locator('#app')).toBeVisible({ timeout: 30_000 }); + await expect( + page.getByText(/请选择您的登录身份|欢迎回来/) + ).toBeVisible({ timeout: 90_000 }); + }); +});