Guide to Playwright - Automation Testing

·

12 min read

What is Playwright

  1. Free & Open source Framework for web automation testing | created by Microsoft

  2. Applications- Web browser | Mobile web apps | API

  3. Languages - JavaScript, TypeScript, Java, Python, .NET (C#)

  4. Browsers - All modern engines chromium , WebKit, and FireFox(headless or headed) 4.1. Headless - will not see browser running physically 4.2. Headed - Can see a physical browser running

  5. OS - Windows, MacOS, Linux, | Supports CI Runs

Features of Playwright

  1. Free and Open source

  2. Multi Browser | Multi Language | Multi OS

  3. Easy setup and Configuration

  4. Functional Testing | API Testing | Accessibility Testing

  5. Built-in Reporters | Custom Reporters

  6. CI CD | Docker support

  7. Recording | Debugging | Explore selectors

  8. Parallel testing

  9. Auto wait (timers)

  10. Built in Assertions | Less Flaky tests

  11. Test retry, logs, screenshots,videos

  12. Multi Tab and Multi window

  13. Frames | Shadow DOM

  14. Emulate mobile devices , geolocations

  15. Test Parameterization

  16. Fast

Prerequisities

Node js

ways to install

-> Install using command as npm package

npm init playwright@latest

To check the version

npm playwright -v

for help

npx playwright --help

-> using vs code extension

File structure
  1. package.json and package.lock.json - node project management file

  2. playwright.config.ts - Configuration file ( we need to change this according to our needs)

  3. tests folder - basic example test

  4. tests-examples folder - detailed example test

  5. .gitignore - to be used during git commit and push

  6. playwright.yml - to be used during ci cd pipeline

How to run tests

command to run tests

npx playwright test

Add workers to test in command so that parallel execution is done

npx playwright test --workers 3

To show report run

npx playwright show-report

To run a specific file

npx playwright test <filepath_name>

we can run multiple files as below

npx playwright test <file_name> <file_name>

we can run file with keyword . If we add keyword in the file it will try to find the word in test file name and it has the name it will run that test

npx playwright test <example>

To run the test with test title

npx playwright test -g <"title">

To run test on specific browser

npx playwright test --project=chromium

To run the test in headed mode

npx playwright test --project=chromium --headed

To debug

npx playwright test --debug

To debug specific file

npx playwright test example.spec.js --debug

To debug specific line in a file

npx playwright test example.spec.js:21 --debug
How to write test
import { test, expect } from "@playwright/test";

test("my first test", async ({ page }) => {
  await page.goto("https://google.com");
  await expect(page).toHaveTitle("Google");
});
To record test

what is code gen - Test generator

Playwright comes with a tool - Codegen also called test generator Can be used to record test and generate test scripts It Opens 2 windows

  1. A browser window to interact with the website

  2. Playwright Inspector window to record test To run recording

npx playwright codegen

To open browser with pre url

npx playwright codegen <google.com | url>

steps to run code gen and work with it

  1. Open terminal and run codegn npx playwright codegen

  2. Check 2 windows open - Browser and Playwright Inspector

  3. Record your test steps and check the test scripts getting started

  4. Save the recorded script in a test file | Run and check

open open specific broswer

npx playwright codegen --browser firefox

need to create file before running below command

Record and save to a file

npx playwright codegen --target javascript -o <filename |path>

set viewport - screen resolution size

npx playwright codegen --viewport-size=800,600

Emulate devices

npx playwright codegen --device="iPhone11"

To add color scheme

This will open the site in dark mode

npx playwright codegen --color-sheme=dark <url>

Trace viewer

GUI tool that helps viewing the executed test along with snapshots, timeline and other details (traces)

How to use Trace viewer
  1. Open the config file and set trace: 'on-first-retry'

    It means - Collect the trace when retrying the failed test for the 1st time only

    add code for 1 retry in the config file

     retries:1
    
  2. Save and Run a test to fail

  3. Check trace.zip file created under test results folder

  4. View trace -

     npx playwright show-trace trace.zip
    

    This will show an UI like this

    We can change the timeouts in the config file for retries

  5. We can also navigate to trace viewer from the reports also

Trace Viewer Options
  • 'on-first-retry' - Record a trace only when retrying a test for the first time

  • 'off' - Do not record trace

  • 'on' - Record a trace for each test ( not recommended as its performance heavy)

  • 'retain-on-failure'- Record a trace for each test , but remove it from successfull test runs

To set trace on from command line

npx playwright test --trace <options>
Different ways to view trace
  1. Using command - npx playwright show-trace trace.zip

  2. using HTML report

  3. using utility - https://trace.playwright.dev/ here we need add our trace our file to view it

How to set Trace programmaticaly

We need to add the code at which part of the execution we want to start trace and at which part we need to stop the trace

test('title',async ({page,context})=>{
await context.tracing.start({screenshots:true,snapshots:true})
await page.goto('https://google.com')
await expect(page).toHaveTitle('Google')
await context.tracing.stop({path:<filename.zip>})
})

We can start and stop this tracing before the test and after the test by using hooks

beforeAll and afterAll

let context
let page
test.beforeAll(async({browser})=>{
context=await browser.newContext()
await context.tracing.start({screenshots:true,snapshots:true})
page= await context.newPage()
})

test.afterAll(async()=>{
await context.tracing.stop({pathe:<filename.zip>})
})

Here we are declaring global context and page which should be using in our test rather than individual param

How to find web Objects

what are Selectors and Locators

  • Selectors are the strings/properties of the web objects

  • Selectors are used to create Locators

  • Selectors example: CSS, Class, Name, ID,Text,XPath

  • To find an object or element we use the syntax page.locator(selector[,options])

  • Locator is class in playwright library

How to find web objects with Playwright

To find an object or element we use the syntax page.locator(selector[,options])

How to find objects using XPath ,CSS, Text, ID etc

Using inspect

How to find and record object locators using Playwright Inspector

There are 3 ways to open playwright inspector

  1. With pause in code

  2. With --debug in command

  3. With codegen tool

import { expect, test } from "@playwright/test";

test("selectors demo", async ({ page }) => {
  await page.goto("https://www.saucedemo.com");

  await page.click("id=user-name");
  await page.locator("id=user-name").fill("Edison"); //or
  await page.locator('[id="user-name"]').fill("vishnu");

  //CSS selector
  //inspect element and right and copy the css selector
  //use explore in inspector to get best possible object
  await page.locator("#login-button").click();

  //XPath
  await page.locator('xpath=//input[@name="password"]').fill("hello");
  await page.locator('//input[@name="password"]').fill("Ramanujan");

  //Text
  await page.pause();
  await page.locator("text=LOGIN").click();
  await page.locator('input:has-text("LOGIN")').click();
});
Demo Login Test
import { test, expect } from "@playwright/test";

test("Demo login test 1", async ({ page }) => {
  await page.goto("https://demo.applitools.com/");

  await page.pause();
  await page.getByPlaceholder("Enter your username").fill("vishnu");
  await page.getByPlaceholder("Enter your password").fill("password");
  //if we want to wait for a ui object and the perform operation we can use this below method
  await page.waitForSelector("text=Sign in", { timeout: 5000 });
  //we can also add expect selector
  await expect(page.locator("text=Sign in")).toHaveCount(1);
  await page.getByRole("link", { name: "Sign in" }).click();
});
//This only will make this the below test only run when we refer this file for test
// we can create test using record also
test.only("Demo login test 1", async ({ page }) => {
  await page.goto("https://demo.applitools.com/");

  await page.pause();
  await page.getByPlaceholder("Enter your username").fill("vishnu");
  await page.getByPlaceholder("Enter your password").fill("password");
  //if we want to wait for a ui object and the perform operation we can use this below method
  await page.waitForSelector("text=Sign in", { timeout: 5000 });
  //we can also add expect selector
  await expect(page.locator("text=Sign in")).toHaveCount(1);
  await page.getByRole("link", { name: "Sign in" }).click();
});
Assertions

Check or verification

Check actual =expected

Assertions in playwright

Playwright Test uses expect library for test assertions

How to add assertions
  • Present / Not present

  • Visible / Hidden

  • Enabled / Disabled

  • Text matches / not matches value

  • Element attribute

  • Url

  • title

  • Page matches screenshot ( visual validation)

  • soft assertions ( continue testing even its fails)

import { test, expect } from "@playwright/test";

test("Assertions demo", async ({ page }) => {
  await page.goto("https://kitchen.applitools.com/");
  await page.pause();
  //assertions
  //check element present or not
  await expect(page.getByRole("heading", { name: "The Kitchen" })).toHaveCount(
    1,
  );

  // for conditions we use this $
  if (await page.$("text=The Kitchen")) {
    await page.locator("text=The Kitchen").click();
  }
  // check element hidden or visible
  await expect(page.locator("text=The Kitchen")).toBeVisible();
  await expect.soft(page.locator("text=The Kitchen")).toBeHidden();

  //check element enabled or disabled
  await expect(page.locator("text=The Kitchen")).toBeEnabled();
  await expect.soft(page.locator("text=The Kitchen")).toBeDisabled();

  //check text
  await expect(page.locator("text=The Kitchen")).toHaveText("The Kitchen");
  await expect
    .soft(page.locator("text=The Kitchen"))
    .not.toHaveText("The Kitchen");

  //to check for element attribute
  // we can use regex in this as the classes might be dynamic
  await expect(page.locator("text=The Kitchen")).toHaveAttribute(
    "class",
    "class_name",
  );
  await expect(page.locator("text=The Kitchen")).toHaveClass(/.*css-dpmy2a/);
  // url
  await expect(page).toHaveURL("some url");
  await expect(page).toHaveTitle("title");
  // screenshot visual validation
  // This will first take a screenshot then it will compare the ui with prev screenshot
  await expect(page).toHaveScreenshot();
});
How to run tests in Slow motion (decrease speed of test execution)

How to record video

from config file and from with the test

  1. From the config file change the use object as below

 video: "on",
    launchOptions: {
      slowMo: 1000,
    },

slowMo slows down Playwright operations by the specified milliseconds

Video:

  • 'on' - Record video for each test

  • 'off' - Do not record video for each test

  • 'retain on failure' - Record for each test, but remove from successful test runs

  • 'on-first-retry' - Record only when retrying a test for the first time

  1. Save & run

  2. Check video files will appear in the test results folder

How to set video recording and slow motion from test (Browser context)

In Playwright we can create isolated incognito browser sessions using browser context

  1. Create a test and create browser context

  2. Add Options for slowmotion in browser

  3. Add options for video recording in new context

  4. Close context

import { test, expect, chromium } from "@playwright/test";

test("slowmotion video recording", async () => {
  const browser = await chromium.launch({
    slowMo: 500,
    headless: false,
  });

  const context = await browser.newContext({
    recordVideo: {
      dir: "videos/",
      size: { width: 800, height: 600 },
    },
  });

  const page = await context.newPage();

  await page.goto("url");

  //rest of the test

  await context.close();
});
Hooks and groups

Hooks :

  • beforeAll

  • beforeEach

  • afterAll

  • afterEach

Groups:

  • describe
import { test, expect } from "@playwright/test";

//this will group all the tests and the hooks inside the block the tests will run only for this group
test.describe("Tests all", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("https://saucedemo.com");
    //await page.pause();
    await page.locator('[data-test="username"]').click();
    await page.locator('[data-test="username"]').fill("standard_user");
    await page.locator('[data-test="password"]').click();
    await page.locator('[data-test="password"]').fill("secret_sauce");
    await page.locator('[data-test="login-button"]').click();
    //await page.getByRole("button", { name: "Open Menu" }).click();
    //await page.locator('[data-test="logout-sidebar-link"]').click();
    //await page.close();
  });

  test("home page", async ({ page }) => {
    // await page.goto("https://saucedemo.com");
    // await page.locator('[data-test="username"]').click();
    // await page.locator('[data-test="username"]').fill("standard_user");
    // await page.locator('[data-test="password"]').click();
    // await page.locator('[data-test="password"]').fill("secret_sauce");
    // await page.locator('[data-test="login-button"]').click();
    await page.locator('[data-test="add-to-cart-sauce-labs-backpack"]').click();
    await page
      .locator('[data-test="add-to-cart-sauce-labs-bike-light"]')
      .click();
    await page.locator('[data-test="item-1-title-link"]').click();
    await page.locator('[data-test="add-to-cart"]').click();
    // await page.close();
  });

  test("logout", async ({ page }) => {
    // await page.goto("https://saucedemo.com");
    // //await page.pause();
    // await page.locator('[data-test="username"]').click();
    // await page.locator('[data-test="username"]').fill("standard_user");
    // await page.locator('[data-test="password"]').click();
    // await page.locator('[data-test="password"]').fill("secret_sauce");
    // await page.locator('[data-test="login-button"]').click();
    await page.getByRole("button", { name: "Open Menu" }).click();
    await page.locator('[data-test="logout-sidebar-link"]').click();
    //await page.close();
  });
  test.afterAll(async ({ page }) => {
    await page.close();
  });
});
Annotations & Tags

