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