test: guest onboarding, logged-in isolation, optional real_name
Add guest-onboarding and logged-in-and-isolation specs; remove moicen-unionid-chain; MOICEN_E2E_EXPECTED_REAL_NAME for 欢迎回来+姓名; CI secret; workers=1; longer goto timeouts. Made-with: Cursor
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
# 复制为 .env.e2e(已 gitignore)
|
||||
MOICEN_E2E_UNIONID=
|
||||
# 可选:与 UC 中 real_name 一致,用于断言「欢迎回来,{姓名}」(如 阿难)
|
||||
MOICEN_E2E_EXPECTED_REAL_NAME=
|
||||
HUIKE_FRONT_BASE_URL=https://music-room.moicen.com
|
||||
|
||||
@@ -50,4 +50,5 @@ jobs:
|
||||
- name: Playwright
|
||||
env:
|
||||
MOICEN_E2E_UNIONID: ${{ secrets.MOICEN_E2E_UNIONID }}
|
||||
MOICEN_E2E_EXPECTED_REAL_NAME: ${{ secrets.MOICEN_E2E_EXPECTED_REAL_NAME }}
|
||||
run: npx playwright test
|
||||
|
||||
@@ -12,11 +12,21 @@ npx playwright test
|
||||
|
||||
走代理安装(默认 `http://localhost:7890`):`npm run install:with-proxy`
|
||||
|
||||
可选:复制 `.env.e2e.example` → `.env.e2e`,填写 `MOICEN_E2E_UNIONID`(勿提交 `.env.e2e`)。未配置时「真实 unionid」用例自动 skip。
|
||||
可选:复制 `.env.e2e.example` → `.env.e2e`,填写 **`MOICEN_E2E_UNIONID`**;若要断言首页展示 **本人姓名**,再加 **`MOICEN_E2E_EXPECTED_REAL_NAME`**(与库 `hty_users.real_name` 一致,如 `阿难`)。勿提交 `.env.e2e`。
|
||||
|
||||
用例概览:
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `tests/guest-onboarding.spec.ts` | 未登录:仅见「请返回微信小程序完成登录」,不出现已登录工作台 |
|
||||
| `tests/home-shell.spec.ts` | `#app`、伪造 unionid 不白屏、`page_path` 净化 |
|
||||
| `tests/logged-in-and-isolation.spec.ts` | 需 Secret:登录后非访客态;URL 异主 unionid 剥离(串号防护);可选姓名断言 |
|
||||
|
||||
未配置 `MOICEN_E2E_UNIONID` 时,`logged-in-and-isolation` 内用例全部 skip。
|
||||
|
||||
## GitHub Actions
|
||||
|
||||
在仓库 **Settings → Secrets → Actions** 配置 **`MOICEN_E2E_UNIONID`**(测试用户 `union_id`)后,全链路用例才会执行;否则仅跑壳层与伪造 unionid 用例。
|
||||
在 **Settings → Secrets → Actions** 配置 **`MOICEN_E2E_UNIONID`**(测试用户 `union_id`)。可选再配 **`MOICEN_E2E_EXPECTED_REAL_NAME`**,否则「欢迎回来 + 姓名」用例 skip。未配 unionid 时仅跑访客与壳层用例。
|
||||
|
||||
`workflow_dispatch` 可改目标 `base_url`;**默认定时:每天 06:30 UTC**(见 `.github/workflows/playwright-music-room.yml`)。
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ loadEnv({ path: path.join(__dirname, '.env.e2e') });
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
timeout: 120_000,
|
||||
workers: 1,
|
||||
expect: { timeout: 30_000 },
|
||||
use: {
|
||||
baseURL:
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* 未登录(无 token、无有效 unionid 登录链):与 `huike-front` `index.vue` 一致,
|
||||
* 根路径展示「请返回微信小程序完成登录」占位(产品侧常称欢迎/引导注册流程)。
|
||||
*/
|
||||
test.describe('未登录访客', () => {
|
||||
test('新浏览器上下文访问 / 只看到小程序登录引导,不出现已登录工作台', async ({
|
||||
browser,
|
||||
}) => {
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
||||
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
||||
await expect(
|
||||
page.getByText('请返回微信小程序完成登录')
|
||||
).toBeVisible({ timeout: 60_000 });
|
||||
await expect(page.getByText('欢迎回来')).not.toBeVisible();
|
||||
await expect(page.getByText('请选择您的登录身份')).not.toBeVisible();
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test('未带 status=2 时访问 / 仍为访客引导(非 Registered 链)', async ({
|
||||
browser,
|
||||
}) => {
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
||||
await expect(
|
||||
page.getByText('请返回微信小程序完成登录')
|
||||
).toBeVisible({ timeout: 60_000 });
|
||||
await context.close();
|
||||
});
|
||||
});
|
||||
@@ -3,16 +3,16 @@ 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();
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
||||
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
||||
});
|
||||
|
||||
test('带伪造 unionid/status 的入口不应导致白屏', async ({ page }) => {
|
||||
await page.goto('/?unionid=fake-wx-unionid-e2e&status=2', {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 30_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
await expect(page.locator('#app')).toBeVisible({ timeout: 30_000 });
|
||||
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
||||
});
|
||||
|
||||
test('page_path 内嵌他人 unionid 时应被剥离(最终 URL 不含该串)', async ({ page }) => {
|
||||
@@ -20,9 +20,9 @@ test.describe('music-room shell', () => {
|
||||
const pagePath = encodeURIComponent(`/?unionid=${poison}&status=2`);
|
||||
await page.goto(`/?page_path=${pagePath}`, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 30_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
await page.waitForURL((u) => !u.toString().includes(poison), { timeout: 30_000 });
|
||||
await page.waitForURL((u) => !u.toString().includes(poison), { timeout: 60_000 });
|
||||
await expect(page.locator('#app')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const moicenUnionid = process.env.MOICEN_E2E_UNIONID?.trim();
|
||||
const expectedRealName = process.env.MOICEN_E2E_EXPECTED_REAL_NAME?.trim();
|
||||
|
||||
test.describe('已登录用户与串号防护', () => {
|
||||
test.skip(!moicenUnionid, '需要 MOICEN_E2E_UNIONID(Secret 或 .env.e2e)');
|
||||
|
||||
test('login2_with_unionid 后不应停留在未登录占位,应出现身份或工作台', async ({
|
||||
page,
|
||||
}) => {
|
||||
const q = new URLSearchParams({
|
||||
unionid: moicenUnionid!,
|
||||
status: '2',
|
||||
});
|
||||
await page.goto(`/?${q.toString()}`, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 60_000,
|
||||
});
|
||||
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
||||
await expect(
|
||||
page.getByText('请返回微信小程序完成登录')
|
||||
).not.toBeVisible({ timeout: 90_000 });
|
||||
await expect(
|
||||
page.getByText(/请选择您的登录身份|欢迎回来|进入工作台/)
|
||||
).toBeVisible({ timeout: 90_000 });
|
||||
});
|
||||
|
||||
test('已登录后 URL 中异主 unionid 应被剥离(不串号)', async ({ page }) => {
|
||||
const q = new URLSearchParams({
|
||||
unionid: moicenUnionid!,
|
||||
status: '2',
|
||||
});
|
||||
await page.goto(`/?${q.toString()}`, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 60_000,
|
||||
});
|
||||
await expect(
|
||||
page.getByText(/请选择您的登录身份|欢迎回来|进入工作台/)
|
||||
).toBeVisible({ timeout: 90_000 });
|
||||
|
||||
const poisonUnionid = 'e2eStrangerUnionidNotCurrentUser001';
|
||||
const pathBefore = new URL(page.url()).pathname || '/';
|
||||
await page.goto(
|
||||
`${pathBefore}?unionid=${encodeURIComponent(poisonUnionid)}&status=2`,
|
||||
{ waitUntil: 'domcontentloaded', timeout: 60_000 }
|
||||
);
|
||||
await page.waitForURL(
|
||||
(u) => !u.toString().includes(poisonUnionid),
|
||||
{ timeout: 45_000 }
|
||||
);
|
||||
expect(page.url()).not.toContain(poisonUnionid);
|
||||
await expect(
|
||||
page.getByText('请返回微信小程序完成登录')
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('可选:工作台「欢迎回来」展示配置的真实姓名(自己的数据)', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip(
|
||||
!expectedRealName,
|
||||
'未设置 MOICEN_E2E_EXPECTED_REAL_NAME 时跳过(与库中 real_name 一致,如 阿难)'
|
||||
);
|
||||
const q = new URLSearchParams({
|
||||
unionid: moicenUnionid!,
|
||||
status: '2',
|
||||
});
|
||||
await page.goto(`/?${q.toString()}`, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 60_000,
|
||||
});
|
||||
if (await page.getByText('请选择您的登录身份').isVisible().catch(() => false)) {
|
||||
await page.locator('.van-grid-item').first().click();
|
||||
}
|
||||
await expect(page.getByText('欢迎回来')).toBeVisible({ timeout: 120_000 });
|
||||
await expect(page.getByText(expectedRealName!)).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
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