QA Graphic
May 22, 2025

Popular Assertions in Playwright with TypeScript

Top 5 Assertions That People Use

A guide to the most commonly used assertions in Playwright for robust test automation with TypeScript.

Playwright's expect function offers a variety of assertions to validate webpage elements, states, and behaviors. Below are the most popular ones, with TypeScript examples.

1. toBeVisible

Verifies that an element is visible on the webpage. This is useful for checking if UI components render correctly.

import { test, expect } from '@playwright/test';
test('should display header', async ({ page }) => {
  await page.goto('https://example.com');
  const header = page.locator('h1');
  await expect(header).toBeVisible();
});

This test navigates to a webpage and checks if an h1 element is visible.

2. toHaveText

Asserts that an element contains the specified text. It can match exact text or a regular expression.

test('should have correct button text', async ({ page }) => {
  await page.goto('https://example.com');
  const button = page.locator('button#submit');
  await expect(button).toHaveText('Submit');
});

Use this to verify text content in buttons, labels, or other elements.

3. toHaveAttribute

Checks if an element has a specific attribute with an expected value, such as class, id, or custom attributes.

test('should have disabled attribute', async ({ page }) => {
  await page.goto('https://example.com');
  const input = page.locator('input#username');
  await expect(input).toHaveAttribute('disabled', '');
});

This is ideal for testing element states like disabled or checked inputs.

4. toBeEnabled / toBeDisabled

Verifies whether an element is enabled or disabled, commonly used for form controls.

test('should have enabled submit button', async ({ page }) => {
  await page.goto('https://example.com');
  const button = page.locator('button#submit');
  await expect(button).toBeEnabled();
});

Use toBeDisabled for the opposite case.

5. toHaveValue

Asserts that an input element (e.g., input, textarea) has a specific value.

test('should have input value', async ({ page }) => {
  await page.goto('https://example.com');
  const input = page.locator('input#username');
  await input.fill('testuser');
  await expect(input).toHaveValue('testuser');
});

Permalink
May 15, 2025

Filtering Locators by Text Content in Playwright with TypeScript

How I used Playwright's locator filtering to extract data

Introduction

Automating web testing often involves interacting with elements that lack unique identifiers like IDs or classes. In such cases, Playwright's powerful locator filtering capabilities can save the day. In this post, we'll explore how to filter locators by text content in TypeScript, using a real-world example from the American Freight Refrigerators & Freezers page.

Our goal is to capture the number of items displayed on the page, where the text (e.g., "Showing 24 results") is not tied to a unique ID. We'll use Playwright's text-based filtering and parse the result to extract the number.

The Challenge

On the American Freight page, the text indicating the number of results is displayed dynamically, something like:

Showing 24 results

The element containing this text doesn't have a unique ID or class, making it tricky to locate directly. A naive approach might grab multiple elements with similar text, leading to incorrect results. Playwright's locator filtering allows us to narrow down the search by combining text matching with additional conditions.

Refrigerators
Screenshot of the page that I am testing.

Solution: Playwright Locator Filtering

Playwright provides the getByText method to locate elements by their text content. We can further refine this using the filter method to ensure we target the exact element. Here's how it works:

  1. Use page.getByText('Showing', { exact: false }) to find elements containing the word "Showing".
  2. Apply filter({ hasText: 'results' }) to narrow down to elements that also contain "results".
  3. Extract the text content and parse it to get the number using a regular expression.

Below is the complete TypeScript code for the test:

import { test, expect } from '@playwright/test';
test('American Freight Refrigerator Numbers', async ({ page }) => {
  await page.goto('https://www.americanfreight.com/plp/appliances/refrigerators-freezers/695');
  // Locate element with text starting with "Showing" and containing "results"
  const locator = page.getByText('Showing', { exact: false });
  const element = locator.filter({ hasText: 'results' });
  const text = await element.innerText();   
  const match = text.match(/Showing (d+) results/);
  const resultsNumber = match ? match[1] : null;
  
  console.log(`Number of results: ${resultsNumber}`);
  expect(resultsNumber).not.toBeNull();
});

Code Breakdown

Let's dissect the key parts of the code:

  • Navigating to the Page: await page.goto(...) loads the American Freight page.
  • Locating by Text: page.getByText('Showing', { exact: false }) finds all elements containing "Showing". The exact: false option allows partial matches.
  • Filtering: locator.filter({ hasText: 'results' }) refines the locator to only include elements that also contain "results".
  • Extracting Text: await element.innerText() retrieves the full text content (e.g., "Showing 24 results").
  • Parsing the Number: The regex /Showing (d+) results/ captures the number between "Showing" and "results". The result is stored in resultsNumber.

