Ultimate Playwright Handbook for Beginners

Ultimate Playwright Handbook for Beginners

Brief On Playwright

Key Features:

  • Cross-Browser Compatibility: Playwright offers a consistent API for controlling web browsers, making it easier to write tests that work across different platforms and browsers.

  • Headless and Headful Testing: You can run tests in headless mode (without a visible browser window) for faster execution or in headful mode for visual inspection.

  • Page Object Model (POM): Playwright supports the POM pattern, which helps you organize your tests and make them more maintainable.

  • Assertions: Playwright provides built-in assertions for verifying the state of web pages, making it easy to write robust tests.

  • Network Interception: You can intercept network requests and responses to simulate network conditions or modify data.

  • Mobile Emulation: Playwright allows you to emulate mobile devices and their specific characteristics, making it suitable for mobile testing.

  • Accessibility Testing: You can use Playwright to test the accessibility of your web applications, ensuring that they are usable by people with disabilities.

Use Cases:

  • End-to-End Testing: Playwright is well-suited for testing web applications from start to finish, simulating user interactions and verifying the correctness of the application's behavior.

  • Integration Testing: You can use Playwright to test the integration between different components of your web application.

  • API Testing: Playwright can be used to test APIs by making HTTP requests and verifying the responses.

  • Visual Regression Testing: Playwright can help you identify visual changes in your web application, ensuring that the user interface remains consistent.

Setup Playwright

Playwright can be setup in 4 languages

  • Javascript (Node.js runtime) | Playwright Documentation

    • Setup environment

        npm install -D playwright ## yarn create playwright
      
    • Setup First Test

        import { test, expect } from '@playwright/test';
      
        test('has title', async ({ page }) => {
          await page.goto('https://playwright.dev/');
      
          // Expect a title "to contain" a substring.
          await expect(page).toHaveTitle(/Playwright/);
        });
      
        test('get started link', async ({ page }) => {
          await page.goto('https://playwright.dev/');
      
          // Click the get started link.
          await page.getByRole('link', { name: 'Get started' }).click();
      
          // Expects page to have a heading with the name of Installation.
          await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
        });
      
  • Python | Playwright Documentation

    • Setup environment

        pip install pytest-playwright
      
    • Setup First Test

    •         import re
              from playwright.sync_api import Page, expect
      
              def test_has_title(page: Page):
                  page.goto("https://playwright.dev/")
      
                  # Expect a title "to contain" a substring.
                  expect(page).to_have_title(re.compile("Playwright"))
      
              def test_get_started_link(page: Page):
                  page.goto("https://playwright.dev/")
      
                  # Click the get started link.
                  page.get_by_role("link", name="Get started").click()
      
                  # Expects page to have a heading with the name of Installation.
                  expect(page.get_by_role("heading", name="Installation")).to_be_visible()
      
  • Java | Playwright Documentation

    • Setup environment

        package org.example;
      
        import com.microsoft.playwright.*;
      
        public class App {
            public static void main(String[] args) {
                try (Playwright playwright = Playwright.create()) {
                    Browser browser = playwright.chromium().launch();
                    Page page = browser.newPage();
                    page.navigate("http://playwright.dev");
                    System.out.println(page.title());
                }
            }
        }
      
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
          <groupId>org.example</groupId>
          <artifactId>examples</artifactId>
          <version>0.1-SNAPSHOT</version>
          <name>Playwright Client Examples</name>
          <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          </properties>
          <dependencies>
            <dependency>
              <groupId>com.microsoft.playwright</groupId>
              <artifactId>playwright</artifactId>
              <version>1.46.0</version>
            </dependency>
          </dependencies>
          <build>
            <plugins>
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <!-- References to interface static methods are allowed only at source level 1.8 or above -->
                <configuration>
                  <source>1.8</source>
                  <target>1.8</target>
                </configuration>
              </plugin>
            </plugins>
          </build>
        </project>
      
    • Setup First Test

        package org.example;
      
        import java.util.regex.Pattern;
        import com.microsoft.playwright.*;
        import com.microsoft.playwright.options.AriaRole;
      
        import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
      
        public class App {
            public static void main(String[] args) {
                try (Playwright playwright = Playwright.create()) {
                    Browser browser = playwright.chromium().launch();
                    Page page = browser.newPage();
                    page.navigate("http://playwright.dev");
      
                    // Expect a title "to contain" a substring.
                    assertThat(page).hasTitle(Pattern.compile("Playwright"));
      
                    // create a locator
                    Locator getStarted = page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Get Started"));
      
                    // Expect an attribute "to be strictly equal" to the value.
                    assertThat(getStarted).hasAttribute("href", "/docs/intro");
      
                    // Click the get started link.
                    getStarted.click();
      
                    // Expects page to have a heading with the name of Installation.
                    assertThat(page.getByRole(AriaRole.HEADING,
                       new Page.GetByRoleOptions().setName("Installation"))).isVisible();
                }
            }
        }
      
  • C# (.Net Runtime) Playwright Documentation

    • Setup environment with NUnit

        dotnet new nunit -n PlaywrightTests
        cd PlaywrightTests
      
      • Install Playwright
        dotnet add package Microsoft.Playwright.NUnit
        dotnet build ## Build project
  • Install browsers

      pwsh bin/Debug/net8.0/playwright.ps1 install
    
  • Setup First Test

  •         using System.Text.RegularExpressions;
            using System.Threading.Tasks;
            using Microsoft.Playwright;
            using Microsoft.Playwright.NUnit;
            using NUnit.Framework;
    
            namespace PlaywrightTests;
    
            [Parallelizable(ParallelScope.Self)]
            [TestFixture]
            public class ExampleTest : PageTest
            {
                [Test]
                public async Task HasTitle()
                {
                    await Page.GotoAsync("https://playwright.dev");
    
                    // Expect a title "to contain" a substring.
                    await Expect(Page).ToHaveTitleAsync(new Regex("Playwright"));
                }
    
                [Test]
                public async Task GetStartedLink()
                {
                    await Page.GotoAsync("https://playwright.dev");
    
                    // Click the get started link.
                    await Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }).ClickAsync();
    
                    // Expects page to have a heading with the name of Installation.
                    await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Installation" })).ToBeVisibleAsync();
                } 
            }
    

