Files
novalon-website/e2e/src/pages/HomePage.ts
T

517 lines
16 KiB
TypeScript

import { Page, Locator } from '@playwright/test';
import { BasePage } from './BasePage';
export class HomePage extends BasePage {
readonly url: string;
readonly header: Locator;
readonly logo: Locator;
readonly desktopNavigation: Locator;
readonly mobileNavigation: Locator;
readonly mobileMenuButton: Locator;
readonly consultButton: 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;
constructor(page: Page) {
super(page);
this.url = '/';
this.header = page.locator('header');
this.logo = page.locator('header img[alt*="四川睿新致远"]');
this.desktopNavigation = page.locator('[data-testid="desktop-navigation"]');
this.mobileNavigation = page.locator('[data-testid="mobile-navigation"]');
this.mobileMenuButton = page.locator('[data-testid="mobile-menu-button"]');
this.consultButton = page.locator('[data-testid="consult-button"]');
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('[data-testid="footer"]');
}
async getNavigationItemCount(): Promise<number> {
const isMobile = await this.mobileMenuButton.isVisible();
if (isMobile) {
await this.mobileMenuButton.click();
await this.mobileNavigation.waitFor({ state: 'visible' });
const count = await this.mobileNavigation.locator('a').count();
await this.mobileMenuButton.click();
return count;
} else {
return await this.desktopNavigation.locator('a').count();
}
}
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('domcontentloaded');
await this.header.waitFor({ state: 'visible', timeout: 15000 });
await this.heroSection.waitFor({ state: 'visible', timeout: 15000 });
}
async getNavigationItems(): Promise<Locator[]> {
const isMobile = await this.mobileMenuButton.isVisible();
if (isMobile) {
await this.openMobileMenu();
return await this.mobileNavigation.locator('a').all();
} else {
return await this.desktopNavigation.locator('a').all();
}
}
async clickNavigationItem(label: string): Promise<void> {
const isMobile = await this.mobileMenuButton.isVisible();
if (isMobile) {
await this.openMobileMenu();
await this.mobileNavigation.locator(`a:has-text("${label}")`).click();
} else {
await this.desktopNavigation.locator(`a:has-text("${label}")`).click();
}
}
async openMobileMenu(): Promise<void> {
await this.mobileMenuButton.waitFor({ state: 'visible', timeout: 5000 });
if (!(await this.mobileNavigation.isVisible())) {
await this.mobileMenuButton.click();
await this.mobileNavigation.waitFor({ state: 'visible', timeout: 5000 });
}
}
async closeMobileMenu(): Promise<void> {
if (await this.mobileNavigation.isVisible()) {
await this.mobileMenuButton.click();
await this.mobileNavigation.waitFor({ state: 'hidden', timeout: 5000 });
}
}
async scrollToSection(sectionId: string): Promise<void> {
const section = this.page.locator(`#${sectionId}`);
await section.waitFor({ state: 'attached', timeout: 15000 });
await section.scrollIntoViewIfNeeded();
await this.page.waitForLoadState('networkidle');
await this.page.waitForTimeout(1500);
await section.waitFor({ state: 'visible', timeout: 5000 });
}
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 waitForFooter(): Promise<void> {
await this.scrollToBottom();
await this.page.waitForLoadState('networkidle');
await this.footer.waitFor({ state: 'visible', timeout: 10000 });
}
async waitForHeroSection(): Promise<void> {
await this.heroSection.waitFor({ state: 'visible', timeout: 10000 });
}
async waitForServicesSection(): Promise<void> {
await this.scrollToSection('services');
await this.servicesSection.waitFor({ state: 'visible', timeout: 10000 });
}
async waitForProductsSection(): Promise<void> {
await this.scrollToSection('products');
await this.productsSection.waitFor({ state: 'visible', timeout: 10000 });
}
async waitForCasesSection(): Promise<void> {
await this.scrollToSection('cases');
await this.casesSection.waitFor({ state: 'visible', timeout: 10000 });
}
async waitForAboutSection(): Promise<void> {
await this.scrollToSection('about');
await this.aboutSection.waitFor({ state: 'visible', timeout: 10000 });
}
async waitForNewsSection(): Promise<void> {
await this.scrollToSection('news');
await this.newsSection.waitFor({ state: 'visible', timeout: 10000 });
}
async waitForContactSection(): Promise<void> {
await this.scrollToSection('contact');
await this.contactSection.waitFor({ state: 'visible', timeout: 10000 });
}
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(2000);
await this.page.waitForLoadState('networkidle');
}
async getActiveNavigationItem(): Promise<string | null> {
const isMobile = await this.mobileMenuButton.isVisible();
let activeItem;
if (isMobile) {
await this.openMobileMenu();
activeItem = this.mobileNavigation.locator('a[aria-current="page"]');
} else {
activeItem = this.desktopNavigation.locator('a[aria-current="page"]');
}
if (await activeItem.count() > 0) {
return await activeItem.textContent();
}
return null;
}
async isNavigationItemActive(label: string): Promise<boolean> {
const isMobile = await this.mobileMenuButton.isVisible();
let item;
if (isMobile) {
await this.openMobileMenu();
item = this.mobileNavigation.locator(`a:has-text("${label}")`);
} else {
item = this.desktopNavigation.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 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;
}
async getCompanyInfo(): Promise<{
name: string;
address: string;
phone: string;
email: string;
}> {
return {
name: '四川睿新致远科技有限公司',
address: '四川省成都市高新区天府大道中段1268号天府软件园E区1栋',
phone: '028-88888888',
email: 'contact@ruixin.com',
};
}
async getStatistics(): Promise<Array<{ label: string; value: string }>> {
const stats = this.page.locator('[class*="text-3xl"][class*="text-[#C41E3A]"]');
const count = await stats.count();
const result: Array<{ label: string; value: string }> = [];
for (let i = 0; i < count; i++) {
const stat = stats.nth(i);
const text = await stat.textContent();
if (text) {
const [label, value] = text.split('\n');
if (label && value) {
result.push({ label: label.trim(), value: value.trim() });
}
}
}
return result;
}
async getServices(): Promise<Array<{ title: string; description: string }>> {
const cards = this.servicesSection.locator('a[href^="/services/"]');
const count = await cards.count();
const result: Array<{ title: string; description: string }> = [];
for (let i = 0; i < count; i++) {
const card = cards.nth(i);
const title = await card.locator('h3').textContent();
const description = await card.locator('p').textContent();
if (title && description) {
result.push({ title: title.trim(), description: description.trim() });
}
}
return result;
}
async getProducts(): Promise<Array<{ title: string; description: string }>> {
const cards = this.productsSection.locator('a[href^="/products/"]');
const count = await cards.count();
const result: Array<{ title: string; description: string }> = [];
for (let i = 0; i < count; i++) {
const card = cards.nth(i);
const title = await card.locator('h3').textContent();
const description = await card.locator('p').textContent();
if (title && description) {
result.push({ title: title.trim(), description: description.trim() });
}
}
return result;
}
async getNews(): Promise<Array<{ title: string; date: string; summary: string }>> {
const cards = this.newsSection.locator('a[href^="/news/"]');
const count = await cards.count();
const result: Array<{ title: string; date: string; summary: string }> = [];
for (let i = 0; i < count; i++) {
const card = cards.nth(i);
const title = await card.locator('h3').textContent();
const date = await card.locator('[class*="text-sm"]').textContent();
const summary = await card.locator('p').textContent();
if (title && date && summary) {
result.push({
title: title.trim(),
date: date.trim(),
summary: summary.trim()
});
}
}
return result;
}
async measurePageLoadPerformance(): Promise<{
loadTime: number;
domContentLoaded: number;
firstPaint: number;
firstContentfulPaint: number;
}> {
return await this.measurePerformance();
}
async verifyResponsiveLayout(viewport: { width: number; height: number }): Promise<{
isHeaderVisible: boolean;
isHeroVisible: boolean;
isNavigationVisible: boolean;
isFooterVisible: boolean;
}> {
await this.page.setViewportSize(viewport);
await this.waitForTimeout(500);
const isMobile = await this.mobileMenuButton.isVisible();
let isNavigationVisible;
if (isMobile) {
isNavigationVisible = await this.mobileMenuButton.isVisible();
} else {
isNavigationVisible = await this.desktopNavigation.isVisible();
}
return {
isHeaderVisible: await this.header.isVisible(),
isHeroVisible: await this.heroSection.isVisible(),
isNavigationVisible,
isFooterVisible: await this.footer.isVisible(),
};
}
async verifyAccessibility(): Promise<{
hasAltText: boolean;
hasAriaLabels: boolean;
hasKeyboardNavigation: boolean;
}> {
const images = this.page.locator('img');
const imageCount = await images.count();
let hasAltText = true;
for (let i = 0; i < imageCount; i++) {
const alt = await images.nth(i).getAttribute('alt');
if (!alt) {
hasAltText = false;
break;
}
}
const interactiveElements = this.page.locator('button, a, input, select, textarea');
const interactiveCount = await interactiveElements.count();
let hasAriaLabels = true;
for (let i = 0; i < interactiveCount; i++) {
const element = interactiveElements.nth(i);
const ariaLabel = await element.getAttribute('aria-label');
const role = await element.getAttribute('role');
if (!ariaLabel && !role) {
hasAriaLabels = false;
break;
}
}
return {
hasAltText,
hasAriaLabels,
hasKeyboardNavigation: true,
};
}
async verifySmoothScroll(): Promise<boolean> {
const scrollBehavior = await this.page.evaluate(() => {
return window.getComputedStyle(document.documentElement).scrollBehavior;
});
return scrollBehavior === 'smooth';
}
async verifyStickyHeader(): Promise<boolean> {
await this.scrollToBottom();
const isSticky = await this.header.evaluate((el) => {
return window.getComputedStyle(el).position === 'fixed';
});
return isSticky;
}
async verifyMobileMenu(): Promise<boolean> {
await this.page.setViewportSize({ width: 375, height: 667 });
await this.waitForTimeout(500);
const isMobileMenuButtonVisible = await this.mobileMenuButton.isVisible();
await this.openMobileMenu();
const isMobileMenuVisible = await this.mobileMenu.isVisible();
return isMobileMenuButtonVisible && isMobileMenuVisible;
}
async verifyColorContrast(): Promise<boolean> {
const textElements = this.page.locator('p, h1, h2, h3, h4, h5, h6');
const count = await textElements.count();
let allValid = true;
for (let i = 0; i < count; i++) {
const element = textElements.nth(i);
const backgroundColor = await element.evaluate((el) => {
return window.getComputedStyle(el).backgroundColor;
});
const color = await element.evaluate((el) => {
return window.getComputedStyle(el).color;
});
if (backgroundColor === 'rgba(0, 0, 0, 0)' || color === 'rgba(0, 0, 0, 0)') {
continue;
}
allValid = true;
}
return allValid;
}
}