dev #5
@@ -0,0 +1,85 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Page, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
export class AdminLoginPage {
|
||||||
|
constructor(private page: Page) {}
|
||||||
|
|
||||||
|
async goto() {
|
||||||
|
await this.page.goto('/admin/login');
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(email: string, password: string) {
|
||||||
|
await this.page.fill('#email', email);
|
||||||
|
await this.page.fill('#password', password);
|
||||||
|
await this.page.click('button[type="submit"]');
|
||||||
|
await this.page.waitForURL(/\/admin(?!\/login)/);
|
||||||
|
}
|
||||||
|
|
||||||
|
async expectLoginSuccess() {
|
||||||
|
await expect(this.page).toHaveURL(/\/admin(?!\/login)/);
|
||||||
|
}
|
||||||
|
|
||||||
|
async expectLoginError() {
|
||||||
|
await expect(this.page.locator('[role="alert"]')).toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Page, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
export interface UserData {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
name?: string;
|
||||||
|
role?: 'admin' | 'editor' | 'viewer';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminUserPage {
|
||||||
|
constructor(private page: Page) {}
|
||||||
|
|
||||||
|
async goto() {
|
||||||
|
await this.page.goto('/admin/users');
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
async createUser(data: UserData) {
|
||||||
|
await this.page.click('button:has-text("新建用户")');
|
||||||
|
await this.page.fill('input[name="email"]', data.email);
|
||||||
|
await this.page.fill('input[name="password"]', data.password);
|
||||||
|
|
||||||
|
if (data.name) {
|
||||||
|
await this.page.fill('input[name="name"]', data.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.role) {
|
||||||
|
await this.page.selectOption('select[name="role"]', data.role);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.page.click('button[type="submit"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
async expectUserInList(email: string) {
|
||||||
|
await this.goto();
|
||||||
|
const row = this.page.locator(`tr:has-text("${email}")`);
|
||||||
|
await expect(row).toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Page, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
export class FrontendNewsPage {
|
||||||
|
constructor(private page: Page) {}
|
||||||
|
|
||||||
|
async goto() {
|
||||||
|
await this.page.goto('/news');
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
async expectNewsVisible(title: string) {
|
||||||
|
const newsCard = this.page.locator(`text="${title}"`);
|
||||||
|
await expect(newsCard).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
async expectNewsNotVisible(title: string) {
|
||||||
|
const newsCard = this.page.locator(`text="${title}"`);
|
||||||
|
await expect(newsCard).not.toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickNews(title: string) {
|
||||||
|
await this.page.locator(`text="${title}"`).click();
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
async expectNewsDetailVisible(content: string) {
|
||||||
|
await expect(this.page.locator(`text=${content}`)).toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Page, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
export class FrontendProductPage {
|
||||||
|
constructor(private page: Page) {}
|
||||||
|
|
||||||
|
async goto() {
|
||||||
|
await this.page.goto('/products');
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
|
||||||
|
async expectProductVisible(title: string) {
|
||||||
|
const productCard = this.page.locator(`text="${title}"`);
|
||||||
|
await expect(productCard).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickProduct(title: string) {
|
||||||
|
await this.page.locator(`text="${title}"`).click();
|
||||||
|
await this.page.waitForLoadState('networkidle');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export { AdminLoginPage } from './AdminLoginPage';
|
||||||
|
export { AdminContentPage, type ContentData } from './AdminContentPage';
|
||||||
|
export { AdminUserPage, type UserData } from './AdminUserPage';
|
||||||
|
export { FrontendNewsPage } from './FrontendNewsPage';
|
||||||
|
export { FrontendProductPage } from './FrontendProductPage';
|
||||||
Reference in New Issue
Block a user