feat: 添加面包屑导航组件并优化页面布局
refactor: 重构页面结构和导航逻辑 fix: 修复移动端菜单导航和滚动行为 perf: 优化图片加载性能和资源请求 test: 添加端到端测试和性能测试用例 docs: 更新.gitignore文件 chore: 更新依赖和配置 style: 优化代码格式和类型安全 ci: 调整Playwright测试超时时间 build: 更新Next.js配置和构建选项
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
import { test, expect } from '../../fixtures/base.fixture';
|
||||
|
||||
test.describe('Mobile UX Tests', () => {
|
||||
test.use({ viewport: { width: 375, height: 667 } });
|
||||
|
||||
test('Mobile menu opens and closes correctly', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const menuButton = page.locator('button[aria-label="打开菜单"], button[aria-label="关闭菜单"]');
|
||||
await expect(menuButton).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await menuButton.click();
|
||||
|
||||
const mobileMenu = page.locator('#mobile-menu');
|
||||
await expect(mobileMenu).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const closeButton = page.locator('button[aria-label="关闭菜单"]');
|
||||
await closeButton.click();
|
||||
await expect(mobileMenu).not.toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('Mobile menu navigation works', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const menuButton = page.locator('button[aria-label="打开菜单"]');
|
||||
await expect(menuButton).toBeVisible({ timeout: 10000 });
|
||||
await menuButton.click();
|
||||
|
||||
const mobileMenu = page.locator('#mobile-menu');
|
||||
await expect(mobileMenu).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await page.getByRole('link', { name: '关于我们' }).first().click();
|
||||
|
||||
await expect(page).toHaveURL(/.*about.*/, { timeout: 30000 });
|
||||
});
|
||||
|
||||
test('Mobile menu closes on outside click', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const menuButton = page.locator('button[aria-label="打开菜单"]');
|
||||
await expect(menuButton).toBeVisible({ timeout: 10000 });
|
||||
await menuButton.click();
|
||||
|
||||
const mobileMenu = page.locator('#mobile-menu');
|
||||
await expect(mobileMenu).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await expect(mobileMenu).not.toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('Mobile viewport renders correctly', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const header = page.locator('header');
|
||||
await expect(header).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const desktopNav = page.locator('nav.hidden.md\\:flex');
|
||||
await expect(desktopNav).not.toBeVisible();
|
||||
|
||||
const menuButton = page.locator('button[aria-label="打开菜单"]');
|
||||
await expect(menuButton).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('Touch targets are appropriately sized', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const menuButton = page.locator('button[aria-label="打开菜单"]');
|
||||
await expect(menuButton).toBeVisible({ timeout: 10000 });
|
||||
await menuButton.click();
|
||||
|
||||
const mobileMenu = page.locator('#mobile-menu');
|
||||
await expect(mobileMenu).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const links = await mobileMenu.locator('a').all();
|
||||
|
||||
for (const link of links) {
|
||||
const box = await link.boundingBox();
|
||||
if (box) {
|
||||
expect(box.height).toBeGreaterThanOrEqual(44);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('Mobile page scrolls smoothly', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const scrollY = await page.evaluate(() => window.scrollY);
|
||||
expect(scrollY).toBe(0);
|
||||
|
||||
await page.evaluate(() => {
|
||||
window.scrollTo({ top: 500, behavior: 'smooth' });
|
||||
});
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const newScrollY = await page.evaluate(() => window.scrollY);
|
||||
expect(newScrollY).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('Mobile images are responsive', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const images = await page.locator('img').all();
|
||||
|
||||
for (const image of images) {
|
||||
const box = await image.boundingBox();
|
||||
if (box) {
|
||||
expect(box.width).toBeLessThanOrEqual(400);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('Mobile text is readable', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const textElements = await page.locator('p, h1, h2, h3, h4, h5, h6').all();
|
||||
|
||||
for (const element of textElements.slice(0, 10)) {
|
||||
const fontSize = await element.evaluate((el) => {
|
||||
const style = window.getComputedStyle(el);
|
||||
return parseFloat(style.fontSize);
|
||||
});
|
||||
expect(fontSize).toBeGreaterThanOrEqual(14);
|
||||
}
|
||||
});
|
||||
|
||||
test('Mobile About page renders correctly', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/about');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const header = page.locator('header');
|
||||
await expect(header).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const breadcrumb = page.locator('nav[aria-label="breadcrumb"]');
|
||||
await expect(breadcrumb).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('Mobile Products page cards stack vertically', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/products');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const productCards = page.locator('a[href^="/products/"]');
|
||||
const count = await productCards.count();
|
||||
expect(count).toBeGreaterThan(0);
|
||||
|
||||
if (count >= 2) {
|
||||
const firstCard = productCards.first();
|
||||
const secondCard = productCards.nth(1);
|
||||
|
||||
const firstBox = await firstCard.boundingBox();
|
||||
const secondBox = await secondCard.boundingBox();
|
||||
|
||||
if (firstBox && secondBox) {
|
||||
expect(secondBox.y).toBeGreaterThan(firstBox.y);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('Mobile Contact page form is usable', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/contact');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
const nameInput = page.locator('input[name="name"], input[placeholder*="姓名"], input[placeholder*="名字"]');
|
||||
if (await nameInput.count() > 0) {
|
||||
await expect(nameInput.first()).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
|
||||
const submitButton = page.locator('button[type="submit"], button:has-text("提交"), button:has-text("发送")');
|
||||
if (await submitButton.count() > 0) {
|
||||
await expect(submitButton.first()).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('Mobile keyboard navigation works', async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
const focusedElement = page.locator(':focus');
|
||||
await expect(focusedElement).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user