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 { 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 { await this.navigate(this.url); await this.waitForLoadState('networkidle'); } async isLoaded(): Promise { 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 { await this.waitForLoadState('domcontentloaded'); await this.header.waitFor({ state: 'visible', timeout: 15000 }); await this.heroSection.waitFor({ state: 'visible', timeout: 15000 }); } async getNavigationItems(): Promise { 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 { 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 { 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 { if (await this.mobileNavigation.isVisible()) { await this.mobileMenuButton.click(); await this.mobileNavigation.waitFor({ state: 'hidden', timeout: 5000 }); } } async scrollToSection(sectionId: string): Promise { 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 { const section = this.page.locator(`#${sectionId}`); return await section.isVisible(); } async getSectionText(sectionId: string): Promise { const section = this.page.locator(`#${sectionId}`); return await section.textContent() || ''; } async clickContactButton(): Promise { await this.page.locator('a:has-text("立即咨询")').first().click(); } async isLogoVisible(): Promise { return await this.logo.isVisible(); } async getLogoAltText(): Promise { return await this.logo.getAttribute('alt'); } async isFooterVisible(): Promise { return await this.footer.isVisible(); } async getFooterText(): Promise { return await this.footer.textContent() || ''; } async waitForFooter(): Promise { await this.scrollToBottom(); await this.page.waitForLoadState('networkidle'); await this.footer.waitFor({ state: 'visible', timeout: 10000 }); } async waitForHeroSection(): Promise { await this.heroSection.waitFor({ state: 'visible', timeout: 10000 }); } async waitForServicesSection(): Promise { await this.scrollToSection('services'); await this.servicesSection.waitFor({ state: 'visible', timeout: 10000 }); } async waitForProductsSection(): Promise { await this.scrollToSection('products'); await this.productsSection.waitFor({ state: 'visible', timeout: 10000 }); } async waitForCasesSection(): Promise { await this.scrollToSection('cases'); await this.casesSection.waitFor({ state: 'visible', timeout: 10000 }); } async waitForAboutSection(): Promise { await this.scrollToSection('about'); await this.aboutSection.waitFor({ state: 'visible', timeout: 10000 }); } async waitForNewsSection(): Promise { await this.scrollToSection('news'); await this.newsSection.waitFor({ state: 'visible', timeout: 10000 }); } async waitForContactSection(): Promise { await this.scrollToSection('contact'); await this.contactSection.waitFor({ state: 'visible', timeout: 10000 }); } async scrollToBottom(): Promise { await this.page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await this.page.waitForTimeout(500); } async scrollToTop(): Promise { await this.page.evaluate(() => window.scrollTo(0, 0)); await this.page.waitForTimeout(2000); await this.page.waitForLoadState('networkidle'); } async getActiveNavigationItem(): Promise { 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 { 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 { 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 { const section = this.page.locator(`#${sectionId}`); await section.screenshot({ path: `test-results/screenshots/${filename}` }); } async getHeroSectionTitle(): Promise { const title = this.heroSection.locator('h1, h2').first(); return await title.textContent() || ''; } async getServicesSectionTitle(): Promise { const title = this.servicesSection.locator('h2').first(); return await title.textContent() || ''; } async getProductsSectionTitle(): Promise { const title = this.productsSection.locator('h2').first(); return await title.textContent() || ''; } async getCasesSectionTitle(): Promise { const title = this.casesSection.locator('h2').first(); return await title.textContent() || ''; } async getAboutSectionTitle(): Promise { const title = this.aboutSection.locator('h2').first(); return await title.textContent() || ''; } async getNewsSectionTitle(): Promise { const title = this.newsSection.locator('h2').first(); return await title.textContent() || ''; } async getContactSectionTitle(): Promise { const title = this.contactSection.locator('h2').first(); return await title.textContent() || ''; } async isHeaderSticky(): Promise { const isSticky = await this.header.evaluate(el => { return window.getComputedStyle(el).position === 'fixed'; }); return isSticky; } async getHeaderBackgroundColor(): Promise { return await this.header.evaluate(el => { return window.getComputedStyle(el).backgroundColor; }); } async isHeaderScrolled(): Promise { const hasShadow = await this.header.evaluate(el => { return window.getComputedStyle(el).boxShadow !== 'none'; }); return hasShadow; } async getAllNavigationLabels(): Promise { 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> { 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> { 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> { 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> { 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 { const scrollBehavior = await this.page.evaluate(() => { return window.getComputedStyle(document.documentElement).scrollBehavior; }); return scrollBehavior === 'smooth'; } async verifyStickyHeader(): Promise { await this.scrollToBottom(); const isSticky = await this.header.evaluate((el) => { return window.getComputedStyle(el).position === 'fixed'; }); return isSticky; } async verifyMobileMenu(): Promise { 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 { 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; } }