feat(e2e): 添加完整的E2E测试框架和测试用例
添加Playwright测试框架配置和基础页面对象 实现冒烟测试用例覆盖首页和联系页面核心功能 更新导航组件以支持滚动高亮功能 添加BackButton组件统一返回按钮行为 配置Woodpecker CI集成和测试报告生成
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
import { test, expect } from '../../fixtures/base.fixture';
|
||||
|
||||
test.describe('联系表单回归测试 @regression', () => {
|
||||
test.beforeEach(async ({ contactPage }) => {
|
||||
await contactPage.goto();
|
||||
await contactPage.waitForPageLoad();
|
||||
});
|
||||
|
||||
test('应该能够提交完整的表单', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillAndSubmitForm(formData);
|
||||
await contactPage.waitForFormSubmission();
|
||||
const isSubmitted = await contactPage.isFormSubmitted();
|
||||
expect(isSubmitted).toBe(true);
|
||||
});
|
||||
|
||||
test('应该验证必填字段', async ({ contactPage }) => {
|
||||
await contactPage.submitForm();
|
||||
await contactPage.waitForFormSubmission();
|
||||
const isSubmitted = await contactPage.isFormSubmitted();
|
||||
expect(isSubmitted).toBe(false);
|
||||
});
|
||||
|
||||
test('应该验证邮箱格式', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
formData.email = testDataGenerator.generateInvalidEmail();
|
||||
await contactPage.fillContactForm(formData);
|
||||
const isValid = await contactPage.isEmailValid();
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够清空表单', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillContactForm(formData);
|
||||
await contactPage.clearForm();
|
||||
|
||||
const nameValue = await contactPage.getNameInputValue();
|
||||
const emailValue = await contactPage.getEmailInputValue();
|
||||
const subjectValue = await contactPage.getSubjectInputValue();
|
||||
const messageValue = await contactPage.getMessageInputValue();
|
||||
|
||||
expect(nameValue).toBe('');
|
||||
expect(emailValue).toBe('');
|
||||
expect(subjectValue).toBe('');
|
||||
expect(messageValue).toBe('');
|
||||
});
|
||||
|
||||
test('应该能够输入长消息', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
formData.message = testDataGenerator.generateMessage(200, 500);
|
||||
await contactPage.fillContactForm(formData);
|
||||
const messageValue = await contactPage.getMessageInputValue();
|
||||
expect(messageValue).toBe(formData.message);
|
||||
});
|
||||
|
||||
test('应该能够输入特殊字符', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
formData.message = testDataGenerator.generateSpecialCharacters();
|
||||
await contactPage.fillContactForm(formData);
|
||||
const messageValue = await contactPage.getMessageInputValue();
|
||||
expect(messageValue).toBe(formData.message);
|
||||
});
|
||||
|
||||
test('应该能够输入中文字符', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
formData.message = testDataGenerator.generateChineseCharacters();
|
||||
await contactPage.fillContactForm(formData);
|
||||
const messageValue = await contactPage.getMessageInputValue();
|
||||
expect(messageValue).toBe(formData.message);
|
||||
});
|
||||
|
||||
test('应该能够输入混合内容', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
formData.message = testDataGenerator.generateMixedContent();
|
||||
await contactPage.fillContactForm(formData);
|
||||
const messageValue = await contactPage.getMessageInputValue();
|
||||
expect(messageValue).toBe(formData.message);
|
||||
});
|
||||
|
||||
test('应该能够聚焦和失焦字段', async ({ contactPage }) => {
|
||||
await contactPage.focusOnField('name');
|
||||
const isNameFocused = await contactPage.nameInput.evaluate(el => document.activeElement === el);
|
||||
expect(isNameFocused).toBe(true);
|
||||
|
||||
await contactPage.blurField('name');
|
||||
const isNameBlurred = await contactPage.nameInput.evaluate(el => document.activeElement !== el);
|
||||
expect(isNameBlurred).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够使用键盘导航表单', async ({ contactPage }) => {
|
||||
await contactPage.nameInput.focus();
|
||||
await contactPage.page.keyboard.press('Tab');
|
||||
await contactPage.page.waitForTimeout(100);
|
||||
const focusedElement = await contactPage.page.evaluate(() => document.activeElement?.tagName);
|
||||
expect(focusedElement).toBe('INPUT');
|
||||
});
|
||||
|
||||
test('应该能够使用回车键提交表单', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillContactForm(formData);
|
||||
await contactPage.messageInput.press('Enter');
|
||||
await contactPage.waitForFormSubmission();
|
||||
const isSubmitted = await contactPage.isFormSubmitted();
|
||||
expect(isSubmitted).toBe(true);
|
||||
});
|
||||
|
||||
test('应该显示提交按钮的加载状态', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillContactForm(formData);
|
||||
await contactPage.submitButton.click();
|
||||
await contactPage.page.waitForTimeout(500);
|
||||
const isLoading = await contactPage.isSubmitButtonLoading();
|
||||
expect(isLoading).toBe(true);
|
||||
});
|
||||
|
||||
test('应该显示成功消息', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillAndSubmitForm(formData);
|
||||
await contactPage.waitForFormSubmission();
|
||||
const isSuccessVisible = await contactPage.isSuccessMessageVisible();
|
||||
expect(isSuccessVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('应该显示正确的成功消息文本', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillAndSubmitForm(formData);
|
||||
await contactPage.waitForFormSubmission();
|
||||
const successText = await contactPage.getSuccessMessageText();
|
||||
expect(successText).toContain('消息已发送');
|
||||
});
|
||||
|
||||
test('应该能够重新提交表单', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData1 = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillAndSubmitForm(formData1);
|
||||
await contactPage.waitForFormSubmission();
|
||||
|
||||
await contactPage.page.reload();
|
||||
await contactPage.waitForPageLoad();
|
||||
|
||||
const formData2 = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillAndSubmitForm(formData2);
|
||||
await contactPage.waitForFormSubmission();
|
||||
const isSubmitted = await contactPage.isFormSubmitted();
|
||||
expect(isSubmitted).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够输入空格', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
formData.message = ' 测试消息 ';
|
||||
await contactPage.fillContactForm(formData);
|
||||
const messageValue = await contactPage.getMessageInputValue();
|
||||
expect(messageValue).toBe(' 测试消息 ');
|
||||
});
|
||||
|
||||
test('应该能够输入换行符', async ({ contactPage }) => {
|
||||
const message = '第一行\n第二行\n第三行';
|
||||
await contactPage.messageInput.fill(message);
|
||||
const messageValue = await contactPage.getMessageInputValue();
|
||||
expect(messageValue).toBe(message);
|
||||
});
|
||||
|
||||
test('应该能够输入URL', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
formData.message = `请查看我的网站:${testDataGenerator.generateUrl()}`;
|
||||
await contactPage.fillContactForm(formData);
|
||||
const messageValue = await contactPage.getMessageInputValue();
|
||||
expect(messageValue).toContain('http');
|
||||
});
|
||||
|
||||
test('应该能够输入数字', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
formData.phone = testDataGenerator.generatePhone();
|
||||
await contactPage.fillContactForm(formData);
|
||||
const phoneValue = await contactPage.getPhoneInputValue();
|
||||
expect(phoneValue).toBe(formData.phone);
|
||||
});
|
||||
|
||||
test('应该能够输入电子邮件地址', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillContactForm(formData);
|
||||
const emailValue = await contactPage.getEmailInputValue();
|
||||
expect(emailValue).toContain('@');
|
||||
expect(emailValue).toContain('.');
|
||||
});
|
||||
|
||||
test('应该能够截取表单截图', async ({ contactPage }) => {
|
||||
await contactPage.scrollToForm();
|
||||
const isVisible = await contactPage.contactForm.isVisible();
|
||||
expect(isVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够截取成功消息截图', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillAndSubmitForm(formData);
|
||||
await contactPage.waitForFormSubmission();
|
||||
const isSuccessVisible = await contactPage.isSuccessMessageVisible();
|
||||
expect(isSuccessVisible).toBe(true);
|
||||
});
|
||||
|
||||
test('应该正确处理表单重置', async ({ contactPage, testDataGenerator }) => {
|
||||
const formData = testDataGenerator.generateContactFormData();
|
||||
await contactPage.fillContactForm(formData);
|
||||
await contactPage.page.reload();
|
||||
await contactPage.waitForPageLoad();
|
||||
|
||||
const nameValue = await contactPage.getNameInputValue();
|
||||
const emailValue = await contactPage.getEmailInputValue();
|
||||
const subjectValue = await contactPage.getSubjectInputValue();
|
||||
const messageValue = await contactPage.getMessageInputValue();
|
||||
|
||||
expect(nameValue).toBe('');
|
||||
expect(emailValue).toBe('');
|
||||
expect(subjectValue).toBe('');
|
||||
expect(messageValue).toBe('');
|
||||
});
|
||||
|
||||
test('应该没有JavaScript错误', async ({ contactPage, page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('pageerror', error => {
|
||||
errors.push(error.toString());
|
||||
});
|
||||
await contactPage.waitForPageLoad();
|
||||
const formData = { name: '测试', email: 'test@example.com', subject: '测试', message: '测试消息' };
|
||||
await contactPage.fillContactForm(formData);
|
||||
await contactPage.submitForm();
|
||||
await contactPage.waitForFormSubmission();
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
test('应该正确处理网络请求', async ({ contactPage, page }) => {
|
||||
const failedRequests: string[] = [];
|
||||
page.on('requestfailed', request => {
|
||||
failedRequests.push(request.url());
|
||||
});
|
||||
await contactPage.waitForPageLoad();
|
||||
await contactPage.page.waitForTimeout(2000);
|
||||
expect(failedRequests.length).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,213 @@
|
||||
import { test, expect } from '../../fixtures/base.fixture';
|
||||
|
||||
test.describe('首页回归测试 @regression', () => {
|
||||
test.beforeEach(async ({ homePage }) => {
|
||||
await homePage.goto();
|
||||
await homePage.waitForPageLoad();
|
||||
});
|
||||
|
||||
test('应该正确响应滚动事件', async ({ homePage }) => {
|
||||
const initialBg = await homePage.getHeaderBackgroundColor();
|
||||
await homePage.scrollToSection('services');
|
||||
await homePage.page.waitForTimeout(500);
|
||||
const scrolledBg = await homePage.getHeaderBackgroundColor();
|
||||
expect(scrolledBg).not.toBe(initialBg);
|
||||
});
|
||||
|
||||
test('应该显示粘性头部', async ({ homePage }) => {
|
||||
const isSticky = await homePage.isHeaderSticky();
|
||||
expect(isSticky).toBe(true);
|
||||
});
|
||||
|
||||
test('滚动后应该显示头部阴影', async ({ homePage }) => {
|
||||
await homePage.scrollToSection('services');
|
||||
await homePage.page.waitForTimeout(500);
|
||||
const hasShadow = await homePage.isHeaderScrolled();
|
||||
expect(hasShadow).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够通过导航跳转到各个区块', async ({ homePage }) => {
|
||||
const labels = await homePage.getAllNavigationLabels();
|
||||
for (let i = 0; i < Math.min(labels.length, 3); i++) {
|
||||
await homePage.clickNavigationItem(labels[i]);
|
||||
await homePage.page.waitForTimeout(1000);
|
||||
const url = homePage.page.url();
|
||||
expect(url).toContain('#');
|
||||
}
|
||||
});
|
||||
|
||||
test('应该正确高亮当前区块的导航项', async ({ homePage }) => {
|
||||
await homePage.scrollToSection('services');
|
||||
await homePage.page.waitForTimeout(500);
|
||||
const activeItem = await homePage.getActiveNavigationItem();
|
||||
expect(activeItem).toBeTruthy();
|
||||
});
|
||||
|
||||
test('应该能够点击Logo返回首页', async ({ homePage }) => {
|
||||
await homePage.scrollToSection('services');
|
||||
await homePage.logo.click();
|
||||
await homePage.page.waitForTimeout(1000);
|
||||
const url = homePage.page.url();
|
||||
expect(url).toMatch(/\/$/);
|
||||
});
|
||||
|
||||
test('应该能够通过立即咨询按钮跳转到联系页面', async ({ homePage }) => {
|
||||
await homePage.clickContactButton();
|
||||
await homePage.page.waitForTimeout(1000);
|
||||
const url = homePage.page.url();
|
||||
expect(url).toContain('/contact');
|
||||
});
|
||||
|
||||
test('应该能够打开和关闭移动端菜单', async ({ homePage }) => {
|
||||
await homePage.openMobileMenu();
|
||||
await expect(homePage.mobileMenu).toBeVisible();
|
||||
|
||||
await homePage.closeMobileMenu();
|
||||
await expect(homePage.mobileMenu).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('移动端菜单应该包含所有导航项', async ({ homePage }) => {
|
||||
await homePage.openMobileMenu();
|
||||
const desktopNavItems = await homePage.getAllNavigationLabels();
|
||||
const mobileNavItems = homePage.mobileMenu.locator('a');
|
||||
const mobileCount = await mobileNavItems.count();
|
||||
expect(mobileCount).toBeGreaterThan(0);
|
||||
expect(mobileCount).toBe(desktopNavItems.length);
|
||||
});
|
||||
|
||||
test('应该能够通过移动端菜单导航', async ({ homePage }) => {
|
||||
await homePage.openMobileMenu();
|
||||
const firstLink = homePage.mobileMenu.locator('a').first();
|
||||
await firstLink.click();
|
||||
await homePage.page.waitForTimeout(1000);
|
||||
const isMenuVisible = await homePage.mobileMenu.isVisible();
|
||||
expect(isMenuVisible).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够平滑滚动到各个区块', async ({ homePage }) => {
|
||||
const sections = ['services', 'products', 'cases'];
|
||||
for (const sectionId of sections) {
|
||||
await homePage.scrollToSection(sectionId);
|
||||
const isVisible = await homePage.isSectionVisible(sectionId);
|
||||
expect(isVisible).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('应该正确显示所有区块标题', async ({ homePage }) => {
|
||||
const titles = [
|
||||
await homePage.getHeroSectionTitle(),
|
||||
await homePage.getServicesSectionTitle(),
|
||||
await homePage.getProductsSectionTitle(),
|
||||
await homePage.getCasesSectionTitle(),
|
||||
await homePage.getAboutSectionTitle(),
|
||||
await homePage.getNewsSectionTitle(),
|
||||
await homePage.getContactSectionTitle(),
|
||||
];
|
||||
titles.forEach(title => {
|
||||
expect(title).toBeTruthy();
|
||||
expect(title.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('应该能够滚动到页面底部并返回顶部', async ({ homePage }) => {
|
||||
await homePage.scrollToBottom();
|
||||
const bottomScroll = await homePage.page.evaluate(() => window.scrollY);
|
||||
expect(bottomScroll).toBeGreaterThan(0);
|
||||
|
||||
await homePage.scrollToTop();
|
||||
const topScroll = await homePage.page.evaluate(() => window.scrollY);
|
||||
expect(topScroll).toBe(0);
|
||||
});
|
||||
|
||||
test('应该正确处理快速滚动', async ({ homePage }) => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await homePage.page.evaluate(() => window.scrollBy(0, 500));
|
||||
await homePage.page.waitForTimeout(100);
|
||||
}
|
||||
const scrollPosition = await homePage.page.evaluate(() => window.scrollY);
|
||||
expect(scrollPosition).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('应该能够截取各个区块的截图', async ({ homePage }) => {
|
||||
const sections = ['home', 'services'];
|
||||
for (const sectionId of sections) {
|
||||
await homePage.scrollToSection(sectionId);
|
||||
await homePage.page.waitForTimeout(500);
|
||||
const isVisible = await homePage.isSectionVisible(sectionId);
|
||||
expect(isVisible).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('应该正确响应窗口大小变化', async ({ homePage }) => {
|
||||
await homePage.page.setViewportSize({ width: 768, height: 1024 });
|
||||
await homePage.page.waitForTimeout(500);
|
||||
await expect(homePage.header).toBeVisible();
|
||||
|
||||
await homePage.page.setViewportSize({ width: 1280, height: 720 });
|
||||
await homePage.page.waitForTimeout(500);
|
||||
await expect(homePage.header).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该能够通过键盘导航', async ({ homePage }) => {
|
||||
await homePage.page.keyboard.press('Tab');
|
||||
await homePage.page.waitForTimeout(100);
|
||||
const focusedElement = await homePage.page.evaluate(() => document.activeElement?.tagName);
|
||||
expect(focusedElement).toBeTruthy();
|
||||
});
|
||||
|
||||
test('应该正确显示页脚内容', async ({ homePage }) => {
|
||||
await homePage.scrollToBottom();
|
||||
const footerText = await homePage.getFooterText();
|
||||
expect(footerText).toBeTruthy();
|
||||
expect(footerText.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('应该能够重新加载页面', async ({ homePage }) => {
|
||||
await homePage.reload();
|
||||
await homePage.waitForPageLoad();
|
||||
await expect(homePage.header).toBeVisible();
|
||||
await expect(homePage.heroSection).toBeVisible();
|
||||
});
|
||||
|
||||
test('应该能够使用浏览器后退按钮', async ({ homePage }) => {
|
||||
await homePage.clickContactButton();
|
||||
await homePage.page.waitForTimeout(1000);
|
||||
await homePage.goBack();
|
||||
await homePage.page.waitForTimeout(1000);
|
||||
const url = homePage.page.url();
|
||||
expect(url).toMatch(/\/$/);
|
||||
});
|
||||
|
||||
test('应该能够使用浏览器前进按钮', async ({ homePage }) => {
|
||||
await homePage.clickContactButton();
|
||||
await homePage.page.waitForTimeout(1000);
|
||||
await homePage.goBack();
|
||||
await homePage.page.waitForTimeout(1000);
|
||||
await homePage.goForward();
|
||||
await homePage.page.waitForTimeout(1000);
|
||||
const url = homePage.page.url();
|
||||
expect(url).toContain('/contact');
|
||||
});
|
||||
|
||||
test('应该没有JavaScript错误', async ({ homePage, page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('pageerror', error => {
|
||||
errors.push(error.toString());
|
||||
});
|
||||
await homePage.waitForPageLoad();
|
||||
await homePage.scrollToSection('services');
|
||||
await homePage.scrollToSection('products');
|
||||
await homePage.scrollToSection('cases');
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
test('应该正确处理网络请求', async ({ homePage, page }) => {
|
||||
const failedRequests: string[] = [];
|
||||
page.on('requestfailed', request => {
|
||||
failedRequests.push(request.url());
|
||||
});
|
||||
await homePage.waitForPageLoad();
|
||||
await homePage.page.waitForTimeout(2000);
|
||||
expect(failedRequests.length).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user