import { Page, Locator } from '@playwright/test'; /** * Base Page Object * * Contains common functionality shared across all page objects. * Extend this class for your specific page objects to avoid duplication. * * Usage: * ```typescript * export class LoginPage extends BasePage { * constructor(page: Page) { * super(page); * } * * async login() { * await this.fillInput('username', 'user@example.com'); * } * } * ``` */ export class BasePage { readonly page: Page; constructor(page: Page) { this.page = page; } /** * Navigate to a URL */ async goto(url: string): Promise { await this.page.goto(url); await this.page.waitForLoadState('domcontentloaded'); } /** * Get locator by data-testid */ getByTestId(testId: string): Locator { return this.page.locator(`[data-testid="${testId}"]`); } /** * Click element by data-testid */ async clickByTestId(testId: string): Promise { const locator = this.getByTestId(testId); await locator.waitFor({ state: 'visible' }); await locator.click(); } /** * Fill input by data-testid */ async fillInput(testId: string, value: string): Promise { const locator = this.getByTestId(testId); await locator.waitFor({ state: 'visible' }); await locator.fill(value); } /** * Get text content by data-testid */ async getTextByTestId(testId: string): Promise { const locator = this.getByTestId(testId); const text = await locator.textContent(); return text?.trim() || ''; } /** * Check if element is visible by data-testid */ async isVisibleByTestId(testId: string): Promise { try { await this.getByTestId(testId).waitFor({ state: 'visible', timeout: 2000, }); return true; } catch { return false; } } /** * Wait for element by data-testid */ async waitForElement(testId: string, timeout = 10000): Promise { const locator = this.getByTestId(testId); await locator.waitFor({ state: 'visible', timeout }); return locator; } /** * Wait for element to disappear */ async waitForElementToDisappear( testId: string, timeout = 10000 ): Promise { const locator = this.getByTestId(testId); await locator.waitFor({ state: 'hidden', timeout }); } /** * Fill a form with multiple fields */ async fillForm(fields: Record): Promise { for (const [testId, value] of Object.entries(fields)) { await this.fillInput(testId, value); } } /** * Click and wait for navigation */ async clickAndNavigate( testId: string, expectedUrl?: string | RegExp ): Promise { const locator = this.getByTestId(testId); await locator.waitFor({ state: 'visible' }); await Promise.all([ expectedUrl ? this.page.waitForURL(expectedUrl) : this.page.waitForLoadState('networkidle'), locator.click(), ]); } /** * Select option from dropdown */ async selectOption(testId: string, value: string): Promise { const locator = this.getByTestId(testId); await locator.waitFor({ state: 'visible' }); await locator.selectOption(value); } /** * Check/uncheck checkbox */ async toggleCheckbox(testId: string, checked: boolean): Promise { const locator = this.getByTestId(testId); await locator.waitFor({ state: 'visible' }); const isChecked = await locator.isChecked(); if (isChecked !== checked) { await locator.click(); } } /** * Upload file */ async uploadFile(testId: string, filePath: string): Promise { const locator = this.getByTestId(testId); await locator.waitFor({ state: 'attached' }); await locator.setInputFiles(filePath); } /** * Get all text contents from multiple elements */ async getAllTextsByTestId(testId: string): Promise { const locator = this.getByTestId(testId); await locator.first().waitFor({ state: 'visible' }); return await locator.allTextContents(); } /** * Get element count by data-testid */ async getCountByTestId(testId: string): Promise { return await this.getByTestId(testId).count(); } /** * Wait for API response */ async waitForApiResponse( urlPattern: string | RegExp, action: () => Promise ): Promise { const [response] = await Promise.all([ this.page.waitForResponse(urlPattern), action(), ]); return await response.json(); } /** * Get current URL */ getCurrentUrl(): string { return this.page.url(); } /** * Reload page */ async reload(): Promise { await this.page.reload(); await this.page.waitForLoadState('domcontentloaded'); } /** * Go back in browser history */ async goBack(): Promise { await this.page.goBack(); await this.page.waitForLoadState('domcontentloaded'); } /** * Go forward in browser history */ async goForward(): Promise { await this.page.goForward(); await this.page.waitForLoadState('domcontentloaded'); } /** * Take screenshot */ async takeScreenshot(name: string): Promise { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); await this.page.screenshot({ path: `${name}-${timestamp}.png`, fullPage: true, }); } } /** * Example usage: * * export class LoginPage extends BasePage { * readonly usernameInput: Locator; * readonly passwordInput: Locator; * readonly loginButton: Locator; * * constructor(page: Page) { * super(page); * this.usernameInput = this.getByTestId('username-input'); * this.passwordInput = this.getByTestId('password-input'); * this.loginButton = this.getByTestId('login-button'); * } * * async goto(): Promise { * await super.goto('/login'); * } * * async login(username: string, password: string): Promise { * await this.fillForm({ * 'username-input': username, * 'password-input': password, * }); * await this.clickAndNavigate('login-button', '/dashboard'); * } * } */