Automated Browser Tests

Joris Verbogt
Jan 8 2021
Posted in Engineering & Technology

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:

screenshot

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.

Keep up-to-date with the latest news