refactor: 删除旧的E2E测试文件
删除文件: - e2e/admin-frontend-interaction.spec.ts - e2e/admin-publish-core.spec.ts - e2e/admin-publish.spec.ts - e2e/website-acceptance.spec.ts 原因: - 已被新的测试架构替代 - 新架构采用Page Object Model模式 - 新架构有更清晰的测试分类(smoke/journeys/features) - 新架构提供更好的可维护性和可扩展性 新测试架构: - smoke/ - 冒烟测试 - journeys/ - 用户旅程测试 - features/ - 功能测试 - pages/ - Page Object Model - fixtures/ - 测试固件
This commit is contained in:
@@ -1,332 +0,0 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
||||
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@novalon.cn';
|
||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123456';
|
||||
|
||||
test.describe('后台与前台页面交互测试', () => {
|
||||
test('首页展示所有内容类型入口', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const navLinks = page.locator('nav a, header a[href]');
|
||||
const count = await navLinks.count();
|
||||
|
||||
console.log(`首页导航链接数量: ${count}`);
|
||||
|
||||
expect(count).toBeGreaterThan(0);
|
||||
|
||||
const linkTexts = await navLinks.allTextContents();
|
||||
console.log('导航链接:', linkTexts);
|
||||
});
|
||||
|
||||
test('新闻页面内容展示', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/\/news/);
|
||||
|
||||
const mainContent = page.locator('main, [role="main"]');
|
||||
await expect(mainContent).toBeVisible();
|
||||
|
||||
const heading = page.locator('h1, h2').first();
|
||||
const hasHeading = await heading.isVisible().catch(() => false);
|
||||
console.log(`新闻页面标题${hasHeading ? '存在' : '不存在'}`);
|
||||
});
|
||||
|
||||
test('产品页面内容展示', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/products`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/\/products/);
|
||||
|
||||
const mainContent = page.locator('main, [role="main"]');
|
||||
await expect(mainContent).toBeVisible();
|
||||
});
|
||||
|
||||
test('服务页面内容展示', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/services`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/\/services/);
|
||||
|
||||
const mainContent = page.locator('main, [role="main"]');
|
||||
await expect(mainContent).toBeVisible();
|
||||
});
|
||||
|
||||
test('案例页面内容展示', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/cases`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/\/cases/);
|
||||
|
||||
const mainContent = page.locator('main, [role="main"]');
|
||||
await expect(mainContent).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('后台内容管理功能测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/login`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const emailInput = page.locator('#email');
|
||||
const passwordInput = page.locator('#password');
|
||||
const submitButton = page.locator('button[type="submit"]');
|
||||
|
||||
await emailInput.fill(ADMIN_EMAIL);
|
||||
await passwordInput.fill(ADMIN_PASSWORD);
|
||||
await submitButton.click();
|
||||
|
||||
await page.waitForURL(/\/admin(?!\/login)/, { timeout: 15000 });
|
||||
});
|
||||
|
||||
test('后台仪表盘加载', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const heading = page.locator('h1, .text-2xl').first();
|
||||
await expect(heading).toBeVisible();
|
||||
|
||||
console.log('后台仪表盘加载成功');
|
||||
});
|
||||
|
||||
test('后台内容列表页面加载', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('table');
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
const rows = page.locator('tbody tr');
|
||||
const count = await rows.count();
|
||||
console.log(`后台内容列表数量: ${count}`);
|
||||
});
|
||||
|
||||
test('后台新建内容页面表单完整性', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/content/new`);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForSelector('input[placeholder="请输入标题"]', { timeout: 60000 });
|
||||
|
||||
const titleInput = page.locator('input[placeholder="请输入标题"]');
|
||||
await expect(titleInput).toBeVisible();
|
||||
|
||||
const slugInput = page.locator('input[placeholder="url-slug"]');
|
||||
await expect(slugInput).toBeVisible();
|
||||
|
||||
const typeSelect = page.locator('select').first();
|
||||
await expect(typeSelect).toBeVisible();
|
||||
|
||||
const categoryInput = page.locator('input[placeholder="分类名称"]');
|
||||
const hasCategory = await categoryInput.isVisible().catch(() => false);
|
||||
console.log(`分类输入框${hasCategory ? '存在' : '不存在'}`);
|
||||
|
||||
const publishButton = page.locator('button:has-text("发布")');
|
||||
await expect(publishButton).toBeVisible();
|
||||
|
||||
const saveDraftButton = page.locator('button:has-text("保存草稿"), button:has-text("保存")');
|
||||
await expect(saveDraftButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('后台内容编辑页面加载', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const rows = page.locator('tbody tr');
|
||||
const count = await rows.count();
|
||||
|
||||
if (count > 0) {
|
||||
const firstEditLink = page.locator('tbody tr:first-child a[href*="/admin/content/"]').first();
|
||||
const hasEditLink = await firstEditLink.isVisible().catch(() => false);
|
||||
|
||||
if (hasEditLink) {
|
||||
await firstEditLink.click();
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const titleInput = page.locator('input[placeholder="请输入标题"]');
|
||||
await expect(titleInput).toBeVisible({ timeout: 30000 });
|
||||
|
||||
console.log('编辑页面加载成功');
|
||||
} else {
|
||||
console.log('没有可编辑的内容');
|
||||
}
|
||||
} else {
|
||||
console.log('内容列表为空');
|
||||
}
|
||||
});
|
||||
|
||||
test('后台内容分类管理', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/categories`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const heading = page.locator('h1, .text-2xl').first();
|
||||
const hasHeading = await heading.isVisible().catch(() => false);
|
||||
|
||||
console.log(`分类管理页面${hasHeading ? '可访问' : '不存在或无权限'}`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('内容导航和链接测试', () => {
|
||||
test('导航到不同内容类型页面', async ({ page }) => {
|
||||
const pages = [
|
||||
{ url: '/news', name: '新闻' },
|
||||
{ url: '/products', name: '产品' },
|
||||
{ url: '/services', name: '服务' },
|
||||
{ url: '/cases', name: '案例' },
|
||||
];
|
||||
|
||||
for (const p of pages) {
|
||||
await page.goto(`${BASE_URL}${p.url}`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const url = page.url();
|
||||
console.log(`${p.name}页面: ${url.includes(p.url) ? '可访问' : '不可访问'}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('内容详情页访问', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const links = page.locator('a[href*="/news/"]');
|
||||
const count = await links.count();
|
||||
|
||||
if (count > 0) {
|
||||
const firstLink = links.first();
|
||||
const href = await firstLink.getAttribute('href');
|
||||
|
||||
if (href && !href.startsWith('http')) {
|
||||
await page.goto(`${BASE_URL}${href}`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const mainContent = page.locator('main, article');
|
||||
const isVisible = await mainContent.isVisible().catch(() => false);
|
||||
console.log(`详情页加载${isVisible ? '成功' : '失败'}`);
|
||||
}
|
||||
} else {
|
||||
console.log('没有可访问的新闻详情链接');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('SEO和元数据测试', () => {
|
||||
test('页面标题验证', async ({ page }) => {
|
||||
const pages = [
|
||||
{ url: '/', name: '首页' },
|
||||
{ url: '/news', name: '新闻' },
|
||||
{ url: '/products', name: '产品' },
|
||||
];
|
||||
|
||||
for (const p of pages) {
|
||||
await page.goto(`${BASE_URL}${p.url}`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const title = await page.title();
|
||||
console.log(`${p.name}标题: ${title}`);
|
||||
|
||||
expect(title.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('Meta描述标签验证', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const metaDesc = page.locator('meta[name="description"]');
|
||||
const hasMetaDesc = await metaDesc.count();
|
||||
|
||||
console.log(`Meta描述标签${hasMetaDesc > 0 ? '存在' : '不存在'}`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('响应式导航测试', () => {
|
||||
test('移动端导航菜单', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const menuButton = page.locator('button[aria-label*="菜单"], button[class*="menu"], button[class*="Menu"]');
|
||||
const hasMenuButton = await menuButton.isVisible().catch(() => false);
|
||||
|
||||
console.log(`移动端菜单按钮${hasMenuButton ? '存在' : '不存在'}`);
|
||||
|
||||
if (hasMenuButton) {
|
||||
await menuButton.click();
|
||||
await page.waitForSelector('nav, [class*="menu"], [class*="Menu"]', { state: 'visible', timeout: 5000 });
|
||||
|
||||
const navMenu = page.locator('nav, [class*="menu"], [class*="Menu"]');
|
||||
const isVisible = await navMenu.isVisible().catch(() => false);
|
||||
console.log(`导航菜单${isVisible ? '展开' : '未展开'}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('桌面端导航显示', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const navLinks = page.locator('nav a');
|
||||
const count = await navLinks.count();
|
||||
|
||||
console.log(`桌面端导航链接数量: ${count}`);
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('页面加载性能测试', () => {
|
||||
test('各页面加载时间', async ({ page }) => {
|
||||
const pages = [
|
||||
{ url: '/', name: '首页' },
|
||||
{ url: '/news', name: '新闻' },
|
||||
{ url: '/products', name: '产品' },
|
||||
{ url: '/services', name: '服务' },
|
||||
{ url: '/cases', name: '案例' },
|
||||
];
|
||||
|
||||
for (const p of pages) {
|
||||
const startTime = Date.now();
|
||||
await page.goto(`${BASE_URL}${p.url}`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
console.log(`${p.name}页面加载时间: ${loadTime}ms`);
|
||||
expect(loadTime).toBeLessThan(5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('错误处理测试', () => {
|
||||
test('访问不存在的页面', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/nonexistent-page-12345`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const errorElement = page.locator('[class*="error"], h1:has-text("404"), text=页面不存在');
|
||||
const hasError = await errorElement.isVisible().catch(() => false);
|
||||
|
||||
console.log(`404页面${hasError ? '正确显示' : '未显示'}`);
|
||||
});
|
||||
|
||||
test('后台访问无权限内容', async ({ browser }) => {
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
await page.goto(`${BASE_URL}/admin/content/99999`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForURL(/\/admin/, { timeout: 5000 });
|
||||
|
||||
const url = page.url();
|
||||
console.log(`访问不存在内容后URL: ${url}`);
|
||||
|
||||
await context.close();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('国际化支持测试', () => {
|
||||
test('页面语言属性', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const htmlLang = await page.locator('html').getAttribute('lang');
|
||||
console.log(`页面语言: ${htmlLang || '未设置'}`);
|
||||
});
|
||||
});
|
||||
@@ -1,198 +0,0 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
||||
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@novalon.cn';
|
||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123456';
|
||||
|
||||
test.describe('后台管理发布功能 - 核心测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/login`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const emailInput = page.locator('#email');
|
||||
const passwordInput = page.locator('#password');
|
||||
const submitButton = page.locator('button[type="submit"]');
|
||||
|
||||
await emailInput.fill(ADMIN_EMAIL);
|
||||
await passwordInput.fill(ADMIN_PASSWORD);
|
||||
await submitButton.click();
|
||||
|
||||
await page.waitForURL(/\/admin(?!\/login)/, { timeout: 15000 });
|
||||
});
|
||||
|
||||
test('管理员登录成功', async ({ page }) => {
|
||||
expect(page.url()).not.toContain('/admin/login');
|
||||
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('h1, .text-2xl').first()).toContainText('内容管理');
|
||||
});
|
||||
|
||||
test('后台内容列表加载', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const table = page.locator('table');
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
const rows = page.locator('tbody tr');
|
||||
const count = await rows.count();
|
||||
expect(count).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test('新建内容页面加载', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/content/new`);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
await page.waitForSelector('input[placeholder="请输入标题"]', { timeout: 60000 });
|
||||
await page.waitForSelector('input[placeholder="url-slug"]', { timeout: 60000 });
|
||||
|
||||
const heading = page.locator('h1, .text-2xl').first();
|
||||
await expect(heading).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const titleInput = page.locator('input[placeholder="请输入标题"]');
|
||||
await expect(titleInput).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const slugInput = page.locator('input[placeholder="url-slug"]');
|
||||
await expect(slugInput).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('新建内容页面表单元素可见', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/content/new`);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForSelector('input[placeholder="请输入标题"]', { timeout: 60000 });
|
||||
|
||||
const typeSelect = page.locator('select').first();
|
||||
await expect(typeSelect).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const categoryInput = page.locator('input[placeholder="分类名称"]');
|
||||
await expect(categoryInput).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const saveButton = page.locator('button:has-text("保存草稿")');
|
||||
await expect(saveButton).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const publishButton = page.locator('button:has-text("发布")');
|
||||
await expect(publishButton).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('前端内容展示验证', () => {
|
||||
test('首页加载正常', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
|
||||
test('新闻页面加载', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/\/news/);
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
});
|
||||
|
||||
test('产品页面加载', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/products`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/\/products/);
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
});
|
||||
|
||||
test('服务页面加载', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/services`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/\/services/);
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
});
|
||||
|
||||
test('案例页面加载', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/cases`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page).toHaveURL(/\/cases/);
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('权限控制测试', () => {
|
||||
test('未登录访问后台重定向到登录页', async ({ browser }) => {
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForURL(/\/admin\/login/, { timeout: 10000 });
|
||||
|
||||
expect(page.url()).toContain('/admin/login');
|
||||
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test('API无权限访问返回403', async ({ request }) => {
|
||||
const response = await request.post(`${BASE_URL}/api/admin/content`, {
|
||||
data: {
|
||||
type: 'news',
|
||||
title: '测试',
|
||||
slug: 'test',
|
||||
content: 'test',
|
||||
},
|
||||
});
|
||||
|
||||
expect([401, 403]).toContain(response.status());
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('性能测试', () => {
|
||||
test('首页加载性能', async ({ page }) => {
|
||||
const startTime = Date.now();
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
console.log(`首页加载时间: ${loadTime}ms`);
|
||||
expect(loadTime).toBeLessThan(5000);
|
||||
});
|
||||
|
||||
test('新闻页面加载性能', async ({ page }) => {
|
||||
const startTime = Date.now();
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
console.log(`新闻页面加载时间: ${loadTime}ms`);
|
||||
expect(loadTime).toBeLessThan(5000);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('响应式设计测试', () => {
|
||||
test('移动端显示', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
|
||||
test('平板端显示', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
|
||||
test('桌面端显示', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,507 +0,0 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
||||
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@novalon.cn';
|
||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123456';
|
||||
|
||||
interface ContentData {
|
||||
type: 'news' | 'product' | 'service' | 'case';
|
||||
title: string;
|
||||
slug: string;
|
||||
excerpt: string;
|
||||
content: string;
|
||||
category: string;
|
||||
tags: string[];
|
||||
status: 'draft' | 'published' | 'archived';
|
||||
}
|
||||
|
||||
const testContents: ContentData[] = [
|
||||
{
|
||||
type: 'news',
|
||||
title: `测试新闻-${Date.now()}`,
|
||||
slug: `test-news-${Date.now()}`,
|
||||
excerpt: '这是一条测试新闻的摘要内容',
|
||||
content: '<p>这是测试新闻的正文内容</p><p>包含多个段落</p>',
|
||||
category: '公司新闻',
|
||||
tags: ['测试', '自动化'],
|
||||
status: 'published',
|
||||
},
|
||||
{
|
||||
type: 'product',
|
||||
title: `测试产品-${Date.now()}`,
|
||||
slug: `test-product-${Date.now()}`,
|
||||
excerpt: '这是一个测试产品的描述',
|
||||
content: '<p>测试产品的详细介绍</p>',
|
||||
category: '软件产品',
|
||||
tags: ['产品', '测试'],
|
||||
status: 'published',
|
||||
},
|
||||
{
|
||||
type: 'service',
|
||||
title: `测试服务-${Date.now()}`,
|
||||
slug: `test-service-${Date.now()}`,
|
||||
excerpt: '这是一个测试服务的描述',
|
||||
content: '<p>测试服务的详细介绍</p>',
|
||||
category: '软件开发',
|
||||
tags: ['服务', '测试'],
|
||||
status: 'published',
|
||||
},
|
||||
{
|
||||
type: 'case',
|
||||
title: `测试案例-${Date.now()}`,
|
||||
slug: `test-case-${Date.now()}`,
|
||||
excerpt: '这是一个测试案例的描述',
|
||||
content: '<p>测试案例的详细介绍</p>',
|
||||
category: '企业服务',
|
||||
tags: ['案例', '测试'],
|
||||
status: 'published',
|
||||
},
|
||||
];
|
||||
|
||||
async function loginAsAdmin(page: Page) {
|
||||
await page.goto(`${BASE_URL}/admin/login`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const emailInput = page.locator('input[name="email"], input[type="email"]');
|
||||
const passwordInput = page.locator('input[name="password"], input[type="password"]');
|
||||
const submitButton = page.locator('button[type="submit"]');
|
||||
|
||||
await emailInput.fill(ADMIN_EMAIL);
|
||||
await passwordInput.fill(ADMIN_PASSWORD);
|
||||
await submitButton.click();
|
||||
|
||||
await page.waitForURL(/\/admin(?!\/login)/, { timeout: 10000 });
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async function createContent(page: Page, contentData: ContentData): Promise<string | null> {
|
||||
await page.goto(`${BASE_URL}/admin/content/new`);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForSelector('input[type="text"]', { state: 'visible', timeout: 10000 });
|
||||
|
||||
const titleInput = page.locator('input[type="text"]').first();
|
||||
await titleInput.fill(contentData.title);
|
||||
|
||||
const slugInput = page.locator('input[placeholder="url-slug"]');
|
||||
await slugInput.fill(contentData.slug);
|
||||
|
||||
const excerptTextarea = page.locator('textarea').first();
|
||||
await excerptTextarea.fill(contentData.excerpt);
|
||||
|
||||
const typeSelect = page.locator('select').first();
|
||||
await typeSelect.selectOption(contentData.type);
|
||||
|
||||
const statusSelect = page.locator('select').nth(1);
|
||||
await statusSelect.selectOption(contentData.status);
|
||||
|
||||
const categoryInput = page.locator('input[placeholder="分类名称"]');
|
||||
await categoryInput.fill(contentData.category);
|
||||
|
||||
const publishButton = page.locator('button:has-text("发布")');
|
||||
await publishButton.click();
|
||||
|
||||
await page.waitForResponse(resp =>
|
||||
resp.url().includes('/api/admin/content') &&
|
||||
(resp.request().method() === 'POST' || resp.request().method() === 'PUT'),
|
||||
{ timeout: 15000 }
|
||||
);
|
||||
|
||||
await page.waitForURL(/\/admin\/content\/[a-zA-Z0-9]+/, { timeout: 10000 });
|
||||
|
||||
const url = page.url();
|
||||
const match = url.match(/\/admin\/content\/([a-zA-Z0-9]+)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
async function deleteContent(page: Page, contentId: string) {
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForSelector('table tbody tr', { state: 'visible', timeout: 10000 });
|
||||
|
||||
const contentRow = page.locator(`tr:has-text("${contentId}")`);
|
||||
if (await contentRow.count() > 0) {
|
||||
const deleteButton = contentRow.locator('button:has-text("删除")');
|
||||
await deleteButton.click();
|
||||
|
||||
const confirmButton = page.locator('button:has-text("确认"), button:has-text("确定")');
|
||||
if (await confirmButton.count() > 0) {
|
||||
await confirmButton.click();
|
||||
await page.waitForResponse(resp =>
|
||||
resp.url().includes('/api/admin/content') &&
|
||||
resp.request().method() === 'DELETE',
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test.describe('后台管理发布功能测试', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginAsAdmin(page);
|
||||
});
|
||||
|
||||
test('TC-001: 创建新闻内容并发布', async ({ page }) => {
|
||||
const contentData = testContents[0];
|
||||
const contentId = await createContent(page, contentData);
|
||||
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const contentRow = page.locator(`tr:has-text("${contentData.title}")`);
|
||||
await expect(contentRow).toBeVisible();
|
||||
|
||||
const statusBadge = contentRow.locator('td:has-text("已发布")');
|
||||
await expect(statusBadge).toBeVisible();
|
||||
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const newsCard = page.locator(`text="${contentData.title}"`);
|
||||
await expect(newsCard).toBeVisible();
|
||||
|
||||
if (contentId) {
|
||||
await deleteContent(page, contentId);
|
||||
}
|
||||
});
|
||||
|
||||
test('TC-002: 创建产品内容并发布', async ({ page }) => {
|
||||
const contentData = testContents[1];
|
||||
const contentId = await createContent(page, contentData);
|
||||
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await page.goto(`${BASE_URL}/products`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const productCard = page.locator(`text="${contentData.title}"`);
|
||||
await expect(productCard).toBeVisible();
|
||||
|
||||
if (contentId) {
|
||||
await deleteContent(page, contentId);
|
||||
}
|
||||
});
|
||||
|
||||
test('TC-003: 创建服务内容并发布', async ({ page }) => {
|
||||
const contentData = testContents[2];
|
||||
const contentId = await createContent(page, contentData);
|
||||
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await page.goto(`${BASE_URL}/services`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const serviceCard = page.locator(`text="${contentData.title}"`);
|
||||
await expect(serviceCard).toBeVisible();
|
||||
|
||||
if (contentId) {
|
||||
await deleteContent(page, contentId);
|
||||
}
|
||||
});
|
||||
|
||||
test('TC-004: 创建案例内容并发布', async ({ page }) => {
|
||||
const contentData = testContents[3];
|
||||
const contentId = await createContent(page, contentData);
|
||||
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await page.goto(`${BASE_URL}/cases`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const caseCard = page.locator(`text="${contentData.title}"`);
|
||||
await expect(caseCard).toBeVisible();
|
||||
|
||||
if (contentId) {
|
||||
await deleteContent(page, contentId);
|
||||
}
|
||||
});
|
||||
|
||||
test('TC-005: 保存为草稿', async ({ page }) => {
|
||||
const draftContent: ContentData = {
|
||||
type: 'news',
|
||||
title: `草稿测试-${Date.now()}`,
|
||||
slug: `draft-test-${Date.now()}`,
|
||||
excerpt: '这是草稿测试内容',
|
||||
content: '<p>草稿内容</p>',
|
||||
category: '公司新闻',
|
||||
tags: ['草稿'],
|
||||
status: 'draft',
|
||||
};
|
||||
|
||||
const contentId = await createContent(page, draftContent);
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const contentRow = page.locator(`tr:has-text("${draftContent.title}")`);
|
||||
await expect(contentRow).toBeVisible();
|
||||
|
||||
const statusBadge = contentRow.locator('td:has-text("草稿")');
|
||||
await expect(statusBadge).toBeVisible();
|
||||
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const newsCard = page.locator(`text="${draftContent.title}"`);
|
||||
await expect(newsCard).not.toBeVisible();
|
||||
|
||||
if (contentId) {
|
||||
await deleteContent(page, contentId);
|
||||
}
|
||||
});
|
||||
|
||||
test('TC-006: 编辑已发布的内容', async ({ page }) => {
|
||||
const contentData = testContents[0];
|
||||
const contentId = await createContent(page, contentData);
|
||||
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await page.goto(`${BASE_URL}/admin/content/${contentId}`);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForSelector('input[type="text"]', { state: 'visible', timeout: 10000 });
|
||||
|
||||
const updatedTitle = `${contentData.title}-已修改`;
|
||||
const titleInput = page.locator('input[type="text"]').first();
|
||||
await titleInput.fill(updatedTitle);
|
||||
|
||||
const saveButton = page.locator('button:has-text("保存草稿")');
|
||||
await saveButton.click();
|
||||
|
||||
await page.waitForResponse(resp =>
|
||||
resp.url().includes(`/api/admin/content/${contentId}`) &&
|
||||
resp.request().method() === 'PUT',
|
||||
{ timeout: 15000 }
|
||||
);
|
||||
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const updatedCard = page.locator(`text="${updatedTitle}"`);
|
||||
await expect(updatedCard).toBeVisible();
|
||||
|
||||
if (contentId) {
|
||||
await deleteContent(page, contentId);
|
||||
}
|
||||
});
|
||||
|
||||
test('TC-007: 删除内容', async ({ page }) => {
|
||||
const contentData = testContents[0];
|
||||
const contentId = await createContent(page, contentData);
|
||||
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await deleteContent(page, contentId!);
|
||||
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const contentRow = page.locator(`tr:has-text("${contentData.title}")`);
|
||||
await expect(contentRow).not.toBeVisible();
|
||||
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const newsCard = page.locator(`text="${contentData.title}"`);
|
||||
await expect(newsCard).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('TC-008: 归档内容', async ({ page }) => {
|
||||
const contentData = testContents[0];
|
||||
const contentId = await createContent(page, contentData);
|
||||
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await page.goto(`${BASE_URL}/admin/content/${contentId}`);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForSelector('select', { state: 'visible', timeout: 10000 });
|
||||
|
||||
const statusSelect = page.locator('select').nth(1);
|
||||
await statusSelect.selectOption('archived');
|
||||
|
||||
const saveButton = page.locator('button:has-text("保存草稿")');
|
||||
await saveButton.click();
|
||||
|
||||
await page.waitForResponse(resp =>
|
||||
resp.url().includes(`/api/admin/content/${contentId}`) &&
|
||||
resp.request().method() === 'PUT',
|
||||
{ timeout: 15000 }
|
||||
);
|
||||
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const contentRow = page.locator(`tr:has-text("${contentData.title}")`);
|
||||
await expect(contentRow).toBeVisible();
|
||||
|
||||
const statusBadge = contentRow.locator('td:has-text("已归档")');
|
||||
await expect(statusBadge).toBeVisible();
|
||||
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const newsCard = page.locator(`text="${contentData.title}"`);
|
||||
await expect(newsCard).not.toBeVisible();
|
||||
|
||||
if (contentId) {
|
||||
await deleteContent(page, contentId);
|
||||
}
|
||||
});
|
||||
|
||||
test('TC-015: 空内容提交验证', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/admin/content/new`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const publishButton = page.locator('button:has-text("发布")');
|
||||
await publishButton.click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const errorMessage = page.locator('text=/请输入标题|标题不能为空|请输入|必填/');
|
||||
await expect(errorMessage.first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('TC-018: 未登录用户访问后台', async ({ context }) => {
|
||||
const newPage = await context.newPage();
|
||||
|
||||
await newPage.goto(`${BASE_URL}/admin/content`);
|
||||
await newPage.waitForLoadState('networkidle');
|
||||
|
||||
expect(newPage.url()).toContain('/admin/login');
|
||||
|
||||
await newPage.close();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('前端内容展示验证', () => {
|
||||
test('新闻页面加载正常', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('h1, .page-header')).toContainText('新闻');
|
||||
|
||||
const newsCards = page.locator('article, .card, [class*="news-item"]');
|
||||
const count = await newsCards.count();
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('产品页面加载正常', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/products`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('h1, .page-header')).toContainText('产品');
|
||||
|
||||
const productCards = page.locator('article, .card, [class*="product"]');
|
||||
const count = await productCards.count();
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('服务页面加载正常', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/services`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('h1, .page-header')).toContainText('服务');
|
||||
});
|
||||
|
||||
test('案例页面加载正常', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/cases`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('h1, .page-header')).toContainText('案例');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('性能测试', () => {
|
||||
test('TC-025: 后台列表加载性能', async ({ page }) => {
|
||||
await loginAsAdmin(page);
|
||||
|
||||
const startTime = Date.now();
|
||||
await page.goto(`${BASE_URL}/admin/content`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
console.log(`后台列表加载时间: ${loadTime}ms`);
|
||||
expect(loadTime).toBeLessThan(3000);
|
||||
});
|
||||
|
||||
test('前端新闻页面加载性能', async ({ page }) => {
|
||||
const startTime = Date.now();
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
console.log(`前端新闻页面加载时间: ${loadTime}ms`);
|
||||
expect(loadTime).toBeLessThan(3000);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('安全测试', () => {
|
||||
test('TC-031: XSS攻击防护', async ({ page }) => {
|
||||
await loginAsAdmin(page);
|
||||
|
||||
const xssContent: ContentData = {
|
||||
type: 'news',
|
||||
title: `XSS测试-${Date.now()}`,
|
||||
slug: `xss-test-${Date.now()}`,
|
||||
excerpt: '<script>alert("XSS")</script>测试摘要',
|
||||
content: '<p><script>alert("XSS")</script>测试内容</p>',
|
||||
category: '公司新闻',
|
||||
tags: ['安全测试'],
|
||||
status: 'published',
|
||||
};
|
||||
|
||||
const contentId = await createContent(page, xssContent);
|
||||
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const xssTriggered = await page.evaluate(() => {
|
||||
return (window as any).xssTriggered === true;
|
||||
});
|
||||
|
||||
expect(xssTriggered).toBe(false);
|
||||
|
||||
if (contentId) {
|
||||
await deleteContent(page, contentId);
|
||||
}
|
||||
});
|
||||
|
||||
test('TC-033: API权限验证', async ({ request }) => {
|
||||
const response = await request.post(`${BASE_URL}/api/admin/content`, {
|
||||
data: {
|
||||
type: 'news',
|
||||
title: '未授权测试',
|
||||
slug: 'unauthorized-test',
|
||||
content: '测试内容',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(403);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('跨浏览器兼容性测试', () => {
|
||||
test('响应式设计 - 移动端', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
|
||||
test('响应式设计 - 平板端', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
|
||||
await page.goto(`${BASE_URL}/news`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { test, expect } from '../../fixtures/auth';
|
||||
import { AdminContentPage } from '../../pages';
|
||||
import { testFixtures } from '../../fixtures/test-data';
|
||||
|
||||
test.describe('内容CRUD测试 @feature @admin', () => {
|
||||
let contentPage: AdminContentPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
contentPage = new AdminContentPage(page);
|
||||
});
|
||||
|
||||
test('创建新闻内容', async ({ authenticatedPage: _authenticatedPage }) => {
|
||||
const testNews = testFixtures.testContent.news;
|
||||
let contentId: string | null = null;
|
||||
|
||||
try {
|
||||
contentId = await contentPage.createContent(testNews);
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await contentPage.expectContentInList(testNews.title);
|
||||
} finally {
|
||||
if (contentId) {
|
||||
await contentPage.deleteContent(contentId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('创建产品内容', async ({ authenticatedPage: _authenticatedPage }) => {
|
||||
const testProduct = testFixtures.testContent.product;
|
||||
let contentId: string | null = null;
|
||||
|
||||
try {
|
||||
contentId = await contentPage.createContent(testProduct);
|
||||
expect(contentId).not.toBeNull();
|
||||
|
||||
await contentPage.expectContentInList(testProduct.title);
|
||||
} finally {
|
||||
if (contentId) {
|
||||
await contentPage.deleteContent(contentId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('创建内容时验证必填字段', async ({ page, authenticatedPage: _authenticatedPage }) => {
|
||||
await contentPage.gotoCreate();
|
||||
await page.click('button:has-text("发布")');
|
||||
|
||||
await expect(page.locator('.error-message, [role="alert"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('删除内容', async ({ authenticatedPage: _authenticatedPage }) => {
|
||||
const testNews = testFixtures.testContent.news;
|
||||
const contentId = await contentPage.createContent(testNews);
|
||||
|
||||
if (contentId) {
|
||||
await contentPage.deleteContent(contentId);
|
||||
await contentPage.expectContentNotInList(testNews.title);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { test, expect } from '../../fixtures/auth';
|
||||
import { AdminUserPage } from '../../pages';
|
||||
|
||||
test.describe('用户管理测试 @feature @admin', () => {
|
||||
let userPage: AdminUserPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
userPage = new AdminUserPage(page);
|
||||
});
|
||||
|
||||
test('查看用户列表', async ({ authenticatedPage: _authenticatedPage }) => {
|
||||
await userPage.goto();
|
||||
|
||||
const table = userPage['page'].locator('table');
|
||||
await expect(table).toBeVisible();
|
||||
|
||||
const rows = table.locator('tbody tr');
|
||||
const count = await rows.count();
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('创建新用户', async ({ authenticatedPage: _authenticatedPage }) => {
|
||||
const timestamp = Date.now();
|
||||
const userData = {
|
||||
email: `test-${timestamp}@example.com`,
|
||||
password: 'Test123456!',
|
||||
name: `测试用户${timestamp}`,
|
||||
role: 'viewer' as const,
|
||||
};
|
||||
|
||||
try {
|
||||
await userPage.createUser(userData);
|
||||
await userPage.expectUserInList(userData.email);
|
||||
} finally {
|
||||
// TODO: 添加删除用户的逻辑
|
||||
}
|
||||
});
|
||||
|
||||
test('搜索用户', async ({ page, authenticatedPage: _authenticatedPage }) => {
|
||||
await userPage.goto();
|
||||
|
||||
const searchInput = page.locator('input[placeholder*="搜索"], input[name="search"]');
|
||||
if (await searchInput.count() > 0) {
|
||||
await searchInput.fill('admin');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
const table = page.locator('table');
|
||||
await expect(table).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('无障碍测试 @feature @frontend', () => {
|
||||
test('首页无障碍检查', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const violations = await page.evaluate(() => {
|
||||
return (window as unknown as { axe?: { run: () => unknown[] } }).axe?.run() || [];
|
||||
});
|
||||
|
||||
expect(violations.length).toBe(0);
|
||||
});
|
||||
|
||||
test('导航键盘可访问', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
const focusedElement = page.locator(':focus');
|
||||
await expect(focusedElement).toBeVisible();
|
||||
});
|
||||
|
||||
test('图片有alt属性', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const images = page.locator('img');
|
||||
const count = await images.count();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const img = images.nth(i);
|
||||
const alt = await img.getAttribute('alt');
|
||||
expect(alt).not.toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
test('表单标签关联正确', async ({ page }) => {
|
||||
await page.goto('/contact');
|
||||
|
||||
const inputs = page.locator('input[type="text"], input[type="email"], textarea');
|
||||
const count = await inputs.count();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const input = inputs.nth(i);
|
||||
const id = await input.getAttribute('id');
|
||||
|
||||
if (id) {
|
||||
const label = page.locator(`label[for="${id}"]`);
|
||||
const hasLabel = await label.count() > 0;
|
||||
const hasAriaLabel = await input.getAttribute('aria-label');
|
||||
|
||||
expect(hasLabel || hasAriaLabel).toBeTruthy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('标题层级正确', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const h1 = page.locator('h1');
|
||||
const h1Count = await h1.count();
|
||||
expect(h1Count).toBeGreaterThanOrEqual(1);
|
||||
expect(h1Count).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
test('链接有明确的文本', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const links = page.locator('a');
|
||||
const count = await links.count();
|
||||
|
||||
for (let i = 0; i < Math.min(count, 10); i++) {
|
||||
const link = links.nth(i);
|
||||
const text = await link.textContent();
|
||||
const ariaLabel = await link.getAttribute('aria-label');
|
||||
|
||||
expect(text || ariaLabel).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('响应式测试 @feature @frontend', () => {
|
||||
test('移动端首页显示正常', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('nav')).toBeVisible();
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
|
||||
test('平板端首页显示正常', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('nav')).toBeVisible();
|
||||
});
|
||||
|
||||
test('桌面端首页显示正常', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('nav')).toBeVisible();
|
||||
});
|
||||
|
||||
test('移动端导航菜单可展开', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto('/');
|
||||
|
||||
const menuButton = page.locator('button[aria-label*="菜单"], button[aria-label*="menu"]');
|
||||
if (await menuButton.count() > 0) {
|
||||
await menuButton.click();
|
||||
|
||||
const mobileMenu = page.locator('[role="dialog"], .mobile-menu, nav[class*="mobile"]');
|
||||
await expect(mobileMenu).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('核心功能测试', () => {
|
||||
test('首页加载正常', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveTitle(/四川睿新致远科技有限公司/);
|
||||
await expect(page.locator('header')).toBeVisible();
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
|
||||
test('导航功能正常', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const navLinks = page.locator('nav a');
|
||||
const count = await navLinks.count();
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('联系表单显示正常', async ({ page }) => {
|
||||
await page.goto('/contact');
|
||||
|
||||
await expect(page.locator('input[name="name"]')).toBeVisible();
|
||||
await expect(page.locator('input[name="phone"]')).toBeVisible();
|
||||
await expect(page.locator('input[name="email"]')).toBeVisible();
|
||||
await expect(page.locator('button[type="submit"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('ICP备案号显示正确', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const footer = page.locator('footer');
|
||||
await expect(footer).toContainText('蜀ICP备2026013658号');
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,13 @@
|
||||
"test:fast": "TEST_TIER=fast playwright test",
|
||||
"test:standard": "TEST_TIER=standard playwright test",
|
||||
"test:deep": "TEST_TIER=deep playwright test",
|
||||
"test:smoke": "playwright test --grep @smoke",
|
||||
"test:journey": "playwright test --grep @journey",
|
||||
"test:feature": "playwright test --grep @feature",
|
||||
"test:admin": "playwright test --grep @admin",
|
||||
"test:frontend": "playwright test --grep @frontend",
|
||||
"test:ui": "playwright test --ui",
|
||||
"test:debug": "playwright test --debug",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
|
||||
@@ -30,6 +30,10 @@ const config = tierConfig[testTier];
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
testMatch: [
|
||||
'**/*.spec.ts',
|
||||
'**/*.test.ts',
|
||||
],
|
||||
fullyParallel: !isCI,
|
||||
forbidOnly: isCI,
|
||||
retries: config.retries,
|
||||
|
||||
Reference in New Issue
Block a user