avatarNadun Indunil

Summary

Playwright fixtures are essential for streamlining test development by providing reusable setup and teardown logic, ensuring consistency, and enhancing the maintainability of test suites.

Abstract

Fixtures in Playwright serve as a mechanism to create consistent and repeatable testing environments, promoting code reusability and maintainability. They encapsulate common setup and teardown operations, which eliminates redundancy and ensures tests start from a known state. By using fixtures, developers can reduce code duplication, as demonstrated in the provided examples, where fixtures are employed to handle authentication, data setup and teardown, environment configuration, navigation, error handling, and mocking external dependencies. This approach to testing not only simplifies the process but also makes it more robust and reliable, allowing for seamless testing across different environments and configurations.

Opinions

  • Fixtures are highly recommended for their ability to maintain code readability and scalability in test suites.
  • The use of fixtures is advocated for their contribution to creating repeatable and consistent test conditions.
  • Centralizing setup and teardown within fixtures is seen as best practice for simplifying test maintenance.
  • Naming fixtures descriptively is emphasized as important for clarity and readability of test code.
  • Regular maintenance of fixtures is considered crucial to keep them aligned with the evolving application under test.
  • Fixtures are praised for their versatility in handling various testing scenarios, including authentication, data management, and error handling.

Understanding Fixtures in Playwright and Why You Should Use Them

Photo by Ryan Quintal on Unsplash

Playwright is an amazing framework that makes testing applications a breeze for testers and developers. In this article, we’ll dive into the world of Playwright fixtures — these handy features not only make testing easier but also offer reasons why you should jump on board and start using them in your testing adventures.

What are Fixtures in Playwright?

Fixtures in tests are not a new thing introduced by Playwright.

In the context of software a test fixture (also called “test context”) is used to set up system state and input data needed for test execution. For example, the Ruby on Rails web framework uses YAML to initialize a database with known parameters before running a test. This allows for tests to be repeatable, which is one of the key features of an effective test framework.

Fixtures in Playwright serve as reusable setups for tests, providing a consistent environment for performing actions against web applications. They encapsulate shared setup and teardown logic, ensuring a uniform starting point for tests. Essentially, fixtures help maintain code readability, reduce redundancy, and enhance the scalability of test suites.

Why Use Fixtures?

  1. Code Reusability: Fixtures enable the creation of reusable code blocks, promoting modular test development and minimizing duplication of setup steps across multiple tests.
  2. Consistency: With fixtures, tests begin in a consistent state, eliminating variations caused by disparate setups and ensuring reliable test outcomes.
  3. Maintainability: Centralizing setup and teardown operations within fixtures simplifies maintenance, making it easier to update or modify shared functionalities.

This is an example code when a developer is not utilizing fixtures in playwright,

import { test, expect } from '@playwright/test';
import { LoginPage } from 'tests/pages/login-page';
import { LandingPage } from 'tests/pages/landing-page';

test('Test 1: Login and Perform Action on Landing Page', async ({ page, baseURL, request }) => {
  // Setup - Login Page
  const loginPage = new LoginPage(page, baseURL, request);
  await loginPage.removeAll();

  // Test Actions
  await loginPage.navigateToLoginPage();
  await loginPage.login('username', 'password');

  // Setup - Landing Page
  const landingPage = new LandingPage(page);

  // Perform actions on Landing Page after successful login
  await landingPage.doSomething();

  // Assertions
  await expect(landingPage.getSomething()).toBeTruthy();

  // Teardown - Logout or Reset State
  // Example: await loginPage.logout();
});

test('Test 2: Another Login and Similar Action on Landing Page', async ({ page, baseURL, request }) => {
  // Setup - Login Page (Duplicated setup logic)
  const loginPage = new LoginPage(page, baseURL, request);
  await loginPage.removeAll();

  // Test Actions (Similar actions as Test 1)
  await loginPage.navigateToLoginPage();
  await loginPage.login('differentUsername', 'differentPassword');

  // Setup - Landing Page (Duplicated setup logic)
  const landingPage = new LandingPage(page);

  // Perform actions on Landing Page after successful login
  await landingPage.doSomething();

  // Assertions (Similar assertions as Test 1)
  await expect(landingPage.getSomething()).toBeTruthy();

  // Teardown - Logout or Reset State (Duplicated teardown logic)
  // Example: await loginPage.logout();
});

Below is an example of how we can reduce the code duplication using Playwright fixtures.

