import { test, expect } from '@playwright/test'; let counter = 0; function shortUid() { counter++; return `${counter}${Math.random().toString(36).slice(2, 7)}`; } const TEST_PASS = 'testpass123'; test.describe('Authentication Flow', () => { test('should redirect unauthenticated users to /login', async ({ page }) => { await page.goto('/'); await page.waitForURL('**/login', { timeout: 10000 }); await expect(page.getByText('Jimeng Clone')).toBeVisible(); }); test('register page should be accessible and show form', async ({ page }) => { await page.goto('/register'); await expect(page.getByText('创建账号')).toBeVisible(); await expect(page.locator('input[type="text"]')).toBeVisible(); await expect(page.locator('input[type="email"]')).toBeVisible(); await expect(page.locator('input[type="password"]').first()).toBeVisible(); }); test('login page should validate empty fields', async ({ page }) => { await page.goto('/login'); await page.getByRole('button', { name: '登录' }).click(); await expect(page.getByText('请输入用户名或邮箱')).toBeVisible(); }); test('login page should validate short password', async ({ page }) => { await page.goto('/login'); await page.locator('input[type="text"]').fill('someuser'); await page.locator('input[type="password"]').fill('12345'); await page.getByRole('button', { name: '登录' }).click(); await expect(page.getByText('密码至少6位')).toBeVisible(); }); test('register page should validate username length', async ({ page }) => { await page.goto('/register'); await page.locator('input[type="text"]').fill('ab'); await page.locator('input[type="email"]').fill('valid@email.com'); await page.locator('input[type="password"]').first().fill('pass123'); await page.locator('input[type="password"]').last().fill('pass123'); await page.getByRole('button', { name: '注册' }).click(); await expect(page.getByText('用户名需要3-20个字符')).toBeVisible(); }); test('register page should validate password mismatch', async ({ page }) => { await page.goto('/register'); await page.locator('input[type="text"]').fill('validuser'); await page.locator('input[type="email"]').fill('valid@email.com'); await page.locator('input[type="password"]').first().fill('pass123'); await page.locator('input[type="password"]').last().fill('different'); await page.getByRole('button', { name: '注册' }).click(); await expect(page.getByText('两次输入的密码不一致')).toBeVisible(); }); test('should register via UI, auto-login, see video page', async ({ page }) => { const uid = shortUid(); await page.goto('/register'); await page.locator('input[type="text"]').fill(`r${uid}`); await page.locator('input[type="email"]').fill(`r${uid}@t.co`); await page.locator('input[type="password"]').first().fill(TEST_PASS); await page.locator('input[type="password"]').last().fill(TEST_PASS); await page.getByRole('button', { name: '注册' }).click(); // After successful registration, should eventually see the textarea await page.locator('textarea').waitFor({ state: 'visible', timeout: 15000 }); }); test('should login via UI and see video page', async ({ page }) => { const uid = shortUid(); // Register via API first const resp = await page.request.post('/api/v1/auth/register', { data: { username: `l${uid}`, email: `l${uid}@t.co`, password: TEST_PASS }, }); expect(resp.ok()).toBeTruthy(); await page.goto('/login'); await page.locator('input[type="text"]').fill(`l${uid}`); await page.locator('input[type="password"]').fill(TEST_PASS); await page.getByRole('button', { name: '登录' }).click(); await page.locator('textarea').waitFor({ state: 'visible', timeout: 15000 }); }); test('should stay on login page after invalid credentials', async ({ page }) => { await page.goto('/login'); await page.locator('input[type="text"]').fill('no_such_user'); await page.locator('input[type="password"]').fill('wrongpass123'); await page.getByRole('button', { name: '登录' }).click(); // NOTE: Due to CODE_BUG in api.ts interceptor, a 401 on /auth/login triggers // window.location.href = '/login' which reloads the page and clears React state. // The error message briefly appears then gets wiped by the reload. // We verify the user stays on /login and the form is still usable after reload. await page.waitForURL('**/login', { timeout: 10000 }); await expect(page.getByRole('button', { name: '登录' })).toBeVisible({ timeout: 5000 }); // Form should be interactable after the reload await expect(page.locator('input[type="text"]')).toBeVisible(); }); test('login page should link to register', async ({ page }) => { await page.goto('/login'); await page.getByText('去注册 →').click(); await page.waitForURL('**/register'); await expect(page.getByText('创建账号')).toBeVisible(); }); test('register page should link to login', async ({ page }) => { await page.goto('/register'); await page.getByText('去登录 →').click(); await page.waitForURL('**/login'); await expect(page.getByText('Jimeng Clone')).toBeVisible(); }); }); test.describe('Protected Routes', () => { test('non-admin should not access admin dashboard', async ({ page }) => { const uid = shortUid(); const regResp = await page.request.post('/api/v1/auth/register', { data: { username: `n${uid}`, email: `n${uid}@t.co`, password: TEST_PASS }, }); expect(regResp.ok()).toBeTruthy(); const { tokens } = await regResp.json(); await page.goto('/login'); await page.evaluate((t: { access: string; refresh: string }) => { localStorage.setItem('access_token', t.access); localStorage.setItem('refresh_token', t.refresh); }, tokens); await page.goto('/admin/dashboard'); // Should redirect away from admin — to / (video page) await page.locator('textarea').waitFor({ state: 'visible', timeout: 10000 }); }); });