Automated Browser Tests
Using Playwright to easily run UI tests in Chrome, Firefox and WebKit
What is Playwright?
In the last decade or so, numerous solutions were created to test the behaviour and performance of your web apps when they are actually running in a browser. Frameworks like Selenium and Cucumber appeared early on but needed a full browser. Then technologies emerged like Headless Chrome and wrappers around it, like PhantomJS. After the latter project was abandoned, Google quickly came up with a replacement called Puppeteer.
Last year, the Puppeteer team basically moved to Microsoft and created Playwright. Its APIs are quite similar to Puppeteer, but it has one huge advantage: it supports not only Chromium, but also Firefox and WebKit. It makes it a great tool to automate real browser tests across the major browsers out there.
What can it do?
Some of the main use cases for Playwright (and Puppeteer) are:
- Crawling of Single Page Applications (SPA)
- Creating a screenshot of a website
- Testing user interaction, like clicks and form submissions
- Automate your tests for different browsers / versions
Let's take a look at a couple of examples for these use cases.
Example 1: Create a screenshot
Since Playwright is a NodeJS module, let's create a NodeJS project and install the prerequisites:
npm init
npm install -D playwright
Then, let's create a script that generates a screenshot:
const { devices, chromium } = require('playwright');
(async () => {
// Start a headless browser
const browser = await chromium.launch()
const pixel2 = devices['Pixel 2']
// Start a browser context
const context = await browser.newContext({
...pixel2
})
// Open a new page
const page = await context.newPage()
page.on('console', (message) => {
console.log('Browser console message :' + message.text())
})
// Load the page, wait for network calls to be done
await page.goto('https://github.com', {waitUntil: 'networkidle'});
// Make a screenshot
await page.screenshot({
path: 'screenshot.png'
})
// Close down
await browser.close()
})();
Running the script will generate screenshot.png
file as the one shown below:
Example 2: SPA crawling
In this example, we generate a static version of a Single Page Application, which is useful to serve to search bots or even for your own site indexer:
const { devices, chromium } = require('playwright');
(async () => {
// Start a headless browser
const browser = await chromium.launch()
const pixel2 = devices['Pixel 2']
// Start a browser context
const context = await browser.newContext({
...pixel2
})
// Open a new page
const page = await context.newPage()
page.on('console', (message) => {
console.log('Browser console message :' + message.text())
})
// Load the page, wait for network calls to be done
await page.goto('https://notificare.com', {waitUntil: 'networkidle'});
// Close down
await browser.close()
})();
It will write the content after rendering to the console as follows:
<!DOCTYPE html><html lang="en" data-react-helmet="lang"><head><meta charset="utf-8"><meta http-equiv="x-ua-compatible" content="ie=edge"><meta name="viewport" content="width=device-width,
In a real world application, you would of course hook this up to your web server and serve it based on the UserAgent header (and probably would be smart to cache it too 😉)
Example 3: Web Push
The final example is a bit more elaborate. We want to test the following:
- Open a browser with https://notificare.com
- Accept cookie policy
- Opt-in for web push, retrieve the subscription endpoint so we can send a test push (this could also be automated of course)
- Wait for a notification to come in
Before we proceed, it's important to note that headless browsers do not support Web Push and Chromium itself does not support Web Push in Incognito Mode.
We will then start a (non-headless) Chromium browser with a persistent user profile, which also allows us to emulate recurring visits that make use of data stored in Local Storage.
const { devices, chromium } = require('playwright');
(async () => {
//const browser = await chromium.launch()
const pixel2 = devices['Pixel 2']
// Start a browser with a user profile, i.e., non-incognito
const context = await chromium.launchPersistentContext('profile', {
headless: false,
...pixel2,
})
const page = await context.newPage()
page.on('console', (message) => {
console.log('Browser console message :' + message.text())
})
// Grant permission for Web Push
await context.grantPermissions(['notifications'], {origin: 'https://notificare.com'})
// Load the page, wait for network calls to be done
await page.goto('https://notificare.com', {waitUntil: 'networkidle'});
// Check if the cookie accept button is there, and click it
const cookieButton = await page.$('div.cookie-manager.open > div > div > form > button')
if (cookieButton) {
await cookieButton.click()
}
await page.waitForTimeout(1000);
// Check if the optin button is there, and click it
const optinButton = await page.$('div.notificare-push-banner > div > div.wrapper > div.buttons > button.button.btn.btn-outline-light')
if (optinButton) {
await optinButton.click()
await page.waitForTimeout(1000)
// Save the settings if modal is shown
const preferenceButton = await page.$('div.fade.modal.show > div > div > div.modal-footer > button')
if (preferenceButton) {
await preferenceButton.click()
}
}
await page.waitForTimeout(1000);
const storage = await context.storageState();
const originEntry = storage.origins.find(entry => entry.origin === 'https://notificare.com')
const notificareDevice = originEntry.localStorage.find(entry => entry.name === 'notificareDevice')
if (notificareDevice && notificareDevice.value) {
const device = JSON.parse(notificareDevice.value);
console.log(device.deviceID);
}
// Wait for 60 seconds, in the meantime we can send a notification
// This could be automated with a Custom Event automation in Notificare Dashboard
// For now we just send it manually from the Dashboard by copying the deviceID
// that is logged to console above
await page.waitForTimeout(60000);
await context.close();
})();
This will log the following in the console:
Browser console message :Notificare: Overriding config.json
Browser console message :Notificare: Registering new Service Worker
https://fcm.googleapis.com/fcm/send/c21srxGmJGA:APA91bFiYecJn1IBDYlxU5tmJQsfLeqJ0Zw
Browser console message :didReceiveNotification 5ff7a3acac8712eexxxxxxxx
How do we use it?
This technology is used to generate the preview images of HTML notifications in Notificare's Dashboard Message Composer. We also use it to test our Web Push SDK in the different browsers.
If you liked this article or have something to add, we are available, as always, via our Support Channel.