Why Use Locator Filtering?

Locator filtering is a game-changer for several reasons:

  • Precision: It allows you to target elements based on multiple conditions, reducing false positives.
  • Flexibility: You can combine text, attributes, or even child elements in the filter.
  • Robustness: It handles dynamic content where IDs or classes may change.

In our example, filtering ensured we got the exact element with "Showing" and "results", avoiding other elements with similar text.

Tips for Success

  • Inspect the Page: Use browser DevTools to confirm the text content and structure before writing your locator.
  • Test Your Locator: Use Playwright's locator.count() or locator.all() to verify how many elements match your criteria.
  • Handle Edge Cases: Add checks for null or unexpected text formats, as we did with the regex match.
  • Debugging: Log the innerText or use Playwright's tracing to inspect the locator's behavior.

Conclusion

Playwright's locator filtering by text content is a powerful tool for tackling elements without unique identifiers. By combining getByText and filter, we successfully extracted the number of refrigerators from the American Freight page. This approach is versatile and can be adapted to many scenarios in web automation.

Try it out in your next Playwright project, and explore other filtering options like has or hasNotText to handle even more complex cases!

Permalink
May 8, 2025

Validating Todays Date with Playwright and TypeScript

Learn how to use Playwright with TypeScript to check if today's date is displayed on a web page.

In this tutorial, we'll create a Playwright script in TypeScript to validate that today's date is present on a web page. This is useful for testing dynamic content, such as date displays in web applications.

Writing the Validation Script

We'll write a script to navigate to a web page, extract text from an element, and verify if it contains today's date. For this example, we'll assume the page displays the date in a format like "April 30, 2025" (you can adjust the format as needed).

Create a file named src/validate-date.ts with the following code:


