cda168cf60
新增文件: - e2e/pages/AdminLoginPage.ts - 管理员登录页面对象 - e2e/pages/AdminContentPage.ts - 内容管理页面对象 - e2e/pages/AdminUserPage.ts - 用户管理页面对象 - e2e/pages/FrontendNewsPage.ts - 前端新闻页面对象 - e2e/pages/FrontendProductPage.ts - 前端产品页面对象 - e2e/pages/index.ts - 导出索引文件 功能特性: - 封装页面交互逻辑,减少测试代码重复 - 提供清晰的API接口,提升测试可读性 - 支持内容创建、删除、验证等核心操作 - 统一等待策略,提升测试稳定性
86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
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() {
|
|
await this.page.goto('/admin/content');
|
|
await this.page.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async gotoCreate() {
|
|
await this.page.goto('/admin/content/new');
|
|
await this.page.waitForLoadState('domcontentloaded');
|
|
await this.page.waitForSelector('input[placeholder="请输入标题"]', { timeout: 60000 });
|
|
}
|
|
|
|
async createContent(data: ContentData): Promise<string | null> {
|
|
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("发布")');
|
|
|
|
await this.page.waitForURL(/\/admin\/content\/[a-zA-Z0-9]+/, { timeout: 15000 });
|
|
|
|
const url = this.page.url();
|
|
const match = url.match(/\/admin\/content\/([a-zA-Z0-9]+)/);
|
|
return match ? match[1] : null;
|
|
}
|
|
|
|
async deleteContent(contentId: string) {
|
|
await this.goto();
|
|
const row = this.page.locator(`tr:has-text("${contentId}")`);
|
|
|
|
if (await row.count() > 0) {
|
|
await row.locator('button:has-text("删除")').click();
|
|
await this.page.locator('button:has-text("确认"), button:has-text("确定")').click();
|
|
await this.page.waitForResponse(resp =>
|
|
resp.url().includes('/api/admin/content') &&
|
|
resp.request().method() === 'DELETE',
|
|
{ timeout: 10000 }
|
|
);
|
|
}
|
|
}
|
|
|
|
async expectContentInList(title: string) {
|
|
await this.goto();
|
|
const row = this.page.locator(`tr:has-text("${title}")`);
|
|
await expect(row).toBeVisible();
|
|
}
|
|
|
|
async expectContentNotInList(title: string) {
|
|
await this.goto();
|
|
const row = this.page.locator(`tr:has-text("${title}")`);
|
|
await expect(row).not.toBeVisible();
|
|
}
|
|
}
|