Rayrun

Tips for Writing Efficient Playwright Test Scripts

Maximize the efficiency of your Playwright test scripts with these practical tips and best practices. This comprehensive guide will help you navigate through the complexities of writing test scripts, making your tests more maintainable, reliable, and efficient. Let's dive into these valuable insights and elevate your Playwright testing game.
  1. Block Unnecessary Requests
    1. Reuse Test Code with Functions
      1. Leverage APIs to Shortcut Lengthy Workflows
        1. Leverage Headless Mode for Faster Execution
          1. Write tests as if they will be running in parallel
            1. Maintain Test Independence
              1. Isolate Test Data
                1. Use Unique Identifiers
                  1. Handle Network Throttling and Rate Limiting
                  2. Prioritize User-Facing Attributes
                    1. Use Locators with Auto-Waiting
                      1. Chain and Filter Locators
                        1. Avoid DOM-Dependent Locators
                        2. Leverage Playwright's Test Generator
                          1. Use Web-First Assertions
                            1. Keep Playwright Up to Date
                              1. Integrate Playwright Tests with CI/CD
                                1. Utilize Playwright's Tooling

                                  Block Unnecessary Requests

                                  In many cases, your tests don't require all the network requests a real user might make. Blocking unnecessary requests like third-party analytics can significantly speed up your tests. Here's how you can do it:

                                  await page.route('**/analytics/**', route => route.abort());

                                  Reuse Test Code with Functions

                                  Instead of repeating similar code in multiple tests, you can define reusable functions and call them in your test scripts. This not only makes your tests cleaner and more organized but also easier to maintain.

                                  async function login(page) {
                                    await page.goto('https://ray.run/login');
                                    await page.getByLabel('Username or email address').fill('username');
                                    await page.getByLabel('Password').fill('password');
                                    await page.getByRole('button', { name: 'Sign in' }).click();
                                  }
                                  
                                  test('first', async ({ page }) => {
                                    await login(page);
                                    // Your test code here
                                  });
                                  
                                  test('second', async ({ page }) => {
                                    await login(page);
                                    // Your test code here
                                  });

                                  Leverage APIs to Shortcut Lengthy Workflows

                                  In end-to-end testing, it's not uncommon to come across lengthy workflows that involve a lot of steps, such as user registration or login. Instead of simulating these workflows through the UI, which can be slow and increase the chances of flaky tests, consider leveraging your application's APIs to shortcut these processes.

                                  By interacting directly with your API, you can setup preconditions and clean up data more efficiently. For example, instead of going through the entire login process via the UI, you can make an API call to authenticate a user and start the test with an authenticated session.

                                  async function authenticateUser() {
                                    const response = await fetch('https://ray.ryn/api/auth/login', {
                                      method: 'POST',
                                      headers: { 'Content-Type': 'application/json' },
                                      body: JSON.stringify({ username: 'test', password: 'test' }),
                                    });
                                  
                                    const { sessionToken } = await response.json();
                                    
                                    return sessionToken;
                                  }
                                  
                                  test('authenticated test', async ({ context }) => {
                                    const sessionToken = await authenticateUser();
                                    
                                    // Store the session token as a cookie or in local storage.
                                    await context.addCookies([
                                      { name: 'sessionToken', value: sessionToken, domain: 'ray.ryn', path: '/', httpOnly: true },
                                    ]);
                                    
                                    // Now you can visit a page as an authenticated user.
                                    await page.goto('https://ray.ryn/dashboard');
                                  });

                                  Keep in mind that while this strategy can speed up your tests, it doesn't replace the need to test these workflows through the UI. You should still write end-to-end tests that go through the login and registration process as a real user would.

                                  Leverage Headless Mode for Faster Execution

                                  In headless mode, the browser runs in the background without displaying a GUI. This often leads to faster execution times, making your testing more efficient. While you might want to occasionally run your tests with the browser UI for debugging purposes, consider using headless mode for regular testing.

                                  const browser = await playwright.chromium.launch({ headless: true });

                                  Write tests as if they will be running in parallel

                                  While it's not always practical or necessary to run every test in parallel, it's a good practice to write your tests as if they will be. Doing so will help you avoid common pitfalls that can make your tests flaky and hard to maintain.

                                  Here are some tips for writing tests with parallel execution in mind:

                                  Maintain Test Independence

                                  Each test should be completely independent from one another. This means a test should not rely on the result or side effect of another test. By writing tests this way, you can run them in any order or in parallel without worrying about unexpected dependencies.

                                  Isolate Test Data

                                  When writing tests that interact with data, it's crucial to isolate the data used by each test. Avoid using shared or global data that could be modified by multiple tests running concurrently. Consider using tools or techniques that provide unique, isolated test data for each test. For example, if you're testing a database-driven application, you might want to use a separate database or schema for each test or test run.

                                  Use Unique Identifiers

                                  When creating resources (like users or records in a database) as part of your tests, use unique identifiers. This ensures that even if tests are running concurrently, they won't conflict with each other.

                                  Handle Network Throttling and Rate Limiting

                                  If your tests involve making requests to third-party APIs, be aware of rate limiting and network throttling. Running many tests in parallel could quickly exhaust API rate limits, causing your tests to fail.

                                  Prioritize User-Facing Attributes

                                  Use Locators with Auto-Waiting

                                  When writing end-to-end tests, you need to find elements on the web page. Playwright's built-in locators come with auto-waiting and retry-ability, ensuring the element is visible and enabled before performing actions. To make tests resilient, prioritize user-facing attributes and explicit contracts.

                                  // Good practice
                                  await page.getByRole('button', { name: 'submit' });

                                  Chain and Filter Locators

                                  You can chain locators to narrow down the search to a specific part of the page. Additionally, you can filter locators by text or another locator.

                                  const product = page.getByRole('listitem').filter({ hasText: 'Product 2' });
                                  
                                  await page
                                    .getByRole('listitem')
                                    .filter({ hasText: 'Product 2' })
                                    .getByRole('button', { name: 'Add to cart' })
                                    .click();

                                  Avoid DOM-Dependent Locators

                                  Your DOM can easily change, and having your tests depend on the DOM structure can lead to failing tests. Avoid selecting elements by their CSS classes or other attributes that are likely to change during design updates. Instead, use locators that are resilient to changes in the DOM.

                                  // Bad practice
                                  await page.locator('button.buttonIcon.episode-actions-later');
                                  
                                  // Good practice
                                  await page.getByRole('button', { name: 'submit' });

                                  Leverage Playwright's Test Generator

                                  Playwright has a test generator that can generate tests and pick locators for you. It prioritizes role, text, and test ID locators and automatically improves the locator to uniquely identify target elements.

                                  To pick a locator, run the codegen command followed by the URL you want to pick a locator from:

                                  npx playwright codegen ray.run

                                  You can also use the VS Code Extension to generate locators and record a test, further improving your developer experience.

                                  Use Web-First Assertions

                                  Playwright provides web-first assertions that wait until the expected condition is met. This eliminates the need for manual waiting and makes your tests more reliable.

                                  // Good practice
                                  await expect(page.getByText('welcome')).toBeVisible();
                                  
                                  // Bad practice
                                  expect(await page.getByText('welcome').isVisible()).toBe(true);

                                  Keep Playwright Up to Date

                                  Keeping your Playwright version up to date allows you to test your app on the latest browser versions and catch failures before they affect users. To update Playwright, run:

                                  npm install -D @playwright/test

                                  Check the release notes for the latest changes and updates.

                                  Integrate Playwright Tests with CI/CD

                                  Set up CI/CD and run your tests frequently. Ideally, you should run your tests on each commit and pull request. Playwright comes with a GitHub Actions workflow that runs tests on CI for you with no setup required. You can also set up Playwright on the CI environment of your choice.

                                  Utilize Playwright's Tooling

                                  Playwright offers a range of tooling to help you write tests:

                                  • The VS Code extension improves your developer experience when writing, running, and debugging tests.
                                  • The test generator generates tests and picks locators for you.
                                  • The trace viewer provides a full trace of your tests as a local PWA that can easily be shared.
                                  • TypeScript support works out of the box and improves IDE integrations.

                                  By implementing these tips and best practices, you'll be on your way to writing more efficient and maintainable Playwright test scripts. Remember, testing is an essential part of any development process, and with the right tools and techniques, you can streamline your testing workflow and ensure that your web applications are reliable and bug-free. Happy testing!

                                  Thank you!
                                  Was this helpful?

                                  Now check your email!

                                  To complete the signup, click the confirmation link in your inbox.

                                  Subscribe to newsletter

                                  You will receive a weekly email with the latest posts and QA jobs.

                                  TwitterGitHubLinkedIn
                                  AboutQuestionsDiscord ForumBrowser ExtensionTagsQA Jobs

                                  Rayrun is a community for QA engineers. I am constantly looking for new ways to add value to people learning Playwright and other browser automation frameworks. If you have feedback, email luc@ray.run.