ebaa7f3c50
ci/woodpecker/manual/woodpecker Pipeline was successful
- 移除未使用的YAML锚点定义 - 替换commands字段中的锚点引用为实际值 - 移除有问题的通知步骤 - 修复测试文件中的问题 - 添加新的测试用例和配置文件
333 lines
11 KiB
TypeScript
333 lines
11 KiB
TypeScript
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 || '未设置'}`);
|
|
});
|
|
});
|