Files
novalon-website/e2e/website-acceptance.spec.ts
T
zhangxiang 3e629bd9ee test(e2e): add GA4 tracking, mobile, user journey tests and update visual baselines
- Add GA4 event tracking E2E tests (4 cases: page view, form submit, button click, product page)
- Add mobile-specific E2E tests (16 cases: navigation, form, product browsing)
- Add user journey tests (UJ-01: homepage to contact, UJ-02: product discovery)
- Fix E2E test selectors and assertions for consistency
- Update visual regression baselines across all browsers (chromium/firefox/webkit)
- Update Playwright config for new test files
2026-07-31 20:24:57 +08:00

246 lines
9.5 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.describe('网站全面测试验收', { tag: '@regression' }, () => {
test.beforeEach(async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle', timeout: 30000 });
});
test('首页加载正常', async ({ page }) => {
await expect(page).toHaveTitle(/四川睿新致远科技有限公司/);
await expect(page.locator('header')).toBeVisible();
await expect(page.locator('[data-testid="footer"]')).toBeVisible();
});
test('公司Logo可见且不被覆盖', async ({ page }) => {
const logo = page.locator('header img[alt*="睿新致远"], [data-testid="logo"]');
const logoCount = await logo.count();
async function assertLogoBoundingBox(logoLocator: ReturnType<typeof page.locator>) {
await expect(logoLocator).toBeVisible({ timeout: 10000 });
// 等待 layout 完成,避免 WebKit 中 boundingBox() 首次返回 null
const logoBox = await expect.poll(async () => logoLocator.boundingBox(), {
timeout: 5000,
intervals: [100, 200, 200],
}).not.toBeNull();
const header = page.locator('header');
const headerBox = await expect.poll(async () => header.boundingBox(), {
timeout: 5000,
intervals: [100, 200, 200],
}).not.toBeNull();
if (logoBox && headerBox) {
expect(logoBox.x).toBeGreaterThanOrEqual(headerBox.x);
expect(logoBox.y).toBeGreaterThanOrEqual(headerBox.y);
expect(logoBox.x + logoBox.width).toBeLessThanOrEqual(headerBox.x + headerBox.width);
expect(logoBox.y + logoBox.height).toBeLessThanOrEqual(headerBox.y + headerBox.height);
}
}
if (logoCount === 0) {
const allImages = page.locator('header img');
expect(await allImages.count()).toBeGreaterThan(0);
await assertLogoBoundingBox(allImages.first());
} else {
await assertLogoBoundingBox(logo.first());
}
});
test('导航菜单功能正常', async ({ page }) => {
const navLinks = page.locator('[data-testid="desktop-navigation"] a, nav a');
const count = await navLinks.count();
expect(count).toBeGreaterThan(0);
await navLinks.nth(0).click();
await page.waitForURL(/\/.+/, { timeout: 10000 });
});
test('联系我们页面没有显示公司电话', async ({ page }) => {
await page.goto('/contact', { waitUntil: 'load' });
await page.waitForTimeout(2000);
const contactInfoSection = page.locator('[data-testid="contact-info"]');
if (await contactInfoSection.isVisible()) {
const phoneInContactInfo = contactInfoSection.locator('text=/电话|028-88888888|联系电话/');
const phoneCount = await phoneInContactInfo.count();
expect(phoneCount).toBe(0);
}
});
test('联系我们页面表单正常显示', async ({ page }) => {
await page.goto('/contact', { waitUntil: 'load' });
// 等待 React 客户端渲染完成
await page.waitForTimeout(2000);
await expect(page.locator('[data-testid="name-input"]')).toBeVisible();
await expect(page.locator('[data-testid="phone-input"]')).toBeVisible();
await expect(page.locator('[data-testid="email-input"]')).toBeVisible();
await expect(page.locator('[data-testid="subject-input"]')).toBeVisible();
await expect(page.locator('[data-testid="message-input"]')).toBeVisible();
await expect(page.locator('[data-testid="submit-button"]')).toBeVisible();
});
test('ICP备案号正确显示', async ({ page }) => {
const footer = page.locator('[data-testid="footer"], footer');
await expect(footer).toBeVisible();
const icpText = await footer.textContent();
expect(icpText).toContain('蜀ICP备2026013658号');
});
test('关于我们页面没有显示公司电话', async ({ page }) => {
await page.goto('/about', { waitUntil: 'load' });
await page.waitForTimeout(2000);
const contactTexts = page.locator('text=/联系我们|联系方式|Contact/');
const contactCount = await contactTexts.count();
if (contactCount > 0) {
for (let i = 0; i < contactCount; i++) {
const contactElement = contactTexts.nth(i);
const parent = contactElement.locator('..');
if (await parent.isVisible()) {
const phoneText = parent.locator('text=/联系电话|028-88888888|电话:/');
const phoneCount = await phoneText.count();
expect(phoneCount).toBe(0);
break;
}
}
}
});
test('响应式设计正常工作', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await expect(page.locator('header')).toBeVisible();
await expect(page.locator('[data-testid="footer"], footer')).toBeVisible();
const mobileMenuButton = page.locator('[data-testid="mobile-menu-button"]');
await expect(mobileMenuButton).toBeVisible({ timeout: 5000 });
await mobileMenuButton.click();
await expect(page.locator('[data-testid="mobile-navigation"]')).toBeVisible({ timeout: 5000 });
});
test('页面跳转功能正常', async ({ page }) => {
const contactLink = page.locator('text=联系我们').first();
if (await contactLink.isVisible()) {
await contactLink.click();
await page.waitForURL(/\/contact/, { timeout: 10000 });
}
const homeLink = page.locator('text=首页').first();
if (await homeLink.isVisible()) {
await homeLink.click();
await page.waitForURL(/\//, { timeout: 10000 });
}
});
test('Footer链接正常工作', async ({ page }) => {
const footer = page.locator('footer').first();
await expect(footer).toBeVisible({ timeout: 10000 });
// Check footer contains expected links and legal info
const footerText = await footer.textContent();
expect(footerText).toContain('ICP');
expect(footerText).toContain('隐私');
});
test('表单验证功能正常', async ({ page }) => {
await page.goto('/contact', { waitUntil: 'networkidle' });
// 联系表单是 'use client' 组件,需等待 hydration 完成后再交互
const submitButton = page.locator('[data-testid="submit-button"]');
await expect(submitButton).toBeVisible({ timeout: 15000 });
await submitButton.click();
// 等待 Zod 验证错误渲染(客户端校验触发后 DOM 更新)
const errorMessages = page.locator('[data-testid="error-message"]');
await expect(errorMessages.first()).toBeVisible({ timeout: 10000 });
const errorCount = await errorMessages.count();
expect(errorCount).toBeGreaterThan(0);
});
test('页面加载性能良好', async ({ page }) => {
// 确保访问本地服务器而非外部域名
await page.goto('/', { waitUntil: 'load', timeout: 30000 });
const performanceMetrics = await page.evaluate(() => {
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
return {
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
};
});
expect(performanceMetrics.domContentLoaded).toBeLessThan(5000);
expect(performanceMetrics.loadComplete).toBeLessThan(15000);
});
test('无障碍访问正常', async ({ page }) => {
const htmlLang = await page.locator('html').getAttribute('lang');
expect(htmlLang).toBeTruthy();
// Skip link 通过 CSS left-[-9999px] 视觉隐藏,仅在 focus 时显示(标准无障碍模式)
// 使用 toBeAttached 验证 DOM 存在性而非视觉可见性
const skipLink = page.locator('[data-skip-to-content]');
await expect(skipLink).toBeAttached();
expect(await skipLink.textContent()).toContain('跳转到主要内容');
const main = page.locator('main').first();
await expect(main).toBeVisible({ timeout: 10000 });
// Check for navigation and footer existence (structure may vary)
const footer = page.locator('footer').first();
await expect(footer).toBeVisible({ timeout: 5000 });
const focusableElements = page.locator('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
const focusCount = await focusableElements.count();
expect(focusCount).toBeGreaterThan(0);
});
test('联系我们页面没有返回按钮覆盖logo', async ({ page }) => {
await page.goto('/contact', { waitUntil: 'load' });
await page.waitForTimeout(2000);
const logoSelectors = [
'header img[alt*="睿新致远"]',
'header img[alt*="novalon"]',
'[data-testid="logo"]',
'header img'
];
let logo = null;
for (const selector of logoSelectors) {
const element = page.locator(selector).first();
if (await element.isVisible().catch(() => false)) {
logo = element;
break;
}
}
if (!logo) {
const allLogos = page.locator('header img');
expect(await allLogos.count()).toBeGreaterThan(0);
logo = allLogos.first();
}
await expect(logo).toBeVisible();
const logoBox = await logo.boundingBox();
expect(logoBox).not.toBeNull();
const header = page.locator('header');
const headerBox = await header.boundingBox();
expect(headerBox).not.toBeNull();
if (logoBox && headerBox) {
const logoCenterX = logoBox.x + logoBox.width / 2;
const logoCenterY = logoBox.y + logoBox.height / 2;
expect(logoCenterX).toBeGreaterThan(headerBox.x);
expect(logoCenterX).toBeLessThan(headerBox.x + headerBox.width);
expect(logoCenterY).toBeGreaterThan(headerBox.y);
expect(logoCenterY).toBeLessThan(headerBox.y + headerBox.height);
}
});
});