Introduction
Following our recent blog post on migrating E2E tests from Cypress to Playwright, we've identified opportunities to enhance our test scripts further. In this guide, we'll delve into the basics of Playwright fixtures, demonstrating their utility and flexibility in test environments.
Playwright fixtures are reusable components that set up and tear down the environment or conditions necessary for tests. They are crucial for writing clean, maintainable, and scalable tests. Fixtures can handle tasks like opening a browser, initializing a database, or logging into an application—actions you might need before running your tests.
As a practical example, we'll revisit one of the tests from our previous post, enhancing it with a new fixture to streamline the testing process and significantly improve maintainability. This post is designed to provide the foundational skills to integrate fixtures into your testing workflows effectively, giving you the confidence to manage and maintain your test scripts more efficiently.
Creating Our First Fixture
To illustrate the power of fixtures in Playwright, let’s consider a practical example from a test scenario in our project. Below is a snippet of a test case from our newsletter page:
test.describe("Newsletter page", () => {
test("subscribing to email newsletter should show success message", async ({ page }) => {
await page.goto("/newsletter");
await page
.locator(`[data-testid="newsletter-email-input"]`)
.fill(SIGN_UP_EMAIL_TEST);
await page.locator(`[data-testid="newsletter-submit-button"]`).click();
await expect(
page.getByText(/You subscribed to the newsletter successfully!/).first(),
).toBeVisible();
});
});
This test scenario navigates to the newsletter page, fills in an email, submits the form, and checks for a success message. To optimize our test suite, we'll refactor common actions like navigation, form completion, and submission into reusable fixtures. This approach makes our tests cleaner and more maintainable and reduces redundancy across similar test scenarios.
Implementing the Fixture
Here’s how the fixture looks:
// NewsletterPage.fixture.ts
import type { Page, Locator } from "@playwright/test";
export class NewsletterPage {
private readonly emailInput: Locator;
private readonly submitButton: Locator;
constructor(public readonly page: Page) {
this.emailInput = this.page.locator(
`[data-testid="newsletter-email-input"]`,
);
this.submitButton = this.page.locator(
`[data-testid="newsletter-submit-button"]`,
);
}
async goto() {
await this.page.goto("/newsletter");
}
async addEmail(text: string) {
await this.emailInput.fill(text);
}
async submitNewsletter() {
await this.submitButton.click();
}
}
This fixture encapsulates the actions of navigating to the page, filling out the email field, and submitting the form. By abstracting these actions, we simplify and focus our test cases.
Refactoring the Test
With the fixture in place, let’s see how it changes our original test file:
import { NewsletterPage } from "playwright/fixtures/NewsletterPage.fixture";
test.describe("Newsletter page", () => {
let newsletterPage: NewsletterPage;
test.beforeEach(({ page }) => {
newsletterPage = new NewsletterPage(page);
});
test("subscribing to email newsletter should show success message", async ({ page }) => {
await newsletterPage.goto();
await newsletterPage.addEmail(SIGN_UP_EMAIL_TEST);
await newsletterPage.submitNewsletter();
await expect(
page.getByText(/You subscribed to the newsletter successfully!/).first(),
).toBeVisible();
});
});
A beforeEach
method to reset the state of the NewsletterPage
fixture ensures a clean and consistent environment for each test scenario. This practice is crucial for maintaining the integrity and reliability of your tests.
By leveraging the NewsletterPage
fixture, each test within the "Newsletter page" suite starts with a clean and pre-configured environment. This setup improves test clarity and efficiency and aligns with best practices for scalable test architecture.
Conclusion
As we've seen, fixtures are powerful tools that help standardize test environments, reduce code redundancy, and ensure that each test operates in a clean state. By abstracting common setup and teardown tasks into fixtures, we can focus our testing efforts on what matters most, verifying the behavior and reliability of the software we're developing.
Remember, the key to successful test management is choosing the right tools and using them wisely to create scalable, maintainable, and robust testing frameworks. Playwright fixtures offer a pathway towards achieving these goals, empowering teams to build better software faster and more confidently.