Annotations

  1. only

  2. skip

  3. skip with condition

  4. fail

  5. fixme

  6. slow

Tags:

  • smoke

  • reg

  • ...

import { test } from "@playwright/test";

//will skip the test
test.skip("test1", async ({ page }) => {});

//will fail the fail wantedly
test("fail", async ({ page }) => {
  test.fail();
});
// test to be fixed
test.fixme("test to be fixed", async ({ page }) => {});

//marks the test as slow and triples the timeout
test("slow", async ({ page }) => {
  test.slow();
});

// when we want to run only specific test in a file
//
test.only("only", async ({ page }) => {});

//skip conditionally

//Tags

// we will add tags to title , when ever we run the tests from command we will add tags to which we want it
// to run , only those will be run
//
test("Test login page @fast", async ({ page }) => {});
test("Test login page @slow", async ({ page }) => {});

//To run this with tags we will run below command
/// npx playwright test --grep "@smoke"

// To skip the tag we have given try below command
//
// npx playwright test --grep-invert "@smoke"

To run with tags we will run the command below

npx playwright test --grep "@smoke"

To skip the tag we have given try below command

npx playwright test --grep-invert "@smoke"

Playwright Page Object Model Project

  1. Create a new folder and open IDE or Vs code

  2. Initialize a new nodejs project by running npm init -y to create a package.json

  3. Install & setup Playwright by running npm init playwright@latest

  4. Create a demo login test, can use Test generator to record npx playwright codegen

  5. Run tests and check results - npx playwright test && npx playwright show-report

  6. Create new folder "pages" in your project , this will contain all page objects

  7. Create a new file and class for each page e.g : login.ts and LoginPage

  8. In the class create element locators & action methods for login page

  9. In test file, import the pages class, create instance of it, & use methods from LoginPage class

  10. Run the test npx playwright test and check results

