Initial commit: Pixel AI comic/video creation platform
- FastAPI backend with SQLModel, Alembic migrations, AgentScope agents - Next.js 15 frontend with React 19, Tailwind, Zustand, React Flow - Multi-provider AI system (DashScope, Kling, MiniMax, Volcengine, OpenAI, etc.) - All HTTP clients migrated from sync requests to async httpx - Admin-managed API keys via environment variables - SSRF vulnerability fixed in ensure_url()
This commit is contained in:
227
frontend/tests/e2e/auth.spec.ts
Normal file
227
frontend/tests/e2e/auth.spec.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Authentication Tests based on feature_list.json
|
||||
* Covers test cases: 5, 7-8, 14-16, 31-34, 35-37
|
||||
*/
|
||||
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:3000';
|
||||
|
||||
// Helper to login
|
||||
async function login(page: Page) {
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await page.fill('#username', 'testuser');
|
||||
await page.fill('#password', 'Test123456');
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(`${BASE_URL}/`, { timeout: 10000 });
|
||||
}
|
||||
|
||||
test.describe('Authentication Flow', () => {
|
||||
|
||||
// Test 5: 登录成功
|
||||
test('should login successfully with valid credentials', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
|
||||
// Fill in credentials using id selectors
|
||||
await page.fill('#username', 'testuser');
|
||||
await page.fill('#password', 'Test123456');
|
||||
|
||||
// Click login and check loading state
|
||||
const loginButton = page.locator('button[type="submit"]');
|
||||
await loginButton.click();
|
||||
|
||||
// Verify loading state
|
||||
await expect(loginButton).toContainText('登录中');
|
||||
|
||||
// Wait for navigation
|
||||
await page.waitForURL(`${BASE_URL}/`, { timeout: 10000 });
|
||||
|
||||
// Verify redirected to home
|
||||
await expect(page).toHaveURL(`${BASE_URL}/`);
|
||||
|
||||
// Verify user avatar is visible in navbar
|
||||
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
|
||||
await expect(avatar).toBeVisible();
|
||||
});
|
||||
|
||||
// Test 7: 已登录用户访问登录页自动重定向
|
||||
test('should redirect logged-in user from login page to home', async ({ page }) => {
|
||||
await login(page);
|
||||
|
||||
// Try to access login page again
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
|
||||
// Should be redirected to home
|
||||
await expect(page).toHaveURL(`${BASE_URL}/`);
|
||||
});
|
||||
|
||||
// Test 8: 登录后重定向到原页面
|
||||
test('should redirect to original page after login', async ({ page }) => {
|
||||
// Access protected page while logged out
|
||||
await page.goto(`${BASE_URL}/canvas`);
|
||||
|
||||
// Should be redirected to login with redirect param
|
||||
await expect(page).toHaveURL(/.*login.*/);
|
||||
await expect(page).toHaveURL(/.*redirect=.*/);
|
||||
|
||||
// Login
|
||||
await page.fill('#username', 'testuser');
|
||||
await page.fill('#password', 'Test123456');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Should redirect back to original page
|
||||
await page.waitForURL(`${BASE_URL}/canvas`, { timeout: 10000 });
|
||||
await expect(page).toHaveURL(`${BASE_URL}/canvas`);
|
||||
});
|
||||
|
||||
// Test 14: 注册失败 - 用户名已存在
|
||||
test('should show error for existing username during registration', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/register`);
|
||||
|
||||
await page.fill('#username', 'testuser');
|
||||
await page.fill('#email', 'unique@example.com');
|
||||
await page.fill('#password', 'Test123456');
|
||||
await page.fill('#confirmPassword', 'Test123456');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Should show error about existing user
|
||||
await expect(page.locator('text=/already registered|已注册|用户名已存在|Username already exists/i').first()).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
// Test 15: 注册成功
|
||||
test('should register successfully with new user', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/register`);
|
||||
|
||||
const timestamp = Date.now();
|
||||
await page.fill('#username', `newuser${timestamp}`);
|
||||
await page.fill('#email', `newuser${timestamp}@example.com`);
|
||||
await page.fill('#password', 'Test123456');
|
||||
await page.fill('#confirmPassword', 'Test123456');
|
||||
|
||||
const registerButton = page.locator('button[type="submit"]');
|
||||
await registerButton.click();
|
||||
|
||||
// Verify loading state
|
||||
await expect(registerButton).toContainText('注册中');
|
||||
|
||||
// Wait for navigation to home
|
||||
await page.waitForURL(`${BASE_URL}/`, { timeout: 10000 });
|
||||
await expect(page).toHaveURL(`${BASE_URL}/`);
|
||||
|
||||
// Verify avatar is visible
|
||||
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
|
||||
await expect(avatar).toBeVisible();
|
||||
});
|
||||
|
||||
// Test 16: 已登录用户访问注册页自动重定向
|
||||
test('should redirect logged-in user from register page to home', async ({ page }) => {
|
||||
await login(page);
|
||||
|
||||
// Try to access register page
|
||||
await page.goto(`${BASE_URL}/register`);
|
||||
|
||||
// Should be redirected to home
|
||||
await expect(page).toHaveURL(`${BASE_URL}/`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('User Menu', () => {
|
||||
|
||||
// Test 31: 已登录状态显示用户头像
|
||||
test('should display user avatar when logged in', async ({ page }) => {
|
||||
await login(page);
|
||||
|
||||
// Check avatar is visible
|
||||
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
|
||||
await expect(avatar).toBeVisible();
|
||||
|
||||
// Check it shows first letter of username
|
||||
await expect(avatar).toContainText('T'); // 'testuser' starts with T
|
||||
});
|
||||
|
||||
// Test 32: 点击头像显示下拉菜单
|
||||
test('should show dropdown menu when clicking avatar', async ({ page }) => {
|
||||
await login(page);
|
||||
|
||||
// Click avatar
|
||||
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
|
||||
await avatar.click();
|
||||
|
||||
// Verify dropdown shows username and email
|
||||
await expect(page.locator('text=testuser').first()).toBeVisible();
|
||||
await expect(page.locator('text=/API Keys|API 密钥/i').first()).toBeVisible();
|
||||
await expect(page.locator('text=/Log out|退出|登出/i').first()).toBeVisible();
|
||||
});
|
||||
|
||||
// Test 33: 点击 API Keys 菜单项跳转
|
||||
test('should navigate to API Keys page from dropdown', async ({ page }) => {
|
||||
await login(page);
|
||||
|
||||
// Click avatar
|
||||
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
|
||||
await avatar.click();
|
||||
|
||||
// Click API Keys
|
||||
await page.click('text=/API Keys|API 密钥/i');
|
||||
|
||||
// Should navigate to api-keys page
|
||||
await expect(page).toHaveURL(`${BASE_URL}/canvas`);
|
||||
});
|
||||
|
||||
// Test 34: 点击 Log out 登出
|
||||
test('should logout when clicking logout', async ({ page }) => {
|
||||
await login(page);
|
||||
|
||||
// Click avatar
|
||||
const avatar = page.locator('[data-testid="user-avatar"]').or(page.locator('.avatar')).first();
|
||||
await avatar.click();
|
||||
|
||||
// Click logout
|
||||
await page.click('text=/Log out|退出|登出/i');
|
||||
|
||||
// Should redirect to login or home
|
||||
await page.waitForLoadState('networkidle');
|
||||
const url = page.url();
|
||||
expect(url.includes('/login') || url === `${BASE_URL}/`).toBeTruthy();
|
||||
|
||||
// Verify login icon is shown (user is logged out)
|
||||
const loginIcon = page.locator('[data-testid="login-icon"]').or(page.locator('a[href="/login"]')).first();
|
||||
await expect(loginIcon).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Route Protection', () => {
|
||||
|
||||
// Test 35: 访问 /settings/* 需要登录
|
||||
test('should redirect to login when accessing settings without auth', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/canvas`);
|
||||
|
||||
// Should be redirected to login
|
||||
await expect(page).toHaveURL(/.*login.*/);
|
||||
});
|
||||
|
||||
// Test 36: 已登录用户访问登录/注册页重定向
|
||||
test('should redirect authenticated users from auth pages', async ({ page }) => {
|
||||
await login(page);
|
||||
|
||||
// Try accessing login
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await expect(page).toHaveURL(`${BASE_URL}/`);
|
||||
|
||||
// Try accessing register
|
||||
await page.goto(`${BASE_URL}/register`);
|
||||
await expect(page).toHaveURL(`${BASE_URL}/`);
|
||||
});
|
||||
|
||||
// Test 37: 访问公开页面无需登录
|
||||
test('should allow access to public pages without auth', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/`);
|
||||
|
||||
// Should stay on home page
|
||||
await expect(page).toHaveURL(`${BASE_URL}/`);
|
||||
|
||||
// Should not redirect to login
|
||||
await expect(page).not.toHaveURL(/.*login.*/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user