How to Use Fixtures in Playwright:

  1. Define fixtures using the test.extend method, specifying setup, and teardown logic.
// fixture.js
import { test as base } from '@playwright/test';

import { LoginPage } from 'tests/pages/login-page';
import { LandingPage } from 'tests/pages/landing-page';

type TestFixtures = {
 loginPage: LoginPage;
 landingPage: LandingPage;
};

export const test = base.extend < TestFixtures >( {
 landingPage: async ( { page }, use ) => {
  const landingPage = new LandingPage( page );

  await use( landingPage );
 },
 loginPage: async ( { page, baseURL, request }, use ) => {
  const loginPage = new LoginPage( page, baseURL, request );

  await use( loginPage );
  await loginPage.removeAll();
 },
} );

2. Incorporate fixtures into tests by referencing them in the test definition.

// sampletest.spec.js
import { test } from './fixture'; // using the test extended previously.
import { expect } from '@playwright/test';

test('Test 1: Login and Perform Action on Landing Page', async ({ loginPage, landingPage }) => {
  // Test Actions
  await loginPage.login('username', 'password');

  // Perform actions on Landing Page after successful login
  await landingPage.doSomething();

  // Assertions
  await expect(landingPage.getSomething()).toBeTruthy();
});

test('Test 2: Another Login and Similar Action on Landing Page', async ({ loginPage, landingPage }) => {
  // Test Actions (Similar actions as Test 1)
  await loginPage.login('differentUsername', 'differentPassword');

  // Perform actions on Landing Page after successful login
  await landingPage.doSomething();

  // Assertions (Similar assertions as Test 1)
  await expect(landingPage.getSomething()).toBeTruthy();
});

Real-world use cases of Fixtures:

1. Authentication Fixtures:

Many web applications require authentication to access certain functionalities. A fixture can be created to handle login/logout actions, ensuring that tests requiring authenticated access can reuse this fixture. This simplifies the process for testing functionalities available only after login.

2. Data Setup and Teardown:

Testing scenarios that involve creating, modifying, or deleting data (e.g., user profiles, products) often require a specific initial state. Fixtures can be used to set up this data before tests and clean it up afterward, ensuring that each test starts with a consistent data state and doesn’t leave behind unwanted artifacts.

3. Environment Configuration:

Tests might need different environments (e.g., staging, production) or configurations (e.g., different browsers, devices) to be validated thoroughly. Fixtures can be created to set up these varied environments, allowing tests to run seamlessly across different configurations without duplicating setup logic.

4. Navigation and Page Setup:

Some tests require navigating to specific pages or performing certain actions before actual testing begins. Fixtures can handle these navigation tasks, ensuring tests start from a known state or perform certain initial interactions (e.g., filling out a form, clicking through a series of pages).

5. Error Handling and Recovery:

Tests often encounter unexpected errors or exceptions. Fixtures can be utilized to set up error-handling mechanisms or recovery actions, such as resetting the state or refreshing the page, enabling tests to continue or gracefully handle failures.

6. Mocking External Dependencies:

When testing functionalities interacting with external services or APIs, fixtures can facilitate the setup of mocks or stubs for these dependencies. This ensures that tests are not affected by fluctuations or unavailability of external services, allowing for controlled and reliable testing.

Best Practices for Fixtures:

  • Keep Fixtures Atomic: Maintain fixtures’ independence from one another to ensure they can be used interchangeably across tests.
  • Reuse Fixtures Wisely: Identify common setup/teardown logic to encapsulate within fixtures, enhancing test suite maintainability.
  • Strive for Clarity: Name fixtures descriptively, highlighting their purpose and making test code more readable.
  • Regular Maintenance: Update fixtures as the application evolves to align with changes in the tested application’s behavior.

In conclusion, fixtures in Playwright play a crucial role in structuring tests, promoting reusability, and ensuring consistency across test suites. By leveraging fixtures effectively, developers and QA engineers can streamline the testing process, enhance code maintainability, and create more robust automation scripts.

References

  1. https://playwright.dev/
  2. https://stackoverflow.com/questions/12071344/what-are-fixtures-in-programming
  3. https://code-craftsmanship-saturdays.gitbook.io/software-testing-fundamentals/test-fixtures
  4. https://en.wikipedia.org/wiki/Test_fixture
Microsoft
Software Test Automation
Fixtures
Testfixtures
Playwrights
Recommended from ReadMedium