feat(e2e): 添加完整的E2E测试框架和测试用例
添加Playwright测试框架配置和基础页面对象 实现冒烟测试用例覆盖首页和联系页面核心功能 更新导航组件以支持滚动高亮功能 添加BackButton组件统一返回按钮行为 配置Woodpecker CI集成和测试报告生成
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
import { Page, Locator, expect } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
|
||||
export class HomePage extends BasePage {
|
||||
readonly url: string;
|
||||
|
||||
readonly header: Locator;
|
||||
readonly logo: Locator;
|
||||
readonly navigation: Locator;
|
||||
readonly heroSection: Locator;
|
||||
readonly servicesSection: Locator;
|
||||
readonly productsSection: Locator;
|
||||
readonly casesSection: Locator;
|
||||
readonly aboutSection: Locator;
|
||||
readonly newsSection: Locator;
|
||||
readonly contactSection: Locator;
|
||||
readonly footer: Locator;
|
||||
|
||||
readonly mobileMenuButton: Locator;
|
||||
readonly mobileMenu: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
this.url = '/';
|
||||
|
||||
this.header = page.locator('header');
|
||||
this.logo = page.locator('header img[alt*="四川睿新致远"]');
|
||||
this.navigation = page.locator('nav[role="navigation"]');
|
||||
this.heroSection = page.locator('#home');
|
||||
this.servicesSection = page.locator('#services');
|
||||
this.productsSection = page.locator('#products');
|
||||
this.casesSection = page.locator('#cases');
|
||||
this.aboutSection = page.locator('#about');
|
||||
this.newsSection = page.locator('#news');
|
||||
this.contactSection = page.locator('#contact');
|
||||
this.footer = page.locator('footer');
|
||||
|
||||
this.mobileMenuButton = page.locator('button[aria-label="打开菜单"]');
|
||||
this.mobileMenu = page.locator('#mobile-menu-panel');
|
||||
}
|
||||
|
||||
async goto(): Promise<void> {
|
||||
await this.navigate(this.url);
|
||||
await this.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
async isLoaded(): Promise<boolean> {
|
||||
try {
|
||||
await this.header.waitFor({ state: 'visible', timeout: 5000 });
|
||||
await this.heroSection.waitFor({ state: 'visible', timeout: 5000 });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async waitForPageLoad(): Promise<void> {
|
||||
await this.waitForLoadState('networkidle');
|
||||
await this.header.waitFor({ state: 'visible' });
|
||||
await this.heroSection.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async getNavigationItems(): Promise<Locator[]> {
|
||||
return await this.navigation.locator('a').all();
|
||||
}
|
||||
|
||||
async clickNavigationItem(label: string): Promise<void> {
|
||||
await this.navigation.locator(`a:has-text("${label}")`).click();
|
||||
}
|
||||
|
||||
async openMobileMenu(): Promise<void> {
|
||||
if (!(await this.mobileMenu.isVisible())) {
|
||||
await this.mobileMenuButton.click();
|
||||
await this.mobileMenu.waitFor({ state: 'visible' });
|
||||
}
|
||||
}
|
||||
|
||||
async closeMobileMenu(): Promise<void> {
|
||||
if (await this.mobileMenu.isVisible()) {
|
||||
await this.mobileMenuButton.click();
|
||||
await this.mobileMenu.waitFor({ state: 'hidden' });
|
||||
}
|
||||
}
|
||||
|
||||
async scrollToSection(sectionId: string): Promise<void> {
|
||||
const section = this.page.locator(`#${sectionId}`);
|
||||
await section.scrollIntoViewIfNeeded();
|
||||
await this.page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async isSectionVisible(sectionId: string): Promise<boolean> {
|
||||
const section = this.page.locator(`#${sectionId}`);
|
||||
return await section.isVisible();
|
||||
}
|
||||
|
||||
async getSectionText(sectionId: string): Promise<string> {
|
||||
const section = this.page.locator(`#${sectionId}`);
|
||||
return await section.textContent() || '';
|
||||
}
|
||||
|
||||
async clickContactButton(): Promise<void> {
|
||||
await this.page.locator('a:has-text("立即咨询")').first().click();
|
||||
}
|
||||
|
||||
async isLogoVisible(): Promise<boolean> {
|
||||
return await this.logo.isVisible();
|
||||
}
|
||||
|
||||
async getLogoAltText(): Promise<string | null> {
|
||||
return await this.logo.getAttribute('alt');
|
||||
}
|
||||
|
||||
async isFooterVisible(): Promise<boolean> {
|
||||
return await this.footer.isVisible();
|
||||
}
|
||||
|
||||
async getFooterText(): Promise<string> {
|
||||
return await this.footer.textContent() || '';
|
||||
}
|
||||
|
||||
async waitForHeroSection(): Promise<void> {
|
||||
await this.heroSection.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async waitForServicesSection(): Promise<void> {
|
||||
await this.scrollToSection('services');
|
||||
await this.servicesSection.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async waitForProductsSection(): Promise<void> {
|
||||
await this.scrollToSection('products');
|
||||
await this.productsSection.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async waitForCasesSection(): Promise<void> {
|
||||
await this.scrollToSection('cases');
|
||||
await this.casesSection.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async waitForAboutSection(): Promise<void> {
|
||||
await this.scrollToSection('about');
|
||||
await this.aboutSection.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async waitForNewsSection(): Promise<void> {
|
||||
await this.scrollToSection('news');
|
||||
await this.newsSection.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async waitForContactSection(): Promise<void> {
|
||||
await this.scrollToSection('contact');
|
||||
await this.contactSection.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async scrollToBottom(): Promise<void> {
|
||||
await this.page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await this.page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async scrollToTop(): Promise<void> {
|
||||
await this.page.evaluate(() => window.scrollTo(0, 0));
|
||||
await this.page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async getActiveNavigationItem(): Promise<string | null> {
|
||||
const activeItem = this.navigation.locator('a[aria-current="page"]');
|
||||
if (await activeItem.count() > 0) {
|
||||
return await activeItem.textContent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async isNavigationItemActive(label: string): Promise<boolean> {
|
||||
const item = this.navigation.locator(`a:has-text("${label}")`);
|
||||
const ariaCurrent = await item.getAttribute('aria-current');
|
||||
return ariaCurrent === 'page';
|
||||
}
|
||||
|
||||
async getAllSectionIds(): Promise<string[]> {
|
||||
return await this.page.evaluate(() => {
|
||||
const sections = document.querySelectorAll('section[id]');
|
||||
return Array.from(sections).map(section => section.id);
|
||||
});
|
||||
}
|
||||
|
||||
async takeScreenshotOfSection(sectionId: string, filename: string): Promise<void> {
|
||||
const section = this.page.locator(`#${sectionId}`);
|
||||
await section.screenshot({ path: `test-results/screenshots/${filename}` });
|
||||
}
|
||||
|
||||
async getHeroSectionTitle(): Promise<string> {
|
||||
const title = this.heroSection.locator('h1, h2').first();
|
||||
return await title.textContent() || '';
|
||||
}
|
||||
|
||||
async getServicesSectionTitle(): Promise<string> {
|
||||
const title = this.servicesSection.locator('h2').first();
|
||||
return await title.textContent() || '';
|
||||
}
|
||||
|
||||
async getProductsSectionTitle(): Promise<string> {
|
||||
const title = this.productsSection.locator('h2').first();
|
||||
return await title.textContent() || '';
|
||||
}
|
||||
|
||||
async getCasesSectionTitle(): Promise<string> {
|
||||
const title = this.casesSection.locator('h2').first();
|
||||
return await title.textContent() || '';
|
||||
}
|
||||
|
||||
async getAboutSectionTitle(): Promise<string> {
|
||||
const title = this.aboutSection.locator('h2').first();
|
||||
return await title.textContent() || '';
|
||||
}
|
||||
|
||||
async getNewsSectionTitle(): Promise<string> {
|
||||
const title = this.newsSection.locator('h2').first();
|
||||
return await title.textContent() || '';
|
||||
}
|
||||
|
||||
async getContactSectionTitle(): Promise<string> {
|
||||
const title = this.contactSection.locator('h2').first();
|
||||
return await title.textContent() || '';
|
||||
}
|
||||
|
||||
async isHeaderSticky(): Promise<boolean> {
|
||||
const isSticky = await this.header.evaluate(el => {
|
||||
return window.getComputedStyle(el).position === 'fixed';
|
||||
});
|
||||
return isSticky;
|
||||
}
|
||||
|
||||
async getHeaderBackgroundColor(): Promise<string> {
|
||||
return await this.header.evaluate(el => {
|
||||
return window.getComputedStyle(el).backgroundColor;
|
||||
});
|
||||
}
|
||||
|
||||
async isHeaderScrolled(): Promise<boolean> {
|
||||
const hasShadow = await this.header.evaluate(el => {
|
||||
return window.getComputedStyle(el).boxShadow !== 'none';
|
||||
});
|
||||
return hasShadow;
|
||||
}
|
||||
|
||||
async getNavigationItemCount(): Promise<number> {
|
||||
return await this.navigation.locator('a').count();
|
||||
}
|
||||
|
||||
async getAllNavigationLabels(): Promise<string[]> {
|
||||
const items = await this.getNavigationItems();
|
||||
const labels: string[] = [];
|
||||
for (const item of items) {
|
||||
const text = await item.textContent();
|
||||
if (text) labels.push(text);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user