Core Concepts and Features

For the context of understand these concepts we will be using JavaScript

  1. Browsers and Contexts
  • Launching different browsers (Chromium, Firefox, WebKit)

    • You can define the browsers you want to run tests on in your playwright.config.js

        import { defineConfig, devices } from '@playwright/test';
      
        export default defineConfig({
          projects: [
            /* Test against desktop browsers */
            {
              name: 'chromium',
              use: { ...devices['Desktop Chrome'] },
            },
            {
              name: 'firefox',
              use: { ...devices['Desktop Firefox'] },
            },
            {
              name: 'webkit',
              use: { ...devices['Desktop Safari'] },
            },
            /* Test against mobile viewports. */
            {
              name: 'Mobile Chrome',
              use: { ...devices['Pixel 5'] },
            },
            {
              name: 'Mobile Safari',
              use: { ...devices['iPhone 12'] },
            },
            /* Test against branded browsers. */
            {
              name: 'Google Chrome',
              use: { ...devices['Desktop Chrome'], channel: 'chrome' }, // or 'chrome-beta'
            },
            {
              name: 'Microsoft Edge',
              use: { ...devices['Desktop Edge'], channel: 'msedge' }, // or 'msedge-dev'
            },
          ],
        });
      
    • Output:

        npx playwright test
      
        Running 7 tests using 5 workers
      
          ✓ [chromium] › example.spec.ts:3:1 › basic test (2s)
          ✓ [firefox] › example.spec.ts:3:1 › basic test (2s)
          ✓ [webkit] › example.spec.ts:3:1 › basic test (2s)
          ✓ [Mobile Chrome] › example.spec.ts:3:1 › basic test (2s)
          ✓ [Mobile Safari] › example.spec.ts:3:1 › basic test (2s)
          ✓ [Google Chrome] › example.spec.ts:3:1 › basic test (2s)
          ✓ [Microsoft Edge] › example.spec.ts:3:1 › basic test (2s)
      
  1. Pages and Selectors | Recorder
  • Interacting with pages

    •         // Create a page.
              const page = await context.newPage();
      
              // Registering a page
              await page.goto('http://devassure.io');
      
              // Fill an input.
              await page.locator('#search').fill('query');
      
              // Navigate implicitly by clicking a link.
              await page.locator('#submit').click();
              // Expect a new url.
              console.log(page.url());
      
    • Multiple Pages

    •         // Create two pages
              const pageOne = await context.newPage();
              await pageOne.goto("https://devassure.io/features")
              const pageTwo = await context.newPage();
              await pageTwo.goto("https://devassure.io/blogs")
      
              // Get pages of a browser context
              const allPages = context.pages();
      
  • Using CSS selectors and XPath

    %[vimeo.com/1007992216?share=copy]

  • CodeGen Recorde: Record your automation test cases and run them

  • Interactions

    • Clicking, typing, and other `interactions

      • Check input checkbox: locator.check()

      • Click element checkbox: locator.click()

      • Press single key while selecting the element: locator.press()

      • Fill text in the input area: locator.fill()

      • Hover mouse over the element: locator.hover()

    • Handling user inputs and form submissions

      • Upload files: locator.setInputFiles()

      • Select options from in Drop Down: locator.setInputFiles()

  • Assertions

    1. Verifying conditions and expected results

      • Playwright uses expect function to make an assertion call. It also includes generics matchers like: toEqual, toContain, toBeTruthy

      • Example: expect(success).toBeTruthy();

    2. Common assertions

      • Checkbox is checked: expect(locator).toBeChecked()

      • Checkbox is checked: expect(locator).toBeVisible()

      • Checkbox is checked: expect(locator).toHaveCount()

      • Checkbox is checked: expect(locator).toHaveText()

      • Checkbox is checked: expect(page).toHaveTitle()

      • Element is in view port: await expect(locator).toBeInViewPort()

      • Page to have a title: await expect(page).toHaveTitle()

  • Screenshots and Videos

    1. Capturing screenshots of pages

      1. await page.screenshot({ path: 'screenshot.png' }); #quick screenshot

      2. await page.screenshot({ path: 'screenshot.png', fullPage: true }); ##full page screenshot

    2. Recording videos of tests

      • Playwright helps you to record your test cases, you can make changes in your playwright.config.ts file

      • options available: ’off’ , ‘on‘, ‘retain-on-failure‘

      •     import { defineConfig } from '@playwright/test';
            export default defineConfig({
              use: {
                video: {
                  mode: 'on-first-retry',
                  size: { width: 640, height: 480 }
                }
              },
            });
        
  • Handling Authentication

    1. Managing user authentication

      1. Make a file in test/auth.setup.ts

         import { test as setup, expect } from '@playwright/test';
         import path from 'path';
        
         const authFile = path.join(__dirname, '../playwright/.auth/user.json');
        
         setup('authenticate', async ({ page }) => {
           // Perform authentication steps. Replace these actions with your own.
           await page.goto('https://github.com/login');
           await page.getByLabel('Username or email address').fill('username');
           await page.getByLabel('Password').fill('password');
           await page.getByRole('button', { name: 'Sign in' }).click();
           // Wait until the page receives the cookies.
           //
           // Sometimes login flow sets cookies in the process of several redirects.
           // Wait for the final URL to ensure that the cookies are actually set.
           await page.waitForURL('https://github.com/');
           // Alternatively, you can wait until the page reaches a state where all cookies are set.
           await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
        
           // End of authentication steps.
        
           await page.context().storageState({ path: authFile });
         });
        
      2. Create a new setup project in playwright.config.ts

         import { defineConfig, devices } from '@playwright/test';
        
         export default defineConfig({
           projects: [
             // Setup project
             { name: 'setup', testMatch: /.*\.setup\.ts/ },
             ]
         .
         .
         });
        
      3. Now all your tests will run with authentication first

         import { test } from '@playwright/test';
        
         test('test', async ({ page }) => {
           // page is authenticated
             await page.goto('https://github.com/nimit2801');
         });
        
  • Visual Regression Testing

    1. How to setup Visual testing

      • Code Snippet

          import { test, expect } from '@playwright/test';
        
          test('example test', async ({ page }) => {
            await page.goto('https://devassure.io');
            await expect(page).toHaveScreenshot('landing.png');
          });
        
    2. Managing snapshots

      • Updating screenshots: npx playwright test --update-snapshots

      • Keep a folder dedicated to store your snapshots /tests/snapshots

        • Relative Path: expect().toHaveScreenshot(['relative', 'path', 'to', 'snapshot.png'])

Best Practices

  1. Test Structure and Organisation | Test Suites

    • Explanation: Properly structuring and organizing your tests is crucial for maintainability and readability. Playwright supports test suites, which allow you to group related tests together.

    • Code snippet:

        // tests/login.spec.js
        const { test, expect } = require('@playwright/test');
      
        test.describe('Login Functionality', () => {
          test('should login with valid credentials', async ({ page }) => {
            // Test implementation
          });
      
          test('should show error with invalid credentials', async ({ page }) => {
            // Test implementation
          });
        });
      
        // tests/search.spec.js
        test.describe('Search Functionality', () => {
          test('should return results for valid search term', async ({ page }) => {
            // Test implementation
          });
      
          test('should show no results for invalid search term', async ({ page }) => {
            // Test implementation
          });
        });
      
  2. Maintaining Test Code | Page Object Model

    • Explanation: The Page Object Model (POM) is a design pattern that creates an object repository for storing all web elements. It helps reduce code duplication and improves test maintenance.

      Code snippet:

        // pages/LoginPage.js
        class LoginPage {
          constructor(page) {
            this.page = page;
            this.usernameInput = page.locator('#username');
            this.passwordInput = page.locator('#password');
            this.loginButton = page.locator('#login-button');
          }
      
          async login(username, password) {
            await this.usernameInput.fill(username);
            await this.passwordInput.fill(password);
            await this.loginButton.click();
          }
        }
      
        // tests/login.spec.js
        const { test, expect } = require('@playwright/test');
        const { LoginPage } = require('../pages/LoginPage');
      
        test('should login with valid credentials', async ({ page }) => {
          const loginPage = new LoginPage(page);
          await page.goto('https://example.com/login');
          await loginPage.login('validuser', 'validpass');
          await expect(page).toHaveURL('https://example.com/dashboard');
        });
      
  3. Debugging and Troubleshooting | Common issues

    • Explanation: Debugging Playwright tests can be challenging. Here are some common issues and solutions:

      a. Timing issues: Replacing waitFor functions to with await and toBeVisibl

      Code snippet:

        // Instead of:
        await page.click('#submit-button');
      
        // Use:
        await page.waitForSelector('#submit-button');
        await page.click('#submit-button');
      

      b. Capturing screenshots on failure: Automatically capture screenshots when tests fail for easier debugging.

      Code snippet:

        // playwright.config.js
        module.exports = {
          use: {
            screenshot: 'only-on-failure',
          },
        };
      

      c. Using the Playwright Inspector: The Playwright Inspector is a powerful tool for debugging tests.

      Explanation: Run your tests with the --debug flag to use the Inspector:

        npx playwright test --debug
      

      This will open the Playwright Inspector, allowing you to step through your test, inspect the DOM, and more.

      d. Handling dynamic content: Use waitForFunction to wait for specific conditions before proceeding.

      Code snippet:

        await page.waitForFunction(() => {
          const element = document.querySelector('#dynamic-content');
          return element && element.textContent.includes('Expected Text');
        });
      

      These best practices and code snippets should provide a good foundation for your Playwright blog post. Would you like me to elaborate on any specific area or provide more information on a particular topic?

Join Our Community

The Community is for people who want to get resources for Quality Engineering and Automation. We provide you with a space where you can ask questions, share projects and get mentorship. We've an amazing set of resources available for you.

https://www.devassure.io/#our-community