feat(e2e): 添加完整的E2E测试框架和测试用例

添加Playwright测试框架配置和基础页面对象
实现冒烟测试用例覆盖首页和联系页面核心功能
更新导航组件以支持滚动高亮功能
添加BackButton组件统一返回按钮行为
配置Woodpecker CI集成和测试报告生成
This commit is contained in:
张翔
2026-02-27 10:30:33 +08:00
parent 4a616fe96e
commit 5d5b7feb0a
50 changed files with 6765 additions and 46 deletions
@@ -0,0 +1,56 @@
import { test, expect } from '@playwright/test';
test('检查联系页面所有元素', async ({ page }) => {
await page.goto('/contact');
await page.waitForLoadState('networkidle');
const badges = await page.locator('[class*="badge"]').all();
console.log('找到的badge数量:', badges.length);
for (let i = 0; i < badges.length; i++) {
const badge = badges[i];
const className = await badge.evaluate(el => el.className);
const text = await badge.textContent();
console.log(`Badge ${i}: ${className} - ${text}`);
}
const contactCard = page.locator('[class*="card"]').filter({ hasText: '联系方式' }).first();
const contactCardText = await contactCard.textContent();
console.log('联系卡片文本:', contactCardText?.substring(0, 100));
const workHoursCard = page.locator('[class*="card"]').filter({ hasText: '工作时间' }).first();
const workHoursCardText = await workHoursCard.textContent();
console.log('工作时间卡片文本:', workHoursCardText?.substring(0, 100));
const addressH3 = contactCard.locator('h3:has-text("公司地址")');
const addressParent = addressH3.locator('..');
const addressGrandParent = addressParent.locator('..');
const addressP = addressGrandParent.locator('p');
const addressText = await addressP.textContent();
console.log('地址文本:', addressText);
const phoneH3 = contactCard.locator('h3:has-text("联系电话")');
const phoneParent = phoneH3.locator('..');
const phoneGrandParent = phoneParent.locator('..');
const phoneP = phoneGrandParent.locator('p');
const phoneText = await phoneP.textContent();
console.log('电话文本:', phoneText);
const emailH3 = contactCard.locator('h3:has-text("电子邮箱")');
const emailParent = emailH3.locator('..');
const emailGrandParent = emailParent.locator('..');
const emailP = emailGrandParent.locator('p');
const emailText = await emailP.textContent();
console.log('邮箱文本:', emailText);
const workHoursRows = workHoursCard.locator('.space-y-2 > div');
const workHoursCount = await workHoursRows.count();
console.log('工作时间行数:', workHoursCount);
for (let i = 0; i < workHoursCount; i++) {
const row = workHoursRows.nth(i);
const day = await row.locator('span').first().textContent();
const hours = await row.locator('span').nth(1).textContent();
console.log(`工作时间 ${i}: ${day} - ${hours}`);
}
});
@@ -0,0 +1,21 @@
import { test, expect } from '@playwright/test';
test('检查联系卡片详细结构', async ({ page }) => {
await page.goto('/contact');
await page.waitForLoadState('networkidle');
const contactCard = page.locator('[data-slot="card"]').filter({ hasText: '联系方式' }).first();
const contactCardChildren = await contactCard.locator('div').all();
console.log('联系卡片子元素数量:', contactCardChildren.length);
for (let i = 0; i < contactCardChildren.length; i++) {
const child = contactCardChildren[i];
const className = await child.evaluate(el => el.className);
const text = await child.textContent();
console.log(`子元素 ${i}: ${className.substring(0, 80)} - ${text?.substring(0, 50)}`);
}
const allDivs = await contactCard.locator('div').all();
console.log('所有div数量:', allDivs.length);
});
@@ -0,0 +1,25 @@
import { test, expect } from '@playwright/test';
test('检查联系页面Card标题', async ({ page }) => {
await page.goto('/contact');
await page.waitForLoadState('networkidle');
const cardTitles = await page.locator('CardTitle').all();
console.log('找到的CardTitle数量:', cardTitles.length);
for (let i = 0; i < cardTitles.length; i++) {
const title = cardTitles[i];
const text = await title.textContent();
console.log(`CardTitle ${i}: ${text}`);
}
const allCards = await page.locator('[class*="card"]').all();
console.log('找到的所有card元素数量:', allCards.length);
for (let i = 0; i < allCards.length; i++) {
const card = allCards[i];
const className = await card.evaluate(el => el.className);
const text = await card.textContent();
console.log(`Card ${i}: ${className.substring(0, 50)} - ${text?.substring(0, 50)}`);
}
});
+23
View File
@@ -0,0 +1,23 @@
import { test, expect } from '@playwright/test';
test('检查联系卡片详细信息', async ({ page }) => {
await page.goto('/contact');
await page.waitForLoadState('networkidle');
const contactCard = page.locator('.card').filter({ hasText: '联系方式' });
const contactText = await contactCard.textContent();
console.log('联系方式卡片内容:', contactText);
const workHoursCard = page.locator('.card').filter({ hasText: '工作时间' });
const workHoursText = await workHoursCard.textContent();
console.log('工作时间卡片内容:', workHoursText);
const allCards = await page.locator('.card').all();
console.log('所有卡片数量:', allCards.length);
for (let i = 0; i < allCards.length; i++) {
const card = allCards[i];
const text = await card.textContent();
console.log(`卡片 ${i}:`, text?.substring(0, 100));
}
});
@@ -0,0 +1,41 @@
import { test, expect } from '@playwright/test';
test('检查联系页面详细元素', async ({ page }) => {
await page.goto('/contact');
await page.waitForLoadState('networkidle');
const pageHeader = page.locator('h1:has-text("与我们取得联系")');
const pageHeaderParent = pageHeader.locator('..');
const pageHeaderGrandParent = pageHeaderParent.locator('..');
console.log('Page Header parent:', await pageHeaderParent.evaluate(el => el.className));
console.log('Page Header grand parent:', await pageHeaderGrandParent.evaluate(el => el.className));
const badges = await pageHeaderGrandParent.locator('.badge').all();
console.log('找到的badge数量:', badges.length);
for (let i = 0; i < badges.length; i++) {
const badge = badges[i];
const text = await badge.textContent();
console.log(`Badge ${i}: ${text}`);
}
const contactCard = page.locator('h3:has-text("联系方式")');
const contactCardParent = contactCard.locator('..');
const contactCardGrandParent = contactCardParent.locator('..');
const contactCardGreatGrandParent = contactCardGrandParent.locator('..');
console.log('Contact card great grand parent:', await contactCardGreatGrandParent.evaluate(el => el.className));
const addressElement = contactCard.locator('text=公司地址').locator('..').locator('p');
const addressText = await addressElement.textContent();
console.log('地址:', addressText);
const phoneElement = contactCard.locator('text=联系电话').locator('..').locator('p');
const phoneText = await phoneElement.textContent();
console.log('电话:', phoneText);
const emailElement = contactCard.locator('text=电子邮箱').locator('..').locator('p');
const emailText = await emailElement.textContent();
console.log('邮箱:', emailText);
});
+21
View File
@@ -0,0 +1,21 @@
import { test, expect } from '@playwright/test';
test('检查联系页面完整DOM', async ({ page }) => {
await page.goto('/contact');
await page.waitForLoadState('networkidle');
const contactCard = page.locator('[class*="card"]').filter({ hasText: '联系方式' }).first();
const contactCardHTML = await contactCard.innerHTML();
console.log('联系卡片HTML:', contactCardHTML.substring(0, 500));
const contactCardChildren = await contactCard.locator('div').all();
console.log('联系卡片子元素数量:', contactCardChildren.length);
for (let i = 0; i < contactCardChildren.length; i++) {
const child = contactCardChildren[i];
const className = await child.evaluate(el => el.className);
const text = await child.textContent();
console.log(`子元素 ${i}: ${className.substring(0, 50)} - ${text?.substring(0, 50)}`);
}
});
@@ -0,0 +1,30 @@
import { test, expect } from '@playwright/test';
test('检查联系页面完整结构', async ({ page }) => {
await page.goto('/contact');
await page.waitForLoadState('networkidle');
const allH3 = await page.locator('h3').all();
console.log('找到的h3元素数量:', allH3.length);
for (let i = 0; i < allH3.length; i++) {
const h3 = allH3[i];
const text = await h3.textContent();
console.log(`H3 ${i}: ${text}`);
const parent = h3.locator('..');
const grandParent = parent.locator('..');
const greatGrandParent = grandParent.locator('..');
try {
const parentClass = await parent.evaluate(el => el.className);
const grandParentClass = await grandParent.evaluate(el => el.className);
const greatGrandParentClass = await greatGrandParent.evaluate(el => el.className);
console.log(` Parent: ${parentClass}`);
console.log(` Grand Parent: ${grandParentClass}`);
console.log(` Great Grand Parent: ${greatGrandParentClass}`);
} catch (e) {
console.log(` Error getting parent info: ${e}`);
}
}
});
+34
View File
@@ -0,0 +1,34 @@
import { test, expect } from '@playwright/test';
test('检查联系页面元素', async ({ page }) => {
await page.goto('/contact');
await page.waitForLoadState('networkidle');
const pageHeaders = await page.locator('.page-header').all();
console.log('找到的page-header数量:', pageHeaders.length);
const forms = await page.locator('form').all();
console.log('找到的form数量:', forms.length);
const cards = await page.locator('.card').all();
console.log('找到的card数量:', cards.length);
for (let i = 0; i < cards.length; i++) {
const card = cards[i];
const text = await card.textContent();
console.log(`Card ${i}: ${text?.substring(0, 50)}`);
}
const inputs = await page.locator('input').all();
console.log('找到的input数量:', inputs.length);
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i];
const name = await input.getAttribute('name');
const type = await input.getAttribute('type');
console.log(`Input ${i}: name="${name}", type="${type}"`);
}
const textareas = await page.locator('textarea').all();
console.log('找到的textarea数量:', textareas.length);
});
@@ -0,0 +1,28 @@
import { test, expect } from '@playwright/test';
test('检查联系页面结构', async ({ page }) => {
await page.goto('/contact');
await page.waitForLoadState('networkidle');
const allElements = await page.evaluate(() => {
const result: any = {};
const h1s = document.querySelectorAll('h1');
result.h1Count = h1s.length;
result.h1Text = Array.from(h1s).map(h => h.textContent?.substring(0, 50));
const cards = document.querySelectorAll('[class*="card"]');
result.cardCount = cards.length;
result.cardText = Array.from(cards).map(c => c.textContent?.substring(0, 50));
const pageHeaders = document.querySelectorAll('[class*="page-header"]');
result.pageHeaderCount = pageHeaders.length;
const contactInfoCards = document.querySelectorAll('[class*="contact"]');
result.contactInfoCount = contactInfoCards.length;
return result;
});
console.log('联系页面结构:', JSON.stringify(allElements, null, 2));
});
+37
View File
@@ -0,0 +1,37 @@
import { test, expect } from '@playwright/test';
test('测试页面加载', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
const title = await page.title();
console.log('页面标题:', title);
const body = await page.locator('body').textContent();
console.log('页面内容长度:', body?.length);
const headers = await page.locator('header').all();
console.log('找到的header元素数量:', headers.length);
const navs = await page.locator('nav').all();
console.log('找到的nav元素数量:', navs.length);
const sections = await page.locator('section').all();
console.log('找到的section元素数量:', sections.length);
const allElements = await page.evaluate(() => {
const headers = document.querySelectorAll('header');
const navs = document.querySelectorAll('nav');
const sections = document.querySelectorAll('section');
return {
headers: headers.length,
navs: navs.length,
sections: sections.length,
bodyText: document.body.textContent?.substring(0, 200)
};
});
console.log('页面元素信息:', allElements);
expect(title).toBeTruthy();
});
+36
View File
@@ -0,0 +1,36 @@
import { test, expect } from '@playwright/test';
test('检查页面元素选择器', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
const logoImages = await page.locator('header img').all();
console.log('找到的logo图片数量:', logoImages.length);
for (let i = 0; i < logoImages.length; i++) {
const img = logoImages[i];
const alt = await img.getAttribute('alt');
const src = await img.getAttribute('src');
console.log(`Logo ${i}: alt="${alt}", src="${src}"`);
}
const contactButtons = await page.locator('a:has-text("立即咨询")').all();
console.log('找到的立即咨询按钮数量:', contactButtons.length);
for (let i = 0; i < contactButtons.length; i++) {
const btn = contactButtons[i];
const href = await btn.getAttribute('href');
const text = await btn.textContent();
console.log(`立即咨询按钮 ${i}: text="${text}", href="${href}"`);
}
const mobileMenuButtons = await page.locator('button').all();
console.log('找到的按钮数量:', mobileMenuButtons.length);
for (let i = 0; i < mobileMenuButtons.length; i++) {
const btn = mobileMenuButtons[i];
const ariaLabel = await btn.getAttribute('aria-label');
const text = await btn.textContent();
console.log(`按钮 ${i}: aria-label="${ariaLabel}", text="${text}"`);
}
});