import { Page, Locator } from '@playwright/test'; import { BasePage } from './BasePage'; import { ContactFormData } from '../types'; export class ContactPage extends BasePage { readonly url: string; readonly pageHeader: Locator; readonly contactForm: Locator; readonly nameInput: Locator; readonly phoneInput: Locator; readonly emailInput: Locator; readonly subjectInput: Locator; readonly messageInput: Locator; readonly submitButton: Locator; readonly contactInfoCard: Locator; readonly workHoursCard: Locator; readonly emailInfo: Locator; readonly phoneInfo: Locator; readonly addressInfo: Locator; readonly emailLink: Locator; readonly phoneLink: Locator; readonly addressText: Locator; readonly pageBadge: Locator; readonly pageDescription: Locator; readonly successMessage: Locator; readonly nameError: Locator; readonly emailError: Locator; readonly phoneError: Locator; readonly messageError: Locator; constructor(page: Page) { super(page); this.url = '/contact'; this.pageHeader = page.locator('h1'); this.contactForm = page.locator('form'); this.nameInput = page.locator('[data-testid="name-input"]'); this.phoneInput = page.locator('[data-testid="phone-input"]'); this.emailInput = page.locator('[data-testid="email-input"]'); this.subjectInput = page.locator('[data-testid="subject-input"]'); this.messageInput = page.locator('[data-testid="message-input"]'); this.submitButton = page.locator('[data-testid="submit-button"]'); this.contactInfoCard = page.locator('[data-testid="contact-info"]'); this.workHoursCard = page.locator('[data-testid="work-hours-card"]'); this.emailInfo = page.locator('[data-testid="email-info"]'); this.phoneInfo = page.locator('[data-testid="phone-info"]'); this.addressInfo = page.locator('[data-testid="address-info"]'); this.emailLink = page.locator('[data-testid="email-link"]'); this.phoneLink = page.locator('[data-testid="phone-link"]'); this.addressText = page.locator('[data-testid="address-text"]'); this.pageBadge = page.locator('[data-testid="page-badge"]'); this.pageDescription = page.locator('[data-testid="page-description"]'); this.successMessage = page.locator('text=消息已发送'); this.nameError = page.locator('[data-testid="name-input"] + .error-message, [data-testid="name-input"] ~ .text-destructive').first(); this.emailError = page.locator('[data-testid="email-input"] + .error-message, [data-testid="email-input"] ~ .text-destructive').first(); this.phoneError = page.locator('[data-testid="phone-input"] + .error-message, [data-testid="phone-input"] ~ .text-destructive').first(); this.messageError = page.locator('[data-testid="message-input"] + .error-message, [data-testid="message-input"] ~ .text-destructive').first(); } get breadcrumb(): Locator { return this.page.locator('nav[aria-label="breadcrumb"]'); } async navigateToContact(): Promise { await this.navigate(this.url); } async verifyBreadcrumb(): Promise { return await this.breadcrumb.isVisible(); } async verifyPageHeader(): Promise { const header = await this.pageHeader.textContent(); return header?.includes('与我们取得联系') || false; } async verifyContactForm(): Promise { return await this.contactForm.isVisible(); } async verifyContactInfo(): Promise { return await this.contactInfoCard.isVisible(); } async goto(): Promise { await this.navigate(this.url); await this.waitForLoadState('networkidle'); } async isLoaded(): Promise { try { await this.pageHeader.waitFor({ state: 'visible', timeout: 5000 }); await this.contactForm.waitFor({ state: 'visible', timeout: 5000 }); return true; } catch { return false; } } async waitForPageLoad(): Promise { await this.waitForLoadState('networkidle'); await this.pageHeader.waitFor({ state: 'visible' }); await this.contactForm.waitFor({ state: 'visible' }); } async fillContactForm(data: ContactFormData): Promise { if (data.name) { await this.nameInput.fill(data.name); } if (data.phone) { await this.phoneInput.fill(data.phone); } if (data.email) { await this.emailInput.fill(data.email); } if (data.subject) { await this.subjectInput.fill(data.subject); } if (data.message) { await this.messageInput.fill(data.message); } } async submitForm(): Promise { await this.submitButton.click(); } async fillAndSubmitForm(data: ContactFormData): Promise { console.log('Filling form with data:', data); await this.fillContactForm(data); console.log('Form filled, clicking submit button'); await this.submitForm(); console.log('Submit button clicked'); } async isSuccessMessageVisible(): Promise { return await this.successMessage.isVisible(); } async getSuccessMessageText(): Promise { return await this.successMessage.textContent() || ''; } async isFormVisible(): Promise { return await this.contactForm.isVisible(); } async isSubmitButtonEnabled(): Promise { return await this.submitButton.isEnabled(); } async getSubmitButtonText(): Promise { return await this.submitButton.textContent() || ''; } async isSubmitButtonLoading(): Promise { const text = await this.getSubmitButtonText(); return text.includes('发送中'); } async getNameInputValue(): Promise { return await this.nameInput.inputValue(); } async getPhoneInputValue(): Promise { return await this.phoneInput.inputValue(); } async getEmailInputValue(): Promise { return await this.emailInput.inputValue(); } async getSubjectInputValue(): Promise { return await this.subjectInput.inputValue(); } async getMessageInputValue(): Promise { return await this.messageInput.inputValue(); } async clearForm(): Promise { await this.nameInput.fill(''); await this.phoneInput.fill(''); await this.emailInput.fill(''); await this.subjectInput.fill(''); await this.messageInput.fill(''); } async isContactInfoCardVisible(): Promise { return await this.contactInfoCard.isVisible(); } async isWorkHoursCardVisible(): Promise { return await this.workHoursCard.isVisible(); } async getContactInfoText(): Promise { return await this.contactInfoCard.textContent() || ''; } async getWorkHoursText(): Promise { return await this.workHoursCard.textContent() || ''; } async getAddress(): Promise { return await this.addressText.textContent() || ''; } async getPhone(): Promise { return await this.phoneLink.textContent() || ''; } async getEmail(): Promise { return await this.emailLink.textContent() || ''; } async getPageTitle(): Promise { return await this.pageHeader.textContent() || ''; } async getPageDescription(): Promise { return await this.pageDescription.textContent() || ''; } async getBadgeText(): Promise { return await this.pageBadge.textContent() || ''; } async isRequiredFieldVisible(fieldName: string): Promise { const label = this.page.locator(`label[for="${fieldName}"]`); return await label.isVisible(); } async isFieldRequired(fieldName: string): Promise { const label = this.page.locator(`label[for="${fieldName}"]`); const text = await label.textContent(); return text?.includes('*') || false; } async getFieldPlaceholder(fieldName: string): Promise { const input = this.page.locator(`[name="${fieldName}"]`); return await input.getAttribute('placeholder') || ''; } async scrollToForm(): Promise { await this.contactForm.scrollIntoViewIfNeeded(); await this.page.waitForTimeout(500); } async takeScreenshotOfForm(filename: string): Promise { await this.contactForm.screenshot({ path: `test-results/screenshots/${filename}` }); } async takeScreenshotOfSuccessMessage(filename: string): Promise { await this.successMessage.screenshot({ path: `test-results/screenshots/${filename}` }); } async waitForFormSubmission(): Promise { await this.page.waitForTimeout(3000); await this.page.waitForLoadState('networkidle'); await this.page.waitForTimeout(2000); } async isFormSubmitted(): Promise { const isSuccessVisible = await this.isSuccessMessageVisible(); console.log('Success message visible:', isSuccessVisible); return isSuccessVisible; } async getFormValidationErrors(): Promise { const errors: string[] = []; const requiredInputs = this.contactForm.locator('input[required], textarea[required]'); const count = await requiredInputs.count(); for (let i = 0; i < count; i++) { const input = requiredInputs.nth(i); const isValid = await input.evaluate(el => (el as HTMLInputElement).checkValidity()); if (!isValid) { const name = await input.getAttribute('name'); errors.push(`${name} is invalid`); } } return errors; } async isEmailValid(): Promise { return await this.emailInput.evaluate(el => (el as HTMLInputElement).checkValidity()); } async isPhoneValid(): Promise { return await this.phoneInput.evaluate(el => (el as HTMLInputElement).checkValidity()); } async focusOnField(fieldName: string): Promise { const input = this.page.locator(`[data-testid="${fieldName}-input"]`); await input.focus(); } async blurField(fieldName: string): Promise { const input = this.page.locator(`[data-testid="${fieldName}-input"]`); await input.blur(); } async typeInField(fieldName: string, text: string, options?: { delay?: number }): Promise { const input = this.page.locator(`[data-testid="${fieldName}-input"]`); await input.type(text, options); } async clearField(fieldName: string): Promise { const input = this.page.locator(`[data-testid="${fieldName}-input"]`); await input.fill(''); } async isFieldVisible(fieldName: string): Promise { const input = this.page.locator(`[data-testid="${fieldName}-input"]`); return await input.isVisible(); } async isFieldEnabled(fieldName: string): Promise { const input = this.page.locator(`[data-testid="${fieldName}-input"]`); return await input.isEnabled(); } async getFieldAttribute(fieldName: string, attribute: string): Promise { const input = this.page.locator(`[data-testid="${fieldName}-input"]`); return await input.getAttribute(attribute); } async getWorkHours(): Promise<{ day: string; hours: string }[]> { const workHours: { day: string; hours: string }[] = []; const rows = this.workHoursCard.locator('.space-y-2 > div'); const count = await rows.count(); for (let i = 0; i < count; i++) { const row = rows.nth(i); const day = await row.locator('span').first().textContent(); const hours = await row.locator('span').nth(1).textContent(); if (day && hours) { workHours.push({ day: day.trim(), hours: hours.trim() }); } } return workHours; } async getNameError(): Promise { return await this.nameError.textContent() || ''; } async getEmailError(): Promise { return await this.emailError.textContent() || ''; } async getPhoneError(): Promise { return await this.phoneError.textContent() || ''; } async getMessageError(): Promise { return await this.messageError.textContent() || ''; } async isNameErrorVisible(): Promise { return await this.nameError.isVisible(); } async isEmailErrorVisible(): Promise { return await this.emailError.isVisible(); } async isPhoneErrorVisible(): Promise { return await this.phoneError.isVisible(); } async isMessageErrorVisible(): Promise { return await this.messageError.isVisible(); } async testXSSInjection(payload: string): Promise { await this.fillContactForm({ name: payload, email: 'test@example.com', phone: '13800138000', message: payload, }); await this.submitForm(); } async testSQLInjection(payload: string): Promise { await this.fillContactForm({ name: payload, email: payload, phone: payload, message: payload, }); await this.submitForm(); } async testPathTraversal(payload: string): Promise { await this.fillContactForm({ name: payload, email: 'test@example.com', phone: '13800138000', message: payload, }); await this.submitForm(); } async verifyFormResponsiveLayout(viewport: { width: number; height: number }): Promise<{ isFormVisible: boolean; isSubmitButtonVisible: boolean; isContactInfoVisible: boolean; }> { await this.page.setViewportSize(viewport); await this.waitForTimeout(500); return { isFormVisible: await this.isFormVisible(), isSubmitButtonVisible: await this.isVisible(this.submitButton), isContactInfoVisible: await this.isContactInfoCardVisible(), }; } async measureFormSubmissionPerformance(): Promise<{ fillTime: number; submitTime: number; totalTime: number; }> { const startTime = Date.now(); const data = { name: '测试用户', email: 'test@example.com', phone: '13800138000', message: '这是一条测试消息', }; const fillStartTime = Date.now(); await this.fillContactForm(data); const fillTime = Date.now() - fillStartTime; const submitStartTime = Date.now(); await this.submitForm(); await this.waitForFormSubmission(); const submitTime = Date.now() - submitStartTime; const totalTime = Date.now() - startTime; return { fillTime, submitTime, totalTime, }; } async getFormAccessibilityAttributes(): Promise<{ nameAriaLabel: string | null; emailAriaLabel: string | null; phoneAriaLabel: string | null; messageAriaLabel: string | null; submitAriaLabel: string | null; }> { return { nameAriaLabel: await this.nameInput.getAttribute('aria-label'), emailAriaLabel: await this.emailInput.getAttribute('aria-label'), phoneAriaLabel: await this.phoneInput.getAttribute('aria-label'), messageAriaLabel: await this.messageInput.getAttribute('aria-label'), submitAriaLabel: await this.submitButton.getAttribute('aria-label'), }; } async verifyFormLabels(): Promise<{ nameLabel: string | null; emailLabel: string | null; phoneLabel: string | null; messageLabel: string | null; }> { return { nameLabel: await this.page.locator('label[for="name"]').textContent(), emailLabel: await this.page.locator('label[for="email"]').textContent(), phoneLabel: await this.page.locator('label[for="phone"]').textContent(), messageLabel: await this.page.locator('label[for="message"]').textContent(), }; } async getFormInputTypes(): Promise<{ nameType: string | null; emailType: string | null; phoneType: string | null; subjectType: string | null; }> { return { nameType: await this.nameInput.getAttribute('type'), emailType: await this.emailInput.getAttribute('type'), phoneType: await this.phoneInput.getAttribute('type'), subjectType: await this.subjectInput.getAttribute('type'), }; } async verifyRequiredFields(): Promise<{ nameRequired: boolean; emailRequired: boolean; phoneRequired: boolean; messageRequired: boolean; }> { return { nameRequired: await this.nameInput.getAttribute('required') !== null, emailRequired: await this.emailInput.getAttribute('required') !== null, phoneRequired: await this.phoneInput.getAttribute('required') !== null, messageRequired: await this.messageInput.getAttribute('required') !== null, }; } async getFormAutocompleteAttributes(): Promise<{ nameAutocomplete: string | null; emailAutocomplete: string | null; phoneAutocomplete: string | null; }> { return { nameAutocomplete: await this.nameInput.getAttribute('autocomplete'), emailAutocomplete: await this.emailInput.getAttribute('autocomplete'), phoneAutocomplete: await this.phoneInput.getAttribute('autocomplete'), }; } async verifyKeyboardNavigation(): Promise { await this.nameInput.focus(); await this.pressKey('Tab'); await this.pressKey('Tab'); await this.pressKey('Tab'); await this.pressKey('Tab'); await this.pressKey('Tab'); } async isFormKeyboardAccessible(): Promise { try { await this.verifyKeyboardNavigation(); return true; } catch { return false; } } }