import { test, expect } from '@playwright/test'; test.describe('Gallery Page E2E Tests', () => { const testUser = { email: 'e2e-test@meteor.dev', password: 'TestPassword123', displayName: 'E2E Test User' }; test.beforeEach(async ({ page }) => { // 导航到首页 await page.goto('/'); }); test('should redirect to login when accessing gallery without authentication', async ({ page }) => { // 直接访问gallery页面 await page.goto('/gallery'); // 应该被重定向到登录页面 await expect(page).toHaveURL('/login'); // 检查登录页面元素 await expect(page.locator('h1')).toContainText('登录'); }); test('should display gallery page after successful login', async ({ page }) => { // 先注册用户 await page.goto('/register'); await page.fill('input[name="email"]', testUser.email); await page.fill('input[name="password"]', testUser.password); await page.fill('input[name="displayName"]', testUser.displayName); await page.click('button[type="submit"]'); // 等待重定向到dashboard await expect(page).toHaveURL('/dashboard'); // 访问gallery页面 await page.goto('/gallery'); // 检查gallery页面元素 await expect(page.locator('h1')).toContainText('Event Gallery'); // 检查空状态消息 await expect(page.locator('text=No events captured yet')).toBeVisible(); }); test('should display loading state initially', async ({ page }) => { // 登录用户 await loginUser(page, testUser); // 访问gallery页面 await page.goto('/gallery'); // 检查loading状态(可能很快消失,所以用waitFor) const loadingText = page.locator('text=Loading events...'); if (await loadingText.isVisible()) { await expect(loadingText).toBeVisible(); } // 最终应该显示空状态或事件列表 await expect(page.locator('h1')).toContainText('Event Gallery'); }); test('should handle API errors gracefully', async ({ page }) => { // 模拟API错误 await page.route('**/api/v1/events**', route => { route.fulfill({ status: 500, body: JSON.stringify({ error: 'Server Error' }) }); }); // 登录用户 await loginUser(page, testUser); // 访问gallery页面 await page.goto('/gallery'); // 检查错误消息 await expect(page.locator('text=Error loading events')).toBeVisible(); }); test('should display events when API returns data', async ({ page }) => { // 模拟API返回测试数据 const mockEvents = { data: [ { id: 'test-event-1', deviceId: 'device-1', eventType: 'meteor', capturedAt: '2024-01-01T12:00:00Z', mediaUrl: 'https://example.com/test1.jpg', isValid: true, createdAt: '2024-01-01T12:00:00Z', metadata: { location: 'Test Location 1' } }, { id: 'test-event-2', deviceId: 'device-1', eventType: 'satellite', capturedAt: '2024-01-01T11:00:00Z', mediaUrl: 'https://example.com/test2.jpg', isValid: true, createdAt: '2024-01-01T11:00:00Z', metadata: { location: 'Test Location 2' } } ], nextCursor: null }; await page.route('**/api/v1/events**', route => { route.fulfill({ status: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(mockEvents) }); }); // 登录用户 await loginUser(page, testUser); // 访问gallery页面 await page.goto('/gallery'); // 检查事件卡片 await expect(page.locator('[data-testid="event-card"]').first()).toBeVisible(); await expect(page.locator('text=Type: meteor')).toBeVisible(); await expect(page.locator('text=Type: satellite')).toBeVisible(); await expect(page.locator('text=Location: Test Location 1')).toBeVisible(); await expect(page.locator('text=Location: Test Location 2')).toBeVisible(); }); test('should implement infinite scroll', async ({ page }) => { // 模拟分页数据 let pageCount = 0; await page.route('**/api/v1/events**', route => { const url = new URL(route.request().url()); const cursor = url.searchParams.get('cursor'); if (!cursor) { // 第一页 pageCount = 1; route.fulfill({ status: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: Array.from({ length: 5 }, (_, i) => ({ id: `event-page1-${i}`, deviceId: 'device-1', eventType: 'meteor', capturedAt: new Date(Date.now() - i * 60000).toISOString(), mediaUrl: `https://example.com/page1-${i}.jpg`, isValid: true, createdAt: new Date().toISOString() })), nextCursor: 'page2-cursor' }) }); } else if (cursor === 'page2-cursor') { // 第二页 pageCount = 2; route.fulfill({ status: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: Array.from({ length: 3 }, (_, i) => ({ id: `event-page2-${i}`, deviceId: 'device-1', eventType: 'satellite', capturedAt: new Date(Date.now() - (i + 5) * 60000).toISOString(), mediaUrl: `https://example.com/page2-${i}.jpg`, isValid: true, createdAt: new Date().toISOString() })), nextCursor: null }) }); } }); // 登录用户 await loginUser(page, testUser); // 访问gallery页面 await page.goto('/gallery'); // 等待第一页加载 await expect(page.locator('[data-testid="event-card"]')).toHaveCount(5); // 滚动到底部触发infinite scroll await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight); }); // 等待第二页加载 await expect(page.locator('[data-testid="event-card"]')).toHaveCount(8); // 检查loading more状态 const loadingMore = page.locator('text=Loading more events...'); if (await loadingMore.isVisible()) { await expect(loadingMore).toBeVisible(); } }); test('should be responsive on mobile devices', async ({ page }) => { // 设置移动设备视口 await page.setViewportSize({ width: 375, height: 667 }); // 模拟事件数据 await page.route('**/api/v1/events**', route => { route.fulfill({ status: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: [{ id: 'mobile-test-event', deviceId: 'device-1', eventType: 'meteor', capturedAt: '2024-01-01T12:00:00Z', mediaUrl: 'https://example.com/mobile-test.jpg', isValid: true, createdAt: '2024-01-01T12:00:00Z' }], nextCursor: null }) }); }); // 登录用户 await loginUser(page, testUser); // 访问gallery页面 await page.goto('/gallery'); // 检查移动端布局 await expect(page.locator('h1')).toContainText('Event Gallery'); await expect(page.locator('[data-testid="event-card"]')).toBeVisible(); // 检查网格布局在移动端的响应性 const grid = page.locator('[data-testid="gallery-grid"]'); if (await grid.isVisible()) { const gridColumns = await grid.evaluate(el => window.getComputedStyle(el).gridTemplateColumns ); // 移动端应该是单列 expect(gridColumns).toContain('1fr'); } }); // 辅助函数:登录用户 async function loginUser(page: any, user: typeof testUser) { await page.goto('/login'); await page.fill('input[name="email"]', user.email); await page.fill('input[name="password"]', user.password); await page.click('button[type="submit"]'); await expect(page).toHaveURL('/dashboard'); } });