Login.ts

import { Page, Locator } from "@playwright/test";

export default class LoginPage {
  username_textBox: Locator;
  pasword_textBox: Locator;
  login_button: Locator;
  page: Page;
  constructor(page: Page) {
    this.page = page;
    this.username_textBox = page.getByLabel("Username");
    this.pasword_textBox = page.getByLabel("Password");
    this.login_button = page.getByRole("button", { name: " Login" });
  }

  async goToLoginPage(url: string) {
    await this.page.goto(url);
  }

  async login(username: string, password: string) {
    await this.username_textBox.fill(username);
    await this.pasword_textBox.fill(password);
    await this.login_button.click();
  }
}

login.spec.ts

import { test } from "@playwright/test";
import LoginPage from "../pages/login";

test(" login test", async ({ page }) => {
  const login = new LoginPage(page);
  await login.goToLoginPage("https://the-internet.herokuapp.com/login");

  await login.login("tomsmith", "SuperSecretPassword");
});

API Testing

To open ui of playwright

npx playwright test --ui

The ui will look as below

import { test, expect } from "@playwright/test";

//reqres dummy api
test("API GET Request", async ({ request }) => {
  const response = await request.get("https://reqres.in/api/users/2");
  expect(response.status()).toBe(200);
  const text = await response.text();
  expect(text).toContain("J");
});

test("API POST Request", async ({ request }) => {
  const response = await request.post("https://reqres.in/api/users", {
    data: {
      name: "vishnu",
      job: "engineer",
    },
  });
  expect(response.status()).toBe(201);
  const text = await response.text();
  expect(text).toContain("vishnu");
});