
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
Get Random Line from a File
Add some variety to your PlayWright tests
In PlayWright, you can easily get the contents of a file to include in a form. This is the getRandomLineFromFile function that I use to open up a local file and get a random line:
async function getRandomLineFromFile(filePath: string): Promise {
try {
// Read file content and split into lines
const fileContent = await fs.readFile(filePath, 'utf8');
const lines = fileContent.split('n').filter(line => line.trim() !== ''); // Remove empty lines
if (lines.length === 0) {
throw new Error('File is empty');
}
// Get random line
const randomIndex = Math.floor(Math.random() * lines.length);
return lines[randomIndex].trim();
} catch (error) {
console.error(`Error reading file: ${error}`);
throw error;
}
}
I would use this to open up a file that has a random sentence to fill in a feedback form. Here's an example PlayWright with TypeScript entry that I would use:
test('Random Feedback Form Test', async ({ page }) => {
const filePath = '/Users/dict/srand.txt';
const randomLine = await getRandomLineFromFile(filePath);
await page.goto('https://www....');
await page.waitForLoadState("networkidle");
await page.getByLabel('name').fill('Chris Ryan');
await page.getByLabel('comment').fill(testText);
....
})
You could also do this to randomize names, locations etc. This is just handy to have when you want to add some variety to a test run.
PermalinkXPath with Playwright page.locator
A Practical Guide
Playwright page.locator method is designed to find elements dynamically, with built-in support for multiple selector types - including CSS, text, and yes, XPath. While CSS selectors are great for straightforward queries, XPath shines when you need more flexibility or when dealing with complex DOM structures.
Here's why XPath might be your go-to:
Structural Navigation: XPath lets you traverse the DOM based on relationships (e.g., parent, sibling, child) rather than just classes or IDs.
Attribute Precision: Target elements by any attribute, not just class or id.
Text-Based Selection: Easily find elements containing specific text, even partial matches.
Dynamic Pages: XPath can handle scenarios where CSS selectors falter, like when class names are auto-generated or unpredictable.
Playwright's page.locator makes XPath a first-class citizen, so let's see it in action.
Getting Started with page.locator and XPath
The syntax for using XPath in page.locator is simple: prefix your XPath expression with xpath= or use the double-slash shorthand //. Here's the basic structure:
await page.locator('xpath=//tag[@attribute="value"]').click();
Playwright will evaluate the XPath expression and return a Locator object, which you can then interact with (e.g., click(), fill(), textContent()).
Practical Example
Let's walk through a real-world scenario where XPath and page.locator save the day.
Targeting an Element by Attribute
Imagine a login form with a button lacking a unique ID or class:
<button type="submit" data-test="login-btn">Sign In</button>
With XPath, you can target it by its data-test attribute:
const { test } = require('@playwright/test');
test('click login button', async ({ page }) => {
await page.goto('https://example.com/login');
await page.locator('xpath=//button[@data-test="login-btn"]').click();
});
The //button[@data-test="login-btn"] means "find any <button> element with a data-test attribute equal to login-btn."
When to Avoid XPath
While XPath is powerful, it?s not always the best choice:
- Simple Selectors: Use CSS for #id or .class?it?s faster and more readable.
- Dynamic IDs: If attributes change frequently, text-based or role-based selectors (role=) might be more stable.
- Maintenance: Complex XPath expressions can become brittle if the DOM structure shifts.
PlayWright URL Scraping
Sample Code to get all URLs
While experimenting with Playwright this week, I put together a script that grabs all the URLs from a website and writes them to a file. Here's the code that I finally came up with:
test('Extract and save my URLs from cryan.com', async ({ page }) => {
// Navigate to the target URL
await page.goto('https://www.cryan.com');
// Extract all tags with href attributes
const links = await page.$$eval('a[href]', (anchors) => anchors.map((a) => a.getAttribute('href')));
// Remove any relative URLs or empty strings
const filteredLinks = links.filter((link) => link?.startsWith('http') && link.trim() !== '');
// Save the unique URLs to a file
const uniqueLinks = [...new Set(filteredLinks)];
await fs.promises.writeFile('/Users/cryan/Desktop/url.txt', uniqueLinks.join('n'), 'utf8');
// Assertions to validate extracted URLs
expect(uniqueLinks.length).toBeGreaterThan(0); // Assert at least one URL found
});
This approach is particularly useful when you need to ensure that all the anchor tags on the homepage are functioning as expected. By verifying the anchor tags separately, you can isolate any issues related to broken or misconfigured links, making it easier to pinpoint and address problems.
Additionally, I'll create another test specifically to validate that the URLs associated with these anchor tags are correct. This two-pronged strategy ensures that both the structure and the destinations of your links are accurate.
Pro Tip: The reason for separating these tasks, instead of validating the URLs while scraping the homepage, is to enhance the efficiency of your test execution. By dividing the workload into smaller, targeted tests, you can leverage parallel execution to speed up the overall testing process. This approach not only reduces the total runtime of your test suite but also provides clearer insights into potential issues, allowing you to debug faster and more effectively.
PermalinkAbout
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
Friday 21 | Macintosh |
Saturday 22 | Internet Tools |
Sunday 23 | Misc |
Monday 24 | Media |
Tuesday 25 | QA |
Wednesday 26 | Pytest |
Thursday 27 | PlayWright |