The Page Object Model is a design pattern that enhances test maintenance and reduces code duplication by segregating the UI elements and their interactions into separate classes. Each class represents a page in your application.
class LoginPage {
private page: Page;
private usernameInput: Locator;
private passwordInput: Locator;
private loginButton: Locator;
constructor(page: Page) {
this.page = page;
this.usernameInput = page.locator('#username');
this.passwordInput = page.locator('#password');
this.loginButton = page.locator('#loginButton');
}
async login(username: string, password: string): Promise<void> {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}
A good practice is to keep each test focused and limited to a single responsibility or assertion. This improves the readability and maintainability of your test cases and makes it easier to identify the cause of a failure.
test('should display error for invalid login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.login('invalidUser', 'invalidPass');
const errorMessage = await page.locator('.error-message').innerText();
expect(errorMessage).toBe('Invalid username or password.');
});
Using specific and stable locators helps in creating robust test scripts. Avoid using locators that are prone to changes like nth-child or overly complicated CSS selectors. Instead, prefer using IDs, data-test attributes, or other unique attributes.
await page.locator('#submitButton').click(); // Prefer using IDs
await page.locator('[data-test="username"]').fill('testUser'); // Use data-test attributes
Fixtures in Playwright allow you to set up the required state before your tests are executed, making your tests more reliable and faster. Reuse fixtures to avoid redundant code.
test.use({
storageState: 'loggedInState.json',
});
test('should display user dashboard', async ({ page }) => {
await page.goto('<https://example.com/dashboard>');
const welcomeMessage = await page.locator('.welcome-message').innerText();
expect(welcomeMessage).toBe('Welcome, testUser!');
});
Ensure that all asynchronous operations are properly handled using await to avoid flaky tests. Playwright provides various methods for waiting, such as waitForSelector, waitForLoadState, etc.
await page.waitForSelector('.loaded-element');
await page.click('#submitButton');
await page.waitForLoadState('networkidle');
Playwright comes with built-in test assertions that are optimized for end-to-end testing. Use these assertions for better performance and reliability.
await expect(page.locator('.success-message')).toHaveText('Operation successful');
await expect(page).toHaveURL('<https://example.com/dashboard>');