import { chromium, Browser, Page } from 'playwright';
async function validateTodaysDate(): Promise {
    // Launch browser
    const browser: Browser = await chromium.launch();
    const page: Page = await browser.newPage();
    try {
        // Navigate to the page (replace with your target URL)
        await page.goto('https://example.com');
        // Get today's date in the format "April 30, 2025"
        const today: string = new Date().toLocaleDateString('en-US', {
            month: 'long',
            day: 'numeric',
            year: 'numeric'
        });
        // Locate the element containing the date (adjust selector as needed)
        const dateElement = await page.locator('h1'); // Example: 

April 30, 2025

const pageDate: string = await dateElement.textContent() || ''; // Validate the date if (pageDate.includes(today)) { console.log(`Success: Today's date "${today}" is displayed on the page.`); } else { console.error(`Failure: Expected "${today}", but found "${pageDate}".`); } } catch (error) { console.error(`Error: ${(error as Error).message}`); } finally { // Close browser await browser.close(); } } // Run the script validateTodaysDate();

This script:

  • Launches a Chromium browser using Playwright.
  • Formats today's date using toLocaleDateString to match the expected format.
  • Navigates to the target page and extracts text from a specified element (e.g., an h1 tag).
  • Checks if the element's text contains today's date.
  • Logs success or failure and handles errors.

Note: Replace https://example.com with your target URL and adjust the locator selector (e.g., 'h1') to match the element containing the date on your page.

Running the Script

Compile and run the TypeScript script using:


npx ts-node src/validate-date.ts
            

Example output if the date is found:


Success: Today's date "April 30, 2025" is displayed on the page.
            

If the date is not found or an error occurs, you'll see an appropriate error message.

Conclusion

Using Playwright with TypeScript, you can reliably validate dynamic content like today's date on a web page. TypeScript's type safety helps catch errors early, while Playwright's powerful APIs simplify browser automation. Customize the selector and date formats to fit your use case, and consider integrating with Playwright's test framework for robust testing.

For further exploration, try adding visual regression testing or combining with CI/CD pipelines to automate date validation in your workflows.

Permalink
May 1, 2025

Measuring Page Load Time with Playwright

Learn how to use Playwright to measure how long it takes for a web page to load.

One of its many capabilities PlayWright is measuring page performance metrics, such as load times. In this tutorial, we'll walk through how to use Playwright to record the time it takes for a page to fully load.

Writing the Script

Create a file named measure-load-time.js and add the following code to measure the page load time for a website (e.g., example.com):


const { chromium } = require('playwright');
(async () => {
    // Launch browser
    const browser = await chromium.launch();
    const page = await browser.newPage();
    // Record start time
    const startTime = performance.now();
    // Navigate to the page
    await page.goto('https://example.com');
    // Wait for the page to fully load
    await page.waitForLoadState('load');
    // Record end time
    const endTime = performance.now();
    // Calculate load time
    const loadTime = endTime - startTime;
    console.log(`Page load time: ${loadTime.toFixed(2)} ms`);
    // Close browser
    await browser.close();
})();
            

This script:

  • Launches a Chromium browser using Playwright.
  • Records the start time using performance.now().
  • Navigates to the specified URL.
  • Waits for the load event, indicating the page is fully loaded.
  • Calculates the load time by subtracting the start time from the end time.
  • Outputs the result in milliseconds.

Running the Script

Run the script using Node.js:


node measure-load-time.js
            

You should see output similar to:


Page load time: 1234.56 ms
            

The actual time will vary depending on the website, network conditions, and system performance.

Permalink
April 24, 2025

Speed Up Your Tests with storageState()

Little Known Tip to Get Your Test Running Faster

Tired of logging in before every test? Playwright’s storageState() lets you save your login state and reuse it across multiple tests - boosting performance and reliability.

Quick Tip: Reusing browser context cuts down execution time and enhances CI/CD pipeline efficiency.

Why storageState() Matters

Every time your test logs in, you're spending time and creating potential points of failure. Also it adds additional overhead on your tests - that may impact the overall test time. Instead, you can log in once, save the state (cookies + local storage), and load that state in future tests.

Step 1: Save the Auth State

Create a setup script that logs in and saves the storage state.

// setup-auth.ts
import { chromium } from '@playwright/test';
(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();
  await page.goto('https://example.com/login');
  await page.fill('#username', 'your-username');
  await page.fill('#password', 'your-password');
  await page.click('button[type="submit"]');
  // Wait for redirect or element that confirms login
  await page.waitForSelector('#dashboard');
  // Save storage state to file
  await context.storageState({ path: 'auth.json' });
  await browser.close();
})();

Step 2: Reuse the Auth State

In your test file, load the previously saved auth.json file to reuse the session.

// example.spec.ts
import { test, expect } from '@playwright/test';
test.use({
  storageState: 'auth.json'
});
test('Dashboard loads for authenticated user', async ({ page }) => {
  await page.goto('https://example.com/dashboard');
  await expect(page.locator('#dashboard')).toBeVisible();
});

When Should You Use This?

  • Tests that require an authenticated user
  • Reducing login redundancy in CI/CD pipelines
  • Speeding up test suites with shared session state
Pro Tip: You can use different storage files for different roles or test scenarios.

Start using storageState() today and take the fast lane through your E2E tests!

Permalink
April 17, 2025

Modern Number Formatting

From Perl to Playwright

The Old Way: Perl's Commify Function

In the days of Perl, developers often wrote custom functions to format numbers with commas for readability. Here's an example of a Perl function called commify that added commas to numbers:


sub commify {
    local($_) = shift;
    1 while s/^(-?d+)(d{3})/$1,$2/;
    return $_;
}

                

This function:

  • Takes a number as input.
  • Uses a regular expression to insert commas every three digits from the right.
  • Handles negative numbers with the -? pattern.
  • Returns the formatted string (e.g., 1234567 becomes 1,234,567).

While effective, this approach is verbose and relies on regex, which can be error-prone and hard to maintain.

The Modern Way: Playwright with TypeScript

Today, when working with modern web automation tools like Playwright and TypeScript, you can achieve the same result using JavaScript's built-in toLocaleString() method. This method is simpler, more readable, and supports internationalization out of the box.

Here's how you can format numbers in a Playwright test with TypeScript:


import { test, expect } from '@playwright/test';
test('Format number with commas', async ({ page }) => {
    await page.goto('https://example.com');
    const number = 1234567;
    const formattedNumber = number.toLocaleString('en-US'); // Outputs: "1,234,567"
    // Example: Set the formatted number in an input field
    await page.locator('#number-input').fill(formattedNumber);
    // Verify the value
    const inputValue = await page.locator('#number-input').inputValue();
    expect(inputValue).toBe('1,234,567');
});

In this example:

  • toLocaleString('en-US') formats the number with commas according to the US locale (e.g., 1234567 becomes 1,234,567).
  • The formatted number is used in a Playwright test to fill an input field and verify its value.
  • No custom regex or loops are needed, making the code cleaner and less prone to errors.

Why toLocaleString() is Better

Using toLocaleString() over the Perl commify function offers several advantages:

  • Simplicity: No need for complex regex or manual string manipulation.
  • Internationalization: Supports different locales (e.g., de-DE for German formatting with periods: 1.234.567).
  • Built-in: Native to JavaScript, so no custom code is required.
  • Type Safety: When used in TypeScript, you get type checking for numbers and locale strings.

For example, to format a number for a German audience:


const number = 1234567;
const formattedNumber = number.toLocaleString('de-DE'); // Outputs: "1.234.567"

                

Using toLocaleString() in Playwright

In a real-world Playwright scenario, you might need to format numbers when testing forms, displaying data, or verifying UI elements. Here's a more complete example:


import { test, expect } from '@playwright/test';
test('Test number formatting in a form', async ({ page }) => {
    await page.goto('https://example.com/form');
    const rawNumber = 9876543.21;
    const formattedNumber = rawNumber.toLocaleString('en-US', {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
    }); // Outputs: "9,876,543.21"
    // Fill a form input with the formatted number
    await page.locator('#price-input').fill(formattedNumber);
    // Submit the form
    await page.locator('#submit-btn').click();
    // Verify the displayed result
    const displayText = await page.locator('#price-display').textContent();
    expect(displayText).toContain('9,876,543.21');
});

This test formats a number with two decimal places, fills a form, submits it, and verifies the result in the UI.

Conclusion

The transition from Perl's commify to JavaScript's toLocaleString() in a Playwright and TypeScript environment showcases how modern web development simplifies tasks that once required custom solutions. With toLocaleString(), you get a robust, maintainable, and internationally aware solution for number formatting, perfectly suited for browser automation with Playwright.

So, next time you're formatting numbers in your Playwright tests, skip the regex and reach for toLocaleString() - your code will thank you!

Permalink
April 10, 2025

Negative Testing in Playwright with TypeScript

Using Try/Catch

One of the key ways of building a champion automated test suite is negative testing - making sure that your application gracefully handles errors, exceptions, and bad user behavior. This is a good way for automation to add value with testing as negative testing isn't something that manual QA does on a regular bases.

When working with Playwright and TypeScript, there isn't a direct equivalent to pytest.raises() in Python for expecting exceptions. But that doesn't mean you're out of luck.

With TypeScript, the tried-and-true try/catch pattern becomes a powerful way to validate that your code throws errors when it's supposed to.

Scenario: Navigating to an Invalid URL

Say you want to verify that navigating to a malformed URL triggers the appropriate error. Here's how you can do it using Playwright with TypeScript:

import { test, expect } from '@playwright/test';
test('should throw an error when navigating to an invalid URL', async ({ playwright }) => {
  let errorCaught = false;
  let errorMessage = '';
  try {
    // Attempt to navigate to an invalid URL, which should throw an error
    await playwright.chromium.launch().then(async browser => {
      const page = await browser.newPage();
      await page.goto('httpy://www.whitehouse.gov'); // Intentionally bad protocol
      await browser.close();
    });
  } catch (error) {
    errorCaught = true;
    errorMessage = error instanceof Error ? error.message : String(error);
  }
  // Assert that an error was caught
  expect(errorCaught).toBe(true);
  // Optionally, check the error message contains expected content
  expect(errorMessage).toContain('net::ERR_ABORTED');
});

Why Use try/catch Instead of Custom Matchers?

Playwright doesn't provide a built-in matcher for error expectations like pytest.raises. However, try/catch blocks are fully supported and give you total control over the error message, how it's caught, and what behavior you want to assert afterward.

You can go beyond just checking that an error occurred-you can validate specific error types, compare error messages, and decide whether to re-throw or suppress the error.

Tips for Cleaner Code

If you find yourself doing this often, you can extract the logic into a helper function:

async function expectError(fn: () => Promise<void>, expectedMessagePart: string) {
  let errorCaught = false;
  let errorMessage = '';
  try {
    await fn();
  } catch (error) {
    errorCaught = true;
    errorMessage = error instanceof Error ? error.message : String(error);
  }
  expect(errorCaught).toBe(true);
  expect(errorMessage).toContain(expectedMessagePart);
}

And use it like this:

test('should throw an error for bad URL', async ({ playwright }) => {
  await expectError(async () => {
    const browser = await playwright.chromium.launch();
    const page = await browser.newPage();
    await page.goto('httpy://www.whitehouse.gov');
    await browser.close();
  }, 'net::ERR_ABORTED');
});

Final Thoughts

try/catch isn't just a fallback-it's a flexible and explicit way to perform negative testing in Playwright. It gives you visibility into what exactly went wrong and control over how to validate it. Whether you're validating error messages, failed network calls, or unhandled exceptions, try/catch should be part of your Playwright testing toolkit.

Permalink
April 3, 2025

In Automation Consistency Is Key

PlayWright Flaky Test Check

In Automation Consistency Is Key

Tests that pass reliably instill confidence in your codebase, while unpredictable ones can erode trust and waste valuable time. Enter flaky tests - those pesky tests that don't always pass on the first try, even when the underlying code seems fine.

Fortunately, Playwright, a powerful browser automation framework, offers a handy tool to help QA automation teams tackle this challenge head-on: the --fail-on-flaky-tests option.


What Are Flaky Tests?

Flaky tests are the wild cards of the testing world. They might pass on one run and fail on the next, often due to timing issues, external dependencies, or subtle environmental differences.

While they don't necessarily indicate a bug in the application, they do signal a problem in the test itself - something that might need a closer look to ensure reliability.

For QA teams, flaky tests can be a headache. They slow down workflows, muddy the waters of test reporting, and make it harder to trust the results of a test suite. That's where Playwright steps in with a feature designed to shine a spotlight on these troublemakers.


Playwright's Flaky Test Detection

Playwright provides a built-in mechanism to identify and flag flaky tests, helping teams address them proactively. By enabling certain configurations, Playwright can detect when a test exhibits inconsistent behavior - passing sometimes and failing others - and mark it as flaky.

This doesn't just sweep the issue under the rug; it gives teams actionable insights to refine their tests.

One particularly useful option in Playwright's arsenal is --fail-on-flaky-tests. By default, this option is set to false, meaning Playwright will note flaky tests but won't treat them as failures in the overall test run.

However, when you flip this switch to true, Playwright takes a stricter stance: if any test is flagged as flaky, the entire test suite fails. This forces the team to confront the issue immediately rather than letting it linger.


How to Use --fail-on-flaky-tests

To leverage this feature, simply add the --fail-on-flaky-tests flag when running your Playwright tests from the command line. For example:

            
                npx playwright test --fail-on-flaky-tests
            
        

Permalink
March 27, 2025

PlayWrite Date format

Typescript vs Python

Amazing how easier it is to get the date format for saving screenshots. Why is it so complicated in Typescript?

Here's getting the date in YYYY-MM-DD-HH-MM format:

Typescript


function getCurrentDateTimeFormatted(): string {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
  const day = String(now.getDate()).padStart(2, '0');
  const hours = String(now.getHours());
  const minutes = String(now.getMinutes());
  return `${year}-${month}-${day}-${hours}-${minutes}`;
}
// Example usage
const formattedDateTime = getCurrentDateTimeFormatted();

Python


from datetime import datetime
datefile = datetime.now().strftime("%Y-%m-%d-%H-%M")

Permalink
March 20, 2025

Parametrization in PlayWright

Test Uptime Status of Multiple Sites

Yesterday, I showed how to use Parametrization in Pytest. Here's an example of how you would run that same code in PlayWright with TypeScript:

This code checks to make sure the four websites are up and running. This is just a quick sanity test, it doesn't do any critical path testing.


import { test, expect } from '@playwright/test';
// List of websites to test
const WEBSITES = [
  "https://www.company.com",
  "https://qa1.company.com",
  "https://qa2.company.com",
  "https://stage.company.com",
];
// Configure Playwright to run in headless mode globally
test.use({ headless: true });
test(`Check Websites Status`, async ({ page }) => {
// Iterate over websites to create a test for each
    for (const website of WEBSITES) {
        test(`Check if ${website} is up and running`, async ({ page }) => {
            try {
                // Attempt to load the website
                await page.goto(website, { waitUntil: 'domcontentloaded' });
                // Check if page title exists and is not empty
                const title = await page.title();
                expect(title).not.toBe('');
                // Check if body element exists
                const body = page.locator('body');
                await expect(body).toBeVisible();
                // Log success
                console.log(`✓ ${website} is up and running (Title: ${title})`);
            } catch (error) {
                // Oh the Horror: Fail the test with a detailed message
                throw new Error(`Website ${website} failed: ${error.message}`);
            }
        });
    }
})

Permalink

About

Welcome to Playwright Tips and Tricks, your go-to resource for mastering the art of web automation and testing with Playwright! Whether you're a seasoned developer looking to streamline your workflows or a curious beginner eager to dive into the world of browser automation, this blog is designed with you in mind. Here, I'll share a treasure trove of practical insights, clever hacks, and step-by-step guides to help you harness the full power of Playwright - a modern, open-source tool that's revolutionizing how we interact with web applications.

Check out all the blog posts.

Blog Schedule

Saturday 24 Internet Tools
Sunday 25 Misc
Monday 26 Media
Tuesday 27 QA
Wednesday 28 Pytest
Thursday 29 PlayWright
Friday 30 Macintosh