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

550 lines
17 KiB
TypeScript

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<void> {
await this.navigate(this.url);
}
async verifyBreadcrumb(): Promise<boolean> {
return await this.breadcrumb.isVisible();
}
async verifyPageHeader(): Promise<boolean> {
const header = await this.pageHeader.textContent();
return header?.includes('与我们取得联系') || false;
}
async verifyContactForm(): Promise<boolean> {
return await this.contactForm.isVisible();
}
async verifyContactInfo(): Promise<boolean> {
return await this.contactInfoCard.isVisible();
}
async goto(): Promise<void> {
await this.navigate(this.url);
await this.waitForLoadState('networkidle');
}
async isLoaded(): Promise<boolean> {
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<void> {
await this.waitForLoadState('networkidle');
await this.pageHeader.waitFor({ state: 'visible' });
await this.contactForm.waitFor({ state: 'visible' });
}
async fillContactForm(data: ContactFormData): Promise<void> {
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<void> {
await this.submitButton.click();
}
async fillAndSubmitForm(data: ContactFormData): Promise<void> {
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<boolean> {
return await this.successMessage.isVisible();
}
async getSuccessMessageText(): Promise<string> {
return await this.successMessage.textContent() || '';
}
async isFormVisible(): Promise<boolean> {
return await this.contactForm.isVisible();
}
async isSubmitButtonEnabled(): Promise<boolean> {
return await this.submitButton.isEnabled();
}
async getSubmitButtonText(): Promise<string> {
return await this.submitButton.textContent() || '';
}
async isSubmitButtonLoading(): Promise<boolean> {
const text = await this.getSubmitButtonText();
return text.includes('发送中');
}
async getNameInputValue(): Promise<string> {
return await this.nameInput.inputValue();
}
async getPhoneInputValue(): Promise<string> {
return await this.phoneInput.inputValue();
}
async getEmailInputValue(): Promise<string> {
return await this.emailInput.inputValue();
}
async getSubjectInputValue(): Promise<string> {
return await this.subjectInput.inputValue();
}
async getMessageInputValue(): Promise<string> {
return await this.messageInput.inputValue();
}
async clearForm(): Promise<void> {
await this.nameInput.fill('');
await this.phoneInput.fill('');
await this.emailInput.fill('');
await this.subjectInput.fill('');
await this.messageInput.fill('');
}
async isContactInfoCardVisible(): Promise<boolean> {
return await this.contactInfoCard.isVisible();
}
async isWorkHoursCardVisible(): Promise<boolean> {
return await this.workHoursCard.isVisible();
}
async getContactInfoText(): Promise<string> {
return await this.contactInfoCard.textContent() || '';
}
async getWorkHoursText(): Promise<string> {
return await this.workHoursCard.textContent() || '';
}
async getAddress(): Promise<string> {
return await this.addressText.textContent() || '';
}
async getPhone(): Promise<string> {
return await this.phoneLink.textContent() || '';
}
async getEmail(): Promise<string> {
return await this.emailLink.textContent() || '';
}
async getPageTitle(): Promise<string> {
return await this.pageHeader.textContent() || '';
}
async getPageDescription(): Promise<string> {
return await this.pageDescription.textContent() || '';
}
async getBadgeText(): Promise<string> {
return await this.pageBadge.textContent() || '';
}
async isRequiredFieldVisible(fieldName: string): Promise<boolean> {
const label = this.page.locator(`label[for="${fieldName}"]`);
return await label.isVisible();
}
async isFieldRequired(fieldName: string): Promise<boolean> {
const label = this.page.locator(`label[for="${fieldName}"]`);
const text = await label.textContent();
return text?.includes('*') || false;
}
async getFieldPlaceholder(fieldName: string): Promise<string> {
const input = this.page.locator(`[name="${fieldName}"]`);
return await input.getAttribute('placeholder') || '';
}
async scrollToForm(): Promise<void> {
await this.contactForm.scrollIntoViewIfNeeded();
await this.page.waitForTimeout(500);
}
async takeScreenshotOfForm(filename: string): Promise<void> {
await this.contactForm.screenshot({ path: `test-results/screenshots/${filename}` });
}
async takeScreenshotOfSuccessMessage(filename: string): Promise<void> {
await this.successMessage.screenshot({ path: `test-results/screenshots/${filename}` });
}
async waitForFormSubmission(): Promise<void> {
await this.page.waitForTimeout(3000);
await this.page.waitForLoadState('networkidle');
await this.page.waitForTimeout(2000);
}
async isFormSubmitted(): Promise<boolean> {
const isSuccessVisible = await this.isSuccessMessageVisible();
console.log('Success message visible:', isSuccessVisible);
return isSuccessVisible;
}
async getFormValidationErrors(): Promise<string[]> {
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<boolean> {
return await this.emailInput.evaluate(el => (el as HTMLInputElement).checkValidity());
}
async isPhoneValid(): Promise<boolean> {
return await this.phoneInput.evaluate(el => (el as HTMLInputElement).checkValidity());
}
async focusOnField(fieldName: string): Promise<void> {
const input = this.page.locator(`[data-testid="${fieldName}-input"]`);
await input.focus();
}
async blurField(fieldName: string): Promise<void> {
const input = this.page.locator(`[data-testid="${fieldName}-input"]`);
await input.blur();
}
async typeInField(fieldName: string, text: string, options?: { delay?: number }): Promise<void> {
const input = this.page.locator(`[data-testid="${fieldName}-input"]`);
await input.type(text, options);
}
async clearField(fieldName: string): Promise<void> {
const input = this.page.locator(`[data-testid="${fieldName}-input"]`);
await input.fill('');
}
async isFieldVisible(fieldName: string): Promise<boolean> {
const input = this.page.locator(`[data-testid="${fieldName}-input"]`);
return await input.isVisible();
}
async isFieldEnabled(fieldName: string): Promise<boolean> {
const input = this.page.locator(`[data-testid="${fieldName}-input"]`);
return await input.isEnabled();
}
async getFieldAttribute(fieldName: string, attribute: string): Promise<string | null> {
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<string> {
return await this.nameError.textContent() || '';
}
async getEmailError(): Promise<string> {
return await this.emailError.textContent() || '';
}
async getPhoneError(): Promise<string> {
return await this.phoneError.textContent() || '';
}
async getMessageError(): Promise<string> {
return await this.messageError.textContent() || '';
}
async isNameErrorVisible(): Promise<boolean> {
return await this.nameError.isVisible();
}
async isEmailErrorVisible(): Promise<boolean> {
return await this.emailError.isVisible();
}
async isPhoneErrorVisible(): Promise<boolean> {
return await this.phoneError.isVisible();
}
async isMessageErrorVisible(): Promise<boolean> {
return await this.messageError.isVisible();
}
async testXSSInjection(payload: string): Promise<void> {
await this.fillContactForm({
name: payload,
email: 'test@example.com',
phone: '13800138000',
message: payload,
});
await this.submitForm();
}
async testSQLInjection(payload: string): Promise<void> {
await this.fillContactForm({
name: payload,
email: payload,
phone: payload,
message: payload,
});
await this.submitForm();
}
async testPathTraversal(payload: string): Promise<void> {
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<void> {
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<boolean> {
try {
await this.verifyKeyboardNavigation();
return true;
} catch {
return false;
}
}
}