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
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# 复制为 .env.e2e(已 gitignore)
|
||||
MOICEN_E2E_UNIONID=
|
||||
HUIKE_FRONT_BASE_URL=https://music-room.moicen.com
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
playwright-report/
|
||||
test-results/
|
||||
.env.e2e
|
||||
*.log
|
||||
.DS_Store
|
||||
@@ -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 / 运维仓同路径即可)。
|
||||
Generated
+90
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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']],
|
||||
});
|
||||
Executable
+17
@@ -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
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user