304 lines
10 KiB
TypeScript
304 lines
10 KiB
TypeScript
import { expect } from '@playwright/test';
|
|
import { test } from './helpers/fixtures';
|
|
import { mock } from './helpers/mock';
|
|
|
|
// ─── Shared test data ───────────────────────────────────────────────────────
|
|
|
|
const baseEvent = {
|
|
event_id: 'e1',
|
|
name: '2026 年春季技术峰会',
|
|
subtitle: 'Building the future together',
|
|
type: 'official',
|
|
enable_kyc: false,
|
|
is_joined: false,
|
|
is_checked_in: false,
|
|
start_time: '2026-06-01T09:00:00Z',
|
|
end_time: '2026-06-02T18:00:00Z'
|
|
};
|
|
|
|
const kycEvent = {
|
|
...baseEvent,
|
|
event_id: 'e2',
|
|
name: 'KYC 认证活动',
|
|
enable_kyc: true
|
|
};
|
|
|
|
// Helper: override the event list with the given items.
|
|
async function overrideEventList(items: (typeof baseEvent)[]) {
|
|
await mock.override('GET', '/event/list', {
|
|
status: 200,
|
|
body: { status: 200, data: { items, total: items.length } }
|
|
});
|
|
}
|
|
|
|
// Helper: override event info.
|
|
async function overrideEventInfo(ev: typeof baseEvent) {
|
|
await mock.override('GET', '/event/info', {
|
|
status: 200,
|
|
body: { status: 200, data: ev }
|
|
});
|
|
}
|
|
|
|
// Helper: register a no-op guide override (needed when is_joined: true triggers getEventGuide).
|
|
async function overrideGuideEmpty(eventId: string = 'e1') {
|
|
void eventId; // mock matches by path, not query param
|
|
await mock.override('GET', '/event/guide', {
|
|
status: 200,
|
|
body: { status: 200, data: { attendance_guide: null } }
|
|
});
|
|
}
|
|
|
|
// ─── Tests ──────────────────────────────────────────────────────────────────
|
|
|
|
test('event list renders event names and tab labels', async ({ page, loggedInUser }) => {
|
|
void loggedInUser;
|
|
await overrideEventList([baseEvent, kycEvent]);
|
|
await page.goto('/app/events');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(page.getByText('2026 年春季技术峰会')).toBeVisible();
|
|
await expect(page.getByText('KYC 认证活动')).toBeVisible();
|
|
await expect(page.getByRole('button', { name: '全部活动' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: /已加入/ })).toBeVisible();
|
|
});
|
|
|
|
test('"已加入" tab shows only joined events', async ({ page, loggedInUser }) => {
|
|
void loggedInUser;
|
|
const joinedEvent = { ...baseEvent, event_id: 'ej', name: '已报名活动', is_joined: true };
|
|
await overrideEventList([baseEvent, joinedEvent]);
|
|
await page.goto('/app/events');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Switch to joined tab
|
|
await page.getByRole('button', { name: /已加入/ }).click();
|
|
|
|
await expect(page.getByText('已报名活动')).toBeVisible();
|
|
await expect(page.getByText('2026 年春季技术峰会')).not.toBeVisible();
|
|
});
|
|
|
|
test('event detail renders title, badges, and attendance guide when joined', async ({
|
|
page,
|
|
loggedInUser
|
|
}) => {
|
|
void loggedInUser;
|
|
const guideMd = Buffer.from('# 参会须知\n请携带身份证。').toString('base64');
|
|
await overrideEventInfo({ ...baseEvent, is_joined: true });
|
|
await mock.override('GET', '/event/guide', {
|
|
status: 200,
|
|
body: { status: 200, data: { attendance_guide: guideMd } }
|
|
});
|
|
|
|
await page.goto('/app/events/e1');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(page.getByRole('heading', { name: '2026 年春季技术峰会' })).toBeVisible();
|
|
await expect(page.getByText('已报名')).toBeVisible();
|
|
// Guide card appears
|
|
await expect(page.getByText('参会指南')).toBeVisible();
|
|
await expect(page.getByText('参会须知')).toBeVisible();
|
|
});
|
|
|
|
test('simple join success shows 已报名 badge', async ({ page, loggedInUser }) => {
|
|
void loggedInUser;
|
|
await overrideEventInfo(baseEvent);
|
|
await mock.override('POST', '/event/join', {
|
|
status: 200,
|
|
body: { status: 200, data: { attendance_id: 'att-1' } }
|
|
});
|
|
// After invalidateAll the load re-runs; return is_joined: true.
|
|
// is_joined: true triggers getEventGuide — register that override too.
|
|
await overrideGuideEmpty();
|
|
|
|
await page.goto('/app/events/e1');
|
|
await page.waitForLoadState('networkidle');
|
|
await page.getByRole('button', { name: '立即加入' }).click();
|
|
|
|
// Join dialog appears
|
|
await expect(page.getByText('是否确认要加入活动')).toBeVisible();
|
|
|
|
// Register the post-join event info override just before clicking 加入.
|
|
await mock.override('GET', '/event/info', {
|
|
status: 200,
|
|
body: { status: 200, data: { ...baseEvent, is_joined: true } }
|
|
});
|
|
|
|
await page.getByRole('button', { name: '加入', exact: true }).click();
|
|
|
|
await expect(page.getByText('已报名')).toBeVisible();
|
|
});
|
|
|
|
test('simple join error shows inline error message', async ({ page, loggedInUser }) => {
|
|
void loggedInUser;
|
|
await overrideEventInfo(baseEvent);
|
|
await mock.override('POST', '/event/join', {
|
|
status: 400,
|
|
body: { status: 400, msg: '活动已满' }
|
|
});
|
|
|
|
await page.goto('/app/events/e1');
|
|
await page.waitForLoadState('networkidle');
|
|
await page.getByRole('button', { name: '立即加入' }).click();
|
|
await expect(page.getByText('是否确认要加入活动')).toBeVisible();
|
|
|
|
await page.getByRole('button', { name: '加入', exact: true }).click();
|
|
|
|
await expect(page.getByText('活动已满')).toBeVisible();
|
|
// Dialog remains open
|
|
await expect(page.getByText('是否确认要加入活动')).toBeVisible();
|
|
});
|
|
|
|
test('KYC prompt → method selection navigation', async ({ page, loggedInUser }) => {
|
|
void loggedInUser;
|
|
await overrideEventInfo(kycEvent);
|
|
|
|
await page.goto('/app/events/e2');
|
|
await page.waitForLoadState('networkidle');
|
|
await page.getByRole('button', { name: '立即加入' }).click();
|
|
|
|
// Prompt stage
|
|
await expect(page.getByRole('heading', { name: '需要身份认证' })).toBeVisible();
|
|
await expect(page.getByText('AES-256 加密存储')).toBeVisible();
|
|
|
|
await page.getByRole('button', { name: '下一步' }).click();
|
|
|
|
// Method selection stage
|
|
await expect(page.getByRole('heading', { name: '选择身份认证模式' })).toBeVisible();
|
|
await expect(page.getByText('身份证')).toBeVisible();
|
|
await expect(page.getByText('护照')).toBeVisible();
|
|
});
|
|
|
|
test('KYC synchronous success (cnrid) joins and shows 已报名', async ({ page, loggedInUser }) => {
|
|
void loggedInUser;
|
|
await overrideEventInfo(kycEvent);
|
|
await mock.override('POST', '/kyc/session', {
|
|
status: 200,
|
|
body: { status: 200, data: { status: 'success', kyc_id: 'k1' } }
|
|
});
|
|
await mock.override('POST', '/event/join', {
|
|
status: 200,
|
|
body: { status: 200, data: { attendance_id: 'att-1' } }
|
|
});
|
|
|
|
await page.goto('/app/events/e2');
|
|
await page.waitForLoadState('networkidle');
|
|
await page.getByRole('button', { name: '立即加入' }).click();
|
|
await page.getByRole('button', { name: '下一步' }).click();
|
|
await page.getByText('身份证').click();
|
|
|
|
await page.locator('#kyc-name').fill('张三');
|
|
await page.locator('#kyc-cnrid').fill('110101199003070034');
|
|
|
|
await page.getByRole('button', { name: '开始认证' }).click();
|
|
|
|
// KYC passed synchronously — dialog transitions to success
|
|
await expect(page.getByRole('heading', { name: '成功' })).toBeVisible();
|
|
|
|
// Register post-join overrides before invalidateAll fires on 完成.
|
|
await mock.override('GET', '/event/info', {
|
|
status: 200,
|
|
body: { status: 200, data: { ...kycEvent, is_joined: true } }
|
|
});
|
|
await overrideGuideEmpty();
|
|
|
|
await page.getByRole('button', { name: '完成' }).click();
|
|
|
|
await expect(page.getByText('已报名')).toBeVisible();
|
|
});
|
|
|
|
test('KYC processing → polling → success shows 已报名', async ({ page, loggedInUser }) => {
|
|
void loggedInUser;
|
|
|
|
// Suppress window.open so the redirect URL does not open a real tab.
|
|
await page.addInitScript(() => {
|
|
window.open = () => null;
|
|
});
|
|
|
|
await overrideEventInfo(kycEvent);
|
|
await mock.override('POST', '/kyc/session', {
|
|
status: 200,
|
|
body: {
|
|
status: 200,
|
|
data: { status: 'processing', kyc_id: 'k1', redirect_uri: 'https://example.com/kyc' }
|
|
}
|
|
});
|
|
// The SvelteKit +server.ts polls POST /kyc/query (backend path).
|
|
await mock.override('POST', '/kyc/query', {
|
|
status: 200,
|
|
body: { status: 200, data: { status: 'success' } }
|
|
});
|
|
await mock.override('POST', '/event/join', {
|
|
status: 200,
|
|
body: { status: 200, data: { attendance_id: 'att-1' } }
|
|
});
|
|
|
|
await page.goto('/app/events/e2');
|
|
await page.waitForLoadState('networkidle');
|
|
await page.getByRole('button', { name: '立即加入' }).click();
|
|
await page.getByRole('button', { name: '下一步' }).click();
|
|
await page.getByText('身份证').click();
|
|
|
|
await page.locator('#kyc-name').fill('李四');
|
|
await page.locator('#kyc-cnrid').fill('110101199003070034');
|
|
|
|
await page.getByRole('button', { name: '开始认证' }).click();
|
|
|
|
// Stage → pending (window.open was suppressed)
|
|
await expect(page.getByRole('heading', { name: '等待身份认证结果' })).toBeVisible();
|
|
|
|
// Polling resolves and join completes — dialog transitions to success
|
|
await expect(
|
|
page.getByRole('heading', { name: '成功' }),
|
|
'polling resolves within 5s'
|
|
).toBeVisible({
|
|
timeout: 5000
|
|
});
|
|
|
|
// Register post-join overrides before invalidateAll fires on 完成.
|
|
await mock.override('GET', '/event/info', {
|
|
status: 200,
|
|
body: { status: 200, data: { ...kycEvent, is_joined: true } }
|
|
});
|
|
await overrideGuideEmpty();
|
|
|
|
await page.getByRole('button', { name: '完成' }).click();
|
|
await expect(page.getByText('已报名')).toBeVisible();
|
|
});
|
|
|
|
test('KYC failed shows 失败 stage', async ({ page, loggedInUser }) => {
|
|
void loggedInUser;
|
|
|
|
await page.addInitScript(() => {
|
|
window.open = () => null;
|
|
});
|
|
|
|
await overrideEventInfo(kycEvent);
|
|
await mock.override('POST', '/kyc/session', {
|
|
status: 200,
|
|
body: {
|
|
status: 200,
|
|
data: { status: 'processing', kyc_id: 'k1', redirect_uri: 'https://example.com/kyc' }
|
|
}
|
|
});
|
|
// Poll returns failed immediately.
|
|
await mock.override('POST', '/kyc/query', {
|
|
status: 200,
|
|
body: { status: 200, data: { status: 'failed' } }
|
|
});
|
|
|
|
await page.goto('/app/events/e2');
|
|
await page.waitForLoadState('networkidle');
|
|
await page.getByRole('button', { name: '立即加入' }).click();
|
|
await page.getByRole('button', { name: '下一步' }).click();
|
|
await page.getByText('身份证').click();
|
|
|
|
await page.locator('#kyc-name').fill('王五');
|
|
await page.locator('#kyc-cnrid').fill('110101199003070034');
|
|
|
|
await page.getByRole('button', { name: '开始认证' }).click();
|
|
|
|
await expect(page.getByRole('heading', { name: '等待身份认证结果' })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: '失败' }), 'poll fails within 5s').toBeVisible({
|
|
timeout: 5000
|
|
});
|
|
});
|