import { Page, expect } from '@playwright/test'; export interface ContentData { type: 'news' | 'product' | 'service' | 'case'; title: string; slug: string; excerpt?: string; content?: string; category?: string; tags?: string[]; status?: 'draft' | 'published' | 'archived'; } export class AdminContentPage { constructor(private page: Page) { } async goto() { try { await this.page.goto('/admin/content', { waitUntil: 'domcontentloaded', timeout: 30000 }); } catch (error) { console.log('导航失败,尝试重新加载:', error); try { await this.page.reload({ waitUntil: 'domcontentloaded', timeout: 30000 }); } catch (reloadError) { console.log('重新加载失败:', reloadError); } } await this.page.waitForLoadState('domcontentloaded'); await this.page.waitForSelector('table', { timeout: 10000, state: 'visible' }); await this.page.waitForTimeout(1000); } async gotoCreate() { try { await this.page.goto('/admin/content/new', { waitUntil: 'domcontentloaded', timeout: 30000 }); } catch (error) { console.log('导航到创建页面失败,尝试重新加载:', error); await this.page.reload({ waitUntil: 'domcontentloaded', timeout: 30000 }); } await this.page.waitForLoadState('domcontentloaded'); await this.page.waitForSelector('input[placeholder="请输入标题"]', { timeout: 10000, state: 'visible' }); } async createContent(data: ContentData): Promise { await this.gotoCreate(); await this.page.fill('input[placeholder="请输入标题"]', data.title); await this.page.fill('input[placeholder="url-slug"]', data.slug); if (data.excerpt) { await this.page.fill('textarea', data.excerpt); } if (data.type) { await this.page.locator('select').first().selectOption(data.type); } if (data.status) { await this.page.locator('select').nth(1).selectOption(data.status); } if (data.category) { await this.page.fill('input[placeholder="分类名称"]', data.category); } await this.page.click('button:has-text("发布")'); try { await this.page.waitForURL(/\/admin\/content\/[a-zA-Z0-9]+/, { timeout: 15000 }); } catch { console.log('等待URL跳转失败,当前URL:', this.page.url()); await this.page.waitForTimeout(2000); const currentUrl = this.page.url(); if (currentUrl.includes('/admin/content/')) { console.log('URL已跳转到内容详情页:', currentUrl); } else { console.log('URL未跳转,可能创建失败'); } } await this.page.waitForLoadState('domcontentloaded'); await this.page.waitForTimeout(1000); const url = this.page.url(); console.log('最终URL:', url); const match = url.match(/\/admin\/content\/([a-zA-Z0-9]+)/); const contentId = match ? match[1] : null; if (!contentId) { console.log('无法从URL提取contentId:', url); } return contentId; } async deleteContent(contentId: string) { await this.goto(); const row = this.page.locator(`tr:has-text("${contentId}")`); try { if (await row.count() > 0) { await row.first().locator('button:has-text("删除")').click({ timeout: 5000 }); await this.page.waitForTimeout(500); const confirmButton = this.page.locator('button:has-text("确认"), button:has-text("确定"), button:has-text("删除")').first(); await confirmButton.click({ timeout: 5000 }); await this.page.waitForResponse(resp => resp.url().includes('/api/admin/content') && resp.request().method() === 'DELETE', { timeout: 10000 } ).catch(() => { console.log('删除请求可能已完成'); }); } } catch (error) { console.error(`删除内容失败 (ID: ${contentId}):`, error); } } async deleteContentByTitle(title: string) { try { await this.goto(); const row = this.page.locator(`tr:has-text("${title}")`); if (await row.count() > 0) { await row.first().locator('button[title="删除"]').click({ timeout: 5000 }); await this.page.waitForTimeout(300); await this.page.click('button:has-text("确认删除")', { timeout: 5000 }); await this.page.waitForResponse(resp => resp.url().includes('/api/admin/content') && resp.request().method() === 'DELETE', { timeout: 8000 } ).catch(() => { console.log('删除请求可能已完成'); }); await this.page.waitForTimeout(500); } } catch (error) { console.error(`删除内容失败 (标题: ${title}):`, error); } } async expectContentInList(title: string) { console.log(`检查内容是否在列表中: ${title}`); await this.goto(); await this.page.waitForLoadState('domcontentloaded'); await this.page.waitForTimeout(2000); let row = this.page.locator(`tr:has-text("${title}")`); let isVisible = await row.count() > 0; if (!isVisible) { console.log('内容不在第一页,尝试搜索'); const searchInput = this.page.locator('input[placeholder*="搜索"]'); if (await searchInput.count() > 0) { await searchInput.fill(title); await this.page.keyboard.press('Enter'); await this.page.waitForTimeout(2000); row = this.page.locator(`tr:has-text("${title}")`); isVisible = await row.count() > 0; } } if (!isVisible) { console.log('搜索后仍未找到,尝试刷新页面'); await this.page.reload({ waitUntil: 'domcontentloaded' }); await this.page.waitForSelector('table', { timeout: 10000, state: 'visible' }); await this.page.waitForTimeout(2000); row = this.page.locator(`tr:has-text("${title}")`); isVisible = await row.count() > 0; } if (!isVisible) { const allRows = this.page.locator('table tbody tr'); const rowCount = await allRows.count(); console.log(`列表中共有 ${rowCount} 行内容`); for (let i = 0; i < Math.min(rowCount, 10); i++) { const rowText = await allRows.nth(i).textContent(); console.log(`行 ${i + 1}: ${rowText?.trim().substring(0, 150)}`); } } await expect(row.first()).toBeVisible({ timeout: 15000 }); console.log(`✅ 找到内容: ${title}`); } async expectContentNotInList(title: string) { await this.goto(); const row = this.page.locator(`tr:has-text("${title}")`); await expect(row).not.toBeVisible(); } }