import { test, expect, devices } from '@playwright/test'; /** * 移动端专项 E2E 测试套件 * * 测试范围:iPhone 14 视口下的移动端专属功能 * * 模块划分: * 1. 移动端导航(汉堡菜单) * 2. 移动端联系表单 * 3. 移动端产品浏览 */ test.setTimeout(60000); // 所有测试使用 iPhone 14 视口 test.use({ ...devices['iPhone 14'] }); test.describe.configure({ mode: 'parallel' }); // ==================== 1. 移动端导航测试 ==================== test.describe('移动端导航 - 汉堡菜单 @mobile @regression', () => { test.beforeEach(async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(2000); }); test('移动端菜单按钮可见', async ({ page }) => { const mobileMenuBtn = page.locator('[data-testid="mobile-menu-button"]').first(); await expect(mobileMenuBtn).toBeVisible({ timeout: 5000 }); }); test('点击汉堡菜单按钮打开导航面板', async ({ page }) => { const mobileMenuBtn = page.locator('[data-testid="mobile-menu-button"]').first(); await expect(mobileMenuBtn).toBeVisible({ timeout: 5000 }); await mobileMenuBtn.click(); await page.waitForTimeout(500); const mobileNav = page.locator('[data-testid="mobile-navigation"]').first(); await expect(mobileNav).toBeVisible({ timeout: 5000 }); }); test('移动端导航包含所有主要链接', async ({ page }) => { const mobileMenuBtn = page.locator('[data-testid="mobile-menu-button"]').first(); await mobileMenuBtn.click(); await page.waitForTimeout(500); const mobileNav = page.locator('[data-testid="mobile-navigation"]').first(); await expect(mobileNav).toBeVisible({ timeout: 5000 }); // 检查关键导航项 const expectedLinks = ['产品', '解决方案', '服务', '关于我们', '联系我们']; for (const linkText of expectedLinks) { const link = mobileNav.locator(`text=${linkText}`).first(); await expect(link).toBeVisible({ timeout: 5000 }); } }); test('通过移动端菜单导航到产品页', async ({ page }) => { const mobileMenuBtn = page.locator('[data-testid="mobile-menu-button"]').first(); await mobileMenuBtn.click(); await page.waitForTimeout(500); const mobileNav = page.locator('[data-testid="mobile-navigation"]').first(); await expect(mobileNav).toBeVisible({ timeout: 5000 }); // 点击产品链接导航 const productLink = mobileNav.locator('text=产品').first(); await productLink.click(); await page.waitForURL(/\/products/, { timeout: 10000 }); await expect(page).toHaveURL(/\/products/); }); test('通过移动端菜单导航到关于我们页', async ({ page }) => { const mobileMenuBtn = page.locator('[data-testid="mobile-menu-button"]').first(); await mobileMenuBtn.click(); await page.waitForTimeout(500); const mobileNav = page.locator('[data-testid="mobile-navigation"]').first(); await expect(mobileNav).toBeVisible({ timeout: 5000 }); const aboutLink = mobileNav.locator('text=关于我们').first(); await aboutLink.click(); await page.waitForURL(/\/about/, { timeout: 10000 }); await expect(page).toHaveURL(/\/about/); }); test('汉堡菜单展开后可通过点击切换按钮收起', async ({ page }) => { const mobileMenuBtn = page.locator('[data-testid="mobile-menu-button"]').first(); await mobileMenuBtn.click(); await page.waitForTimeout(500); const mobileNav = page.locator('[data-testid="mobile-navigation"]').first(); await expect(mobileNav).toBeVisible({ timeout: 5000 }); // 再次点击关闭菜单 await mobileMenuBtn.click(); await page.waitForTimeout(500); await expect(mobileNav).not.toBeVisible(); }); }); // ==================== 2. 移动端联系表单测试 ==================== test.describe('移动端联系表单 - 表单交互 @mobile @regression', () => { test.beforeEach(async ({ page }) => { await page.goto('/contact', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(2500); // 关闭 Cookie 同意横幅,避免覆盖底部表单元素导致点击错位 const acceptAllButton = page.locator('button:has-text("接受所有")').first(); if (await acceptAllButton.count() > 0 && await acceptAllButton.isVisible()) { await acceptAllButton.click(); await page.waitForTimeout(500); } }); test('移动端联系表单所有字段可见', async ({ page }) => { const formFields = [ '[data-testid="name-input"]', '[data-testid="phone-input"]', '[data-testid="email-input"]', '[data-testid="subject-input"]', '[data-testid="message-input"]', '[data-testid="submit-button"]', ]; for (const selector of formFields) { const field = page.locator(selector).first(); await expect(field).toBeVisible({ timeout: 5000 }); } }); test('移动端表单输入功能正常', async ({ page }) => { const testData = { name: '移动端测试用户', phone: '13900139000', email: 'mobile@test.com', subject: '移动端测试咨询', message: '这是一条来自移动端 E2E 测试的咨询消息。', }; const nameInput = page.locator('[data-testid="name-input"]').first(); await nameInput.fill(testData.name); expect(await nameInput.inputValue()).toBe(testData.name); const phoneInput = page.locator('[data-testid="phone-input"]').first(); await phoneInput.fill(testData.phone); expect(await phoneInput.inputValue()).toBe(testData.phone); const emailInput = page.locator('[data-testid="email-input"]').first(); await emailInput.fill(testData.email); expect(await emailInput.inputValue()).toBe(testData.email); const subjectInput = page.locator('[data-testid="subject-input"]').first(); await subjectInput.fill(testData.subject); expect(await subjectInput.inputValue()).toBe(testData.subject); const messageInput = page.locator('[data-testid="message-input"]').first(); await messageInput.fill(testData.message); expect(await messageInput.inputValue()).toBe(testData.message); }); test('移动端填写有效信息并提交成功', async ({ page }) => { let routeHit = false; // 拦截表单提交 API,返回成功响应 await page.route('/api/contact', async (route) => { routeHit = true; await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ success: true, message: '发送成功' }), }); }); // 填写表单 await page.locator('[data-testid="name-input"]').fill('移动端测试用户'); await page.locator('[data-testid="phone-input"]').fill('13900139000'); await page.locator('[data-testid="email-input"]').fill('mobile@test.com'); await page.locator('[data-testid="subject-input"]').fill('移动端测试咨询'); await page.locator('[data-testid="message-input"]').fill('这是一条来自移动端 E2E 测试的咨询消息。'); // 使用原生 DOM click() 触发提交 const submitButton = page.locator('[data-testid="submit-button"]'); await submitButton.scrollIntoViewIfNeeded(); await page.waitForTimeout(300); await page.evaluate(() => { const btn = document.querySelector('[data-testid="submit-button"]') as HTMLButtonElement | null; btn?.click(); }); // 等待成功状态出现 await page.waitForTimeout(1500); console.log('Route hit in mobile success test:', routeHit); // 验证成功提示或成功状态存在 const bodyText = await page.locator('body').textContent(); const hasSuccessIndicator = bodyText!.includes('消息已发送') || bodyText!.includes('感谢') || bodyText!.includes('发送成功'); expect(hasSuccessIndicator).toBe(true); }); test('移动端表单必填验证工作正常', async ({ page }) => { const submitButton = page.locator('[data-testid="submit-button"]').first(); await expect(submitButton).toBeVisible({ timeout: 5000 }); // 直接提交空表单 await submitButton.scrollIntoViewIfNeeded(); await page.waitForTimeout(300); await page.evaluate(() => { const btn = document.querySelector('[data-testid="submit-button"]') as HTMLButtonElement | null; btn?.click(); }); // 等待验证错误出现 await page.waitForTimeout(500); // 检查是否出现错误提示 const errorMessages = page.locator('[data-testid="error-message"], .error-message, [role="alert"]'); const errorCount = await errorMessages.count(); expect(errorCount).toBeGreaterThan(0); }); }); // ==================== 3. 移动端产品浏览测试 ==================== test.describe('移动端产品浏览 - 产品中心 @mobile @regression', () => { test.beforeEach(async ({ page }) => { await page.goto('/products', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(2000); }); test('移动端产品列表页面加载成功', async ({ page }) => { await expect(page).toHaveURL(/\/products/); // 检查页面标题包含"产品" const pageTitle = page.locator('h1, h2').first(); await expect(pageTitle).toBeVisible(); const titleText = await pageTitle.textContent(); expect(titleText).toContain('产品'); }); test('移动端产品卡片可见且布局完整', async ({ page }) => { // 检查产品卡片容器 const productCards = page.locator('a[href*="/products/"]:not([href$="/products"])'); const cardCount = await productCards.count(); expect(cardCount).toBeGreaterThan(0); // 检查每个卡片在移动视口中可见且无水平溢出 for (let i = 0; i < Math.min(cardCount, 3); i++) { const card = productCards.nth(i); await expect(card).toBeVisible(); // 检查卡片边界在视口内 const box = await card.boundingBox(); expect(box).not.toBeNull(); if (box) { expect(box.x).toBeGreaterThanOrEqual(0); expect(box.x + box.width).toBeLessThanOrEqual(390); // iPhone 14 宽度 } } }); test('移动端无水平滚动条', async ({ page }) => { // 检查是否出现水平滚动条 const hasHorizontalScroll = await page.evaluate(() => { return document.documentElement.scrollWidth > document.documentElement.clientWidth; }); expect(hasHorizontalScroll).toBeFalsy(); }); test('移动端点击产品卡片进入详情页', async ({ page }) => { const productLinks = page.locator('a[href*="/products/"]:not([href$="/products"])'); const linkCount = await productLinks.count(); expect(linkCount).toBeGreaterThan(0); // 点击第一个产品卡片 const firstProduct = productLinks.first(); await firstProduct.click(); await page.waitForURL(/\/products\/[^/]+$/, { timeout: 10000 }); expect(page.url()).toMatch(/\/products\/[^/]+$/); }); test('移动端产品详情页内容完整', async ({ page }) => { const productLinks = page.locator('a[href*="/products/"]:not([href$="/products"])'); const linkCount = await productLinks.count(); expect(linkCount).toBeGreaterThan(0); const firstProduct = productLinks.first(); await firstProduct.click(); await page.waitForURL(/\/products\/.+/, { timeout: 10000 }); // 详情页主要内容应渲染 const mainContent = page.locator('main').first(); await expect(mainContent).toBeVisible({ timeout: 10000 }); const contentText = await mainContent.textContent(); expect(contentText!.length).toBeGreaterThan(100); // 移动端详情页应包含返回操作 const backLink = page.locator('a[href="/products"], a:has-text("返回")').first(); if (await backLink.count() > 0) { await expect(backLink).toBeVisible(); } }); test('移动端产品详情页可返回列表', async ({ page }) => { const productLinks = page.locator('a[href*="/products/"]:not([href$="/products"])'); const linkCount = await productLinks.count(); expect(linkCount).toBeGreaterThan(0); const firstProduct = productLinks.first(); await firstProduct.click(); await page.waitForURL(/\/products\/.+/, { timeout: 10000 }); // 查找返回或面包屑链接 const backLink = page.locator('a[href="/products"], a:has-text("返回")').first(); if (await backLink.count() > 0 && await backLink.isVisible()) { await backLink.click(); await page.waitForURL(/\/products$/, { timeout: 10000 }); await expect(page).toHaveURL(/\/products$/); } }); });