<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tim Nolet 👨🏻‍🚀</title>
    <description>The latest articles on DEV Community by Tim Nolet 👨🏻‍🚀 (@tim_nolet).</description>
    <link>https://dev.to/tim_nolet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F52996%2Ff44b0cfb-959a-4137-9a81-41133fd60cd5.jpg</url>
      <title>DEV Community: Tim Nolet 👨🏻‍🚀</title>
      <link>https://dev.to/tim_nolet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tim_nolet"/>
    <language>en</language>
    <item>
      <title>Avoiding hard waits in Playwright and Puppeteer</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Thu, 16 Dec 2021 15:39:13 +0000</pubDate>
      <link>https://dev.to/checkly/avoiding-hard-waits-in-playwright-and-puppeteer-272</link>
      <guid>https://dev.to/checkly/avoiding-hard-waits-in-playwright-and-puppeteer-272</guid>
      <description>&lt;p&gt;Looking to solve the issue of a page or element not being loaded, many take the shortcut of waiting for a fixed amount of time - adding a hard wait, in other words. This is regarded as an anti-pattern, as it lowers performance and increases the chances of a script breaking (possibly intermittently). Let's explore how those issues arise and what better solutions we can use to avoid them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems with hard waits
&lt;/h2&gt;

&lt;p&gt;Hard waits do one thing and one thing only: wait for the specified amount of time. There is nothing more to them. This makes them dangerous: they are intuitive enough to be favoured by beginners and inflexible enough to create serious issues.&lt;/p&gt;

&lt;p&gt;Let's explore these issues in practical terms through an example. Imagine the following situation: our script is running using a tool without any sort of built-in smart waiting, and we need to wait until an element appears on a page and then attempt to click it. We try to solve this issue with a hard wait, like Puppeteer's &lt;code&gt;page.waitFor(timeout)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This could looks something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// hard wait for 1000ms&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#button-login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In such a situation, the following can happen:&lt;/p&gt;

&lt;p&gt;1) We can end up waiting for a shorter amount of time than the element takes to load!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffi57q4vvjvprw6oedoo4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffi57q4vvjvprw6oedoo4.png" alt="playwright hard wait time too short" width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, our hard wait terminates and our click action is attempted too early. The script terminates with an error, possibly of the &lt;a href="https://checklyhq.com/learn/headless/error-element-not-found" rel="noopener noreferrer"&gt;"Element not found"&lt;/a&gt; sort.&lt;/p&gt;

&lt;p&gt;2) The element can load before our hard wait has expired.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksb6vzfmx9nzut98zzaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksb6vzfmx9nzut98zzaw.png" alt="playwright hard wait time too long" width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the element is correctly clicked once our wait expires, and our script continues executing as planned, we are wasting precious time - likely on each hard wait we perform. Across multiple scripts and suites, this can add up to noticeable drag on build time.&lt;/p&gt;

&lt;p&gt;In general, with hard waits we are virtually always waiting too little or too long. In the worst case scenario, the fluctuations in load time between different script executions are enough to make the wait sometimes too long and sometimes too short (meaning we will switch between scenario 1 and 2 from above in an unpredictable manner), making our script fail intermittently. That will result in unpredictable, seemingly random failures, also known as flakiness.&lt;/p&gt;

&lt;p&gt;Flakiness, a higher-than-acceptable false failure rate, can be a major problem. It is essentially a source of noise, making it harder to understand what the state of the system we are testing or monitoring really is. Not only that, but stakeholders who routinely need to investigate failures only to find out that they are script-related (instead of system-related) will rapidly lose confidence in an automation setup. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to fix it
&lt;/h2&gt;

&lt;p&gt;To avoid these issues, we have to ditch hard waits completely outside debugging scenarios. That means that &lt;strong&gt;hard waits should never appear in production scripts under any circumstance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Our aim should be to wait just long enough for the element to appear. We want to always be certain the element is available, and never waste any time doing that. Luckily most automation tools and frameworks today offer multiple ways to achieve this. We can call these "smart waits".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1yfm2fodxvzhn0p7fvu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1yfm2fodxvzhn0p7fvu.png" alt="playwright smart wait" width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Different tools approach the broad topic of waiting in different ways. Both Puppeteer and Playwright offer many different kinds of smart waits, but Playwright takes things one step further and introduces an auto-waiting mechanism on most page interactions. &lt;/p&gt;

&lt;p&gt;Let's take a look at different smart waiting techniques and how they are used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in waits
&lt;/h2&gt;

&lt;p&gt;Playwright comes with built-in waiting mechanisms on &lt;a href="https://playwright.dev/docs/navigations" rel="noopener noreferrer"&gt;navigation&lt;/a&gt; and &lt;a href="https://playwright.dev/docs/actionability" rel="noopener noreferrer"&gt;page interactions&lt;/a&gt;. Since these are baked into the tool itself, it is good to get familiar with the logic behind them, as well as how to override the default behaviour when necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explicit waits
&lt;/h2&gt;

&lt;p&gt;Explicit waits are a type of smart wait we invoke explicitly as part of our script. We will want to use them more or less often depending on whether our automation tool has a built-in waiting mechanism (e.g. Playwright) or requires us to handle all the waiting (e.g. Puppeteer).&lt;/p&gt;

&lt;p&gt;If you can rely on automatic waits, use explicit waits only when necessary. An auto-wait system failing once is no good reason for ditching the approach completely and adding explicit waits before every page load and element interaction. If the tool you are using does not do auto-waiting, you will be using explicit waits quite heavily (possibly after each navigation and before each element interaction), and that is fine - there is just less work being done behind the scenes, and you are therefore expected to take more control into your hands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Waiting on navigations and network conditions
&lt;/h3&gt;

&lt;p&gt;On a page load, we can use the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-navigation" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForNavigation&lt;/code&gt;&lt;/a&gt; to wait until a page navigation (new URL or page reload) has completed.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-load-state" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForLoadState&lt;/code&gt;&lt;/a&gt; for Playwright, waits until the required load state has been reached (defaults to &lt;code&gt;load&lt;/code&gt;); &lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;show=api-pagewaitfornetworkidleoptions" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForNetworkIdle&lt;/code&gt;&lt;/a&gt; with Puppeteer, a narrower method to wait until all network calls have ended.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-url" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForURL&lt;/code&gt;&lt;/a&gt; with Playwright, waits until a navigation to the target URL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All the above default to waiting for the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event" rel="noopener noreferrer"&gt;load&lt;/a&gt; event, but can also be set to wait for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event" rel="noopener noreferrer"&gt;&lt;code&gt;DOMContentLoaded&lt;/code&gt;&lt;/a&gt; event.&lt;/li&gt;
&lt;li&gt;Playwright only: &lt;code&gt;networkidle&lt;/code&gt;, raised when there are no network connections for at least 500 ms.&lt;/li&gt;
&lt;li&gt;Playwright only: &lt;code&gt;commit&lt;/code&gt;, when the network response is received and the document starts loading (Playwright only).&lt;/li&gt;
&lt;li&gt;Puppeteer only: &lt;code&gt;networkidle0&lt;/code&gt;, raised when there are no network connections for at least 500 ms.&lt;/li&gt;
&lt;li&gt;Puppeteer only: &lt;code&gt;networkidle2&lt;/code&gt;, raise when the there are no more than 2 network connections for at least 500 ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading" rel="noopener noreferrer"&gt;Lazy-loaded pages&lt;/a&gt; might require extra attention when waiting for the content to load, often demanding explicitly waiting for specific UI elements. See the following section.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Additionally, we can also wait until a specific request is sent out or a specific response is received with &lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-request" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForRequest&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-response" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForResponse&lt;/code&gt;&lt;/a&gt;. These two methods are key for implementing &lt;a href="https://checklyhq.com/learn/headless/request-interception" rel="noopener noreferrer"&gt;request and response interception&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Waiting for an element
&lt;/h3&gt;

&lt;p&gt;We can also explicitly wait for a specific element to appear on the page. This is normally done via &lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-selector" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForSelector&lt;/code&gt;&lt;/a&gt; or a similar method, like &lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;show=api-pagewaitforxpathxpath-options" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForXPath&lt;/code&gt;&lt;/a&gt; (Puppeteer only). A good knowledge of &lt;a href="https://checklyhq.com/learn/headless/basics-selectors" rel="noopener noreferrer"&gt;selectors&lt;/a&gt; is key to enable us to select precisely the element we need to wait for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Waiting on page events
&lt;/h3&gt;

&lt;p&gt;With Playwright, we can also directly wait on &lt;a href="https://playwright.dev/docs/events" rel="noopener noreferrer"&gt;page events&lt;/a&gt; using &lt;a href="href="&gt;&lt;code&gt;page.waitForEvent&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Waiting on page functions
&lt;/h3&gt;

&lt;p&gt;For more advanced cases, we can pass a function to be evaluated within the browser context via &lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-function" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForFunction&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Never use hard waits outside of debugging&lt;/li&gt;
&lt;li&gt;Use smart waits instead, choosing the best one for your situation&lt;/li&gt;
&lt;li&gt;Use more or less smart waits depending on whether your tool support auto-waits&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Banner image: detail from &lt;a href="https://www.flickr.com/photos/49461427@N06/17023731035" rel="noopener noreferrer"&gt;"IMG_0952"&lt;/a&gt; by &lt;a href="https://www.flickr.com/photos/49461427@N06" rel="noopener noreferrer"&gt;sean_emmett&lt;/a&gt; is licensed under CC BY-NC-SA 2.0&lt;/p&gt;

</description>
      <category>puppeteer</category>
      <category>playwright</category>
      <category>testing</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Migrating from Puppeteer to Playwright</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Tue, 16 Nov 2021 17:13:26 +0000</pubDate>
      <link>https://dev.to/checkly/migrating-from-puppeteer-to-playwright-4j31</link>
      <guid>https://dev.to/checkly/migrating-from-puppeteer-to-playwright-4j31</guid>
      <description>&lt;p&gt;&lt;em&gt;This article originally appeared in &lt;a href="https://www.checklyhq.com/guides/puppeteer-to-playwright/" rel="noopener noreferrer"&gt;Checkly's Guides&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Puppeteer and Playwright today
&lt;/h2&gt;

&lt;p&gt;While they share a number of similarities, &lt;a href="https://pptr.dev" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; and &lt;a href="https://playwright.dev" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; have evolved at different speeds over the last two years, with Playwright gaining a lot of momentum and arguably even leaving Puppeteer behind.&lt;/p&gt;

&lt;p&gt;These developments have led many to switch from Puppeteer to Playwright. This guide aims to show what practical steps are necessary and what new possibilities this transition enables. Do not let the length of this article discourage you - in most cases the migration is quick and painless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why switch
&lt;/h3&gt;

&lt;p&gt;While a comprehensive comparison of each tool's strengths and weaknesses could fill up a guide of its own (see our &lt;a href="https://blog.checklyhq.com/cypress-vs-selenium-vs-playwright-vs-puppeteer-speed-comparison/" rel="noopener noreferrer"&gt;previous benchmarks&lt;/a&gt; for an example), the case for migrating to Playwright today is rather straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;As of the writing of this guide, Playwright has been frequently and consistently adding game changing features (see below for a partial list) for many months, with Puppeteer releasing in turn mostly smaller changes and bug fixes. This led to a reversal of the feature gap that had once separated the two tools.&lt;/li&gt;
&lt;li&gt;Playwright maintains an edge in performance in real-world E2E scenarios (see benchmark linked above), resulting in lower execution times for test suites and faster monitoring checks.&lt;/li&gt;
&lt;li&gt;Playwright scripts seem to run even more stable than their already reliable Puppeteer counterparts.&lt;/li&gt;
&lt;li&gt;The Playwright community on &lt;a href="https://github.com/microsoft/playwright/discussions" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://twitter.com/playwrightweb" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://aka.ms/playwright-slack" rel="noopener noreferrer"&gt;Slack&lt;/a&gt; and beyond has gotten very vibrant, while Puppeteer's has gone more and more quiet. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What to change in your scripts - short version
&lt;/h2&gt;

&lt;p&gt;Below you can find a cheat sheet with Puppeteer commands and the corresponding evolution in Playwright. Keep reading for a longer, more in-depth explanation of each change.&lt;/p&gt;

&lt;p&gt;Remember to add &lt;code&gt;await&lt;/code&gt; as necessary.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Puppeteer&lt;/th&gt;
&lt;th&gt;Playwright&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;require('puppeteer')&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;require('playwright')&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;puppeteer.launch(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;playwright.chromium.launch(...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;browser.createIncognitoBrowserContext(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;browser.newContext(...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.setViewport(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;page.setViewportSize(...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;page.waitForSelector(selector)&lt;/code&gt; &lt;code&gt;page.click(selector);&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;page.click(selector)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.waitForXPath(XPathSelector)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;page.waitForSelector(XPathSelector)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.$x(xpath_selector)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;page.$(xpath_selector)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.waitForNetworkIdle(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;page.waitForLoadState({ state: 'networkidle' }})&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.waitForFileChooser(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removed, &lt;a href="https://playwright.dev/docs/input/#upload-files" rel="noopener noreferrer"&gt;handled differently&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.waitFor(timeout)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;page.waitForTimeout(timeout)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.type(selector, text)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;page.fill(selector, text)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.cookies([...urls])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;browserContext.cookies([urls])&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.deleteCookie(...cookies)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;browserContext.clearCookies()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.setCookie(...cookies)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;browserContext.addCookies(cookies)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.on('request', ...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Handled through &lt;a href="https://playwright.dev/docs/api/class-page#page-route" rel="noopener noreferrer"&gt;page.route&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;elementHandle.uploadFile(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;elementHandle.setInputFiles(...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tricky file download.&lt;/td&gt;
&lt;td&gt;Better &lt;a href="https://playwright.dev/docs/downloads" rel="noopener noreferrer"&gt;support for downloads&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Did we forget anything? Please let us know by getting in touch, or &lt;a href="https://github.com/checkly/checklyhq.com" rel="noopener noreferrer"&gt;submit your own PR&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What to change in your scripts - in depth
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Require Playwright package
&lt;/h3&gt;

&lt;p&gt;In Puppeteer, the first few lines of your script would have most likely looked close to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;puppeteer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Playwright you do not need to change much:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright offers cross-browser support out of the box, and you can choose which browser to run with just by changing the first line, e.g. to &lt;code&gt;const { webkit } = require('playwright');&lt;/code&gt;&lt;br&gt;
In Puppeteer, this would have been done throught the browser's launch options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firefox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The browser context
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://playwright.dev/docs/api/class-browsercontext" rel="noopener noreferrer"&gt;Browser contexts&lt;/a&gt; already existed in Puppeteer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIncognitoBrowserContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright's API puts even more importance on them, and handles them a little differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like in Puppeteer, for basic cases and single-page flows, you can use the default context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;When in doubt, explicitly create a new context at the beginning of your script.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Waiting
&lt;/h3&gt;

&lt;p&gt;The auto-waiting mechanism in Playwright means you will likely not need to care about explicitly waiting as often. Still, waiting being one of the trickiest bits of UI automation, you will still want to know different ways of having your script explicitly wait for one or more conditions to be met.&lt;/p&gt;

&lt;p&gt;In this area, Playwright brings about several changes you want to be mindful of: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-navigation" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForNavigation&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-selector" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForSelector&lt;/code&gt;&lt;/a&gt; remain, but in many cases will not be necessary due to auto-waiting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-event" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForEvent&lt;/code&gt;&lt;/a&gt; has been added.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Puppeteer's &lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v11.0.0&amp;amp;show=api-pagewaitforxpathxpath-options" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForXPath&lt;/code&gt;&lt;/a&gt; has been incorporated into &lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-selector" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForSelector&lt;/code&gt;&lt;/a&gt;, which recognises XPath expressions automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v11.0.0&amp;amp;show=api-pagewaitforfilechooseroptions" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForFileChooser&lt;/code&gt;&lt;/a&gt; been removed removed (see the &lt;a href="https://playwright.dev/docs/input#upload-files" rel="noopener noreferrer"&gt;official dedicated page&lt;/a&gt; and our &lt;a href="https://www.checklyhq.com/learn/headless/e2e-account-settings/" rel="noopener noreferrer"&gt;file upload example&lt;/a&gt; for new usage)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v11.0.0&amp;amp;show=api-pagewaitfornetworkidleoptions" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForNetworkIdle&lt;/code&gt;&lt;/a&gt; has been generalised into &lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-load-state" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForLoadState&lt;/code&gt;&lt;/a&gt; (see the &lt;code&gt;networkidle&lt;/code&gt; state to recreate previous behaviour)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://playwright.dev/docs/api/class-page#page-wait-for-url" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForUrl&lt;/code&gt;&lt;/a&gt; has been added allowing you to wait until a URL has been loaded by the page's main frame.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v11.0.0&amp;amp;show=api-pagewaitforselectororfunctionortimeout-options-args" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitFor(timeout)&lt;/code&gt;&lt;/a&gt; becomes &lt;a href="https://playwright.dev/docs/api/class-frame#frame-wait-for-timeout" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForTimeout(timeout)&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;This is as good a place as any to remind that &lt;code&gt;page.waitForTimeout&lt;/code&gt; should never be used in production scripts! Hard waits/sleeps should be used only for debugging purposes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setting viewport
&lt;/h3&gt;

&lt;p&gt;Puppeteer's &lt;code&gt;page.setViewport&lt;/code&gt; becomes &lt;code&gt;page.setViewportSize&lt;/code&gt; in Playwright.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typing
&lt;/h3&gt;

&lt;p&gt;While puppeteer's &lt;a href="https://playwright.dev/docs/api/class-page#page-type" rel="noopener noreferrer"&gt;&lt;code&gt;page.type&lt;/code&gt;&lt;/a&gt; is available in Playwright and still handles fine-grained keyboard events, Playwright adds &lt;a href="https://playwright.dev/docs/api/class-page#page-fill" rel="noopener noreferrer"&gt;&lt;code&gt;page.fill&lt;/code&gt;&lt;/a&gt; specifically for filling and clearing forms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cookies
&lt;/h3&gt;

&lt;p&gt;With Puppeteer cookies are handled at the page level; with Playwright you manipulate them at the BrowserContext level. &lt;/p&gt;

&lt;p&gt;The old...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v11.0.0&amp;amp;show=api-pagecookiesurls" rel="noopener noreferrer"&gt;&lt;code&gt;page.cookies([...urls])&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v11.0.0&amp;amp;show=api-pagedeletecookiecookies" rel="noopener noreferrer"&gt;&lt;code&gt;page.deleteCookie(...cookies)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v11.0.0&amp;amp;show=api-pagesetcookiecookies" rel="noopener noreferrer"&gt;&lt;code&gt;page.setCookie(...cookies)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;...become:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://playwright.dev/docs/api/class-browsercontext#browser-context-cookies" rel="noopener noreferrer"&gt;&lt;code&gt;browserContext.cookies([urls])&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://playwright.dev/docs/api/class-browsercontext#browser-context-clear-cookies" rel="noopener noreferrer"&gt;&lt;code&gt;browserContext.clearCookies()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://playwright.dev/docs/api/class-browsercontext#browser-context-add-cookies" rel="noopener noreferrer"&gt;&lt;code&gt;browserContext.addCookies(cookies)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note the slight differences in the methods and how the cookies are passed to them.&lt;/p&gt;

&lt;h3&gt;
  
  
  XPath selectors
&lt;/h3&gt;

&lt;p&gt;XPath selectors starting with &lt;code&gt;//&lt;/code&gt; or &lt;code&gt;..&lt;/code&gt; are automatically recognised by Playwright, whereas Puppeteer had dedicated methods for them. That means you can use e.g. &lt;code&gt;page.$(xpath_selector)&lt;/code&gt; instead of &lt;code&gt;page.$x(xpath_selector)&lt;/code&gt;, and &lt;code&gt;page.waitForSelector(xpath_selector)&lt;/code&gt; instead of &lt;code&gt;page.waitForXPath(xpath_selector)&lt;/code&gt;. The same holds true for &lt;code&gt;page.click&lt;/code&gt; and &lt;code&gt;page.fill&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Device emulation
&lt;/h3&gt;

&lt;p&gt;Playwright &lt;a href="https://playwright.dev/docs/emulation" rel="noopener noreferrer"&gt;device emulation settings&lt;/a&gt; are set at Browser Context level, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pixel2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pixel 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pixel2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On top of that, permission, geolocation and other device parameters are also available for you to control.&lt;/p&gt;

&lt;h3&gt;
  
  
  File download
&lt;/h3&gt;

&lt;p&gt;Trying to download files in Puppeteer in headless mode can be tricky. Playwright makes this more streamlined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;download&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#orders &amp;gt; ul &amp;gt; li:nth-child(1) &amp;gt; a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See our &lt;a href="https://checklyhq.com/learn/headless/e2e-file-download/" rel="noopener noreferrer"&gt;example on file download&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  File upload
&lt;/h3&gt;

&lt;p&gt;Puppeteer's &lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v11.0.0&amp;amp;show=api-elementhandleuploadfilefilepaths" rel="noopener noreferrer"&gt;&lt;code&gt;elementHandle.uploadFile&lt;/code&gt;&lt;/a&gt; becomes &lt;a href="https://playwright.dev/docs/api/class-elementhandle#element-handle-set-input-files" rel="noopener noreferrer"&gt;&lt;code&gt;elementHandle.setInputFiles&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;See our &lt;a href="https://checklyhq.com/learn/headless/e2e-account-settings/" rel="noopener noreferrer"&gt;example on file upload&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request interception
&lt;/h3&gt;

&lt;p&gt;Request interception in Puppeteer is handled via &lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v11.0.0&amp;amp;show=api-event-request" rel="noopener noreferrer"&gt;&lt;code&gt;page.on('request', ...)&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequestInterception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resourceType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Playwright, &lt;a href="https://playwright.dev/docs/api/class-page#page-route" rel="noopener noreferrer"&gt;&lt;code&gt;page.route&lt;/code&gt;&lt;/a&gt; can be used to intercept requests with a URL matching a specific pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;resourceType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See our &lt;a href="https://checklyhq.com/learn/headless/request-interception/" rel="noopener noreferrer"&gt;full guide&lt;/a&gt; on request interception for more examples.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For many of the points in the list above, variations of the same function exist at &lt;a href="https://playwright.dev/docs/api/class-page/" rel="noopener noreferrer"&gt;&lt;code&gt;Page&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://playwright.dev/docs/api/class-frame/" rel="noopener noreferrer"&gt;&lt;code&gt;Frame&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://playwright.dev/docs/api/class-elementhandle/" rel="noopener noreferrer"&gt;&lt;code&gt;ElementHandle&lt;/code&gt;&lt;/a&gt; level. For simplicity, we reported only one. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  New possibilities to be aware of
&lt;/h2&gt;

&lt;p&gt;When moving from Puppeteer to Playwright, make sure you inform yourself about the many completely new features Playwright introduces, as they might open up new solutions and possibilities for your testing or monitoring setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  New selector engines
&lt;/h3&gt;

&lt;p&gt;Playwright brings with it added flexibility when referencing UI elements via selectors by exposing &lt;a href="https://playwright.dev/docs/selectors" rel="noopener noreferrer"&gt;different selector engines&lt;/a&gt;. Aside from CSS and XPath, it adds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Playwright-specific selectors, e.g.: &lt;code&gt;:nth-match(:text("Buy"), 3)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Text selectors, e.g.: &lt;code&gt;text=Add to Cart&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Chained selectors, e.g.: &lt;code&gt;css=preview &amp;gt;&amp;gt; text=In stock&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can even create your own &lt;a href="https://playwright.dev/docs/extensibility#custom-selector-engines" rel="noopener noreferrer"&gt;custom selector engine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For more information on selectors and how to use them, see &lt;a href="https://checklyhq.com/learn/headless/basics-selectors/" rel="noopener noreferrer"&gt;our dedicated guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Saving and reusing state
&lt;/h3&gt;

&lt;p&gt;Playwright makes it easy for you to save the authenticated state (cookies and localStorage) of a given session and reuse it for subsequent script runs. &lt;/p&gt;

&lt;p&gt;Reusing state can &lt;a href="https://checklyhq.com/learn/headless/valuable-tests/#keep-tests-independent" rel="noopener noreferrer"&gt;save significant amounts of time&lt;/a&gt; on larger suites by skipping the pre-authentication phase in scripts where it is not supposed to be directly tested / monitored.&lt;/p&gt;

&lt;h3&gt;
  
  
  Locator API
&lt;/h3&gt;

&lt;p&gt;You might be interested in checking out Playwright's &lt;a href="https://playwright.dev/docs/api/class-locator" rel="noopener noreferrer"&gt;Locator API&lt;/a&gt;, which encapsulates the logic necessary to retrieve a given element, allowing you to easily retrieve an up-to-date DOM element at different points in time in your script.&lt;/p&gt;

&lt;p&gt;This is particularly helpful if you are structuring your setup according to the &lt;a href="https://martinfowler.com/bliki/PageObject.html" rel="noopener noreferrer"&gt;Page Object Model&lt;/a&gt;, or if you are interested to do start doing that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Playwright Inspector
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://playwright.dev/docs/inspector" rel="noopener noreferrer"&gt;Playwright Inspector&lt;/a&gt; is a GUI tool that comes in very handy when debugging scripts, allowing you to step instruction-by-instruction through your script to more esily identify the cause of a failure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ssqtmsbkr63pevqh3f9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ssqtmsbkr63pevqh3f9.png" alt="playwright inspector" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Inspector also comes in handy due its ability to suggest selectors for page elements and even record new scripts from scratch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Playwright Test
&lt;/h3&gt;

&lt;p&gt;Playwright comes with its own runner, &lt;a href="https://playwright.dev/docs/intro" rel="noopener noreferrer"&gt;Playwright Test&lt;/a&gt;, which adds useful features around end-to-end testing, like out-of-the-box parallelisation, test fixtures, hooks and more. &lt;/p&gt;

&lt;h3&gt;
  
  
  Trace Viewer
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://playwright.dev/docs/trace-viewer" rel="noopener noreferrer"&gt;Playwright Trace Viewer&lt;/a&gt; allows you to explore traces recorded using Playwright Test or the BrowserContext Tracing API. Traces are where you can get the most fine-grained insights into your script's execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbj82rte2k8klvona821v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbj82rte2k8klvona821v.png" alt="playwright trace inspection" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Generator
&lt;/h3&gt;

&lt;p&gt;You can use the &lt;a href="https://playwright.dev/docs/codegen" rel="noopener noreferrer"&gt;Playwright Test Generator&lt;/a&gt; to record interactions in your browser. The output will be a full-fledged script ready to review and execute.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fhrteu35ledx1anv1t7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fhrteu35ledx1anv1t7.png" alt="page being inspected with playwright codegen" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching to Playwright for richer browser check results
&lt;/h2&gt;

&lt;p&gt;Checkly users switching to Playwright can take advantage of its new Rich Browser Check Results, which come with &lt;a href="https://www.checklyhq.com/docs/browser-checks/tracing-web-vitals/" rel="noopener noreferrer"&gt;tracing and Web Vitals&lt;/a&gt; and make it easier to isolate the root cause of a failed check and remediate faster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl53vwbka3rw3d7olgq3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl53vwbka3rw3d7olgq3q.png" alt="performance and error tracing check results on checkly" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This reveals additional information about the check execution, including:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Overview of all errors raised (console, network and script errors)&lt;/li&gt;
&lt;li&gt;A timeline summarising the execution across page navigations&lt;/li&gt;
&lt;li&gt;For each page visited, a network &amp;amp; timing timeline, Web Vitals, console and network tabs.&lt;/li&gt;
&lt;li&gt;In case of a failing check, a screenshot on failure.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Aside from running a Playwright script, performance and error tracing also require the use of &lt;a href="https://www.checklyhq.com/docs/runtimes/" rel="noopener noreferrer"&gt;Runtime&lt;/a&gt; &lt;code&gt;2021.06&lt;/code&gt; or newer.&lt;/p&gt;

&lt;p&gt;Note that cross-browser support is not available on Checkly - &lt;a href="https://www.checklyhq.com/docs/browser-checks/" rel="noopener noreferrer"&gt;our Browser checks run on Chromium&lt;/a&gt; only.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;banner image: &lt;a href="https://www.flickr.com/photos/92081855@N07/8369463912" rel="noopener noreferrer"&gt;"rocket engine"&lt;/a&gt; by &lt;a href="https://www.flickr.com/photos/92081855@N07" rel="noopener noreferrer"&gt;industrial arts&lt;/a&gt;, licensed under &lt;a href="https://creativecommons.org/licenses/by/2.0/?ref=ccsearch&amp;amp;atype=rich" rel="noopener noreferrer"&gt;CC BY 2.0&lt;/a&gt;&lt;/p&gt;

</description>
      <category>puppeteer</category>
      <category>testing</category>
      <category>monitoring</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Debugging Puppeteer &amp; Playwright scripts</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Tue, 10 Aug 2021 14:27:03 +0000</pubDate>
      <link>https://dev.to/checkly/debugging-puppeteer-playwright-scripts-10j4</link>
      <guid>https://dev.to/checkly/debugging-puppeteer-playwright-scripts-10j4</guid>
      <description>&lt;p&gt;&lt;em&gt;This article originally appeared on Checkly's &lt;a href="https://www.checklyhq.com/learn/headless/basics-debugging/" rel="noopener noreferrer"&gt;Learn Puppeteer &amp;amp; Playwright&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Understanding why a script does not work as expected, or just what the root cause of a failure is, is a key skill for automation. Given its importance and its sometimes deceptive complexity, debugging is a topic that should receive quite some attention. &lt;/p&gt;

&lt;p&gt;This article will explore basic concepts and tools to point beginners in the right direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Awareness comes first
&lt;/h2&gt;

&lt;p&gt;Script debugging is firstly about observing and understanding. Finding out what is causing the failure (or misbehaviour) in your execution heavily depends on your knowledge of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What the script you are looking at is &lt;em&gt;supposed&lt;/em&gt; to do&lt;/li&gt;
&lt;li&gt;How the application the script is running against is supposed to behave at each step of the script&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When approaching a debugging session, make sure the above points are taken care of. Skipping this step is way more likely to cost you additional time than it is to save you any. &lt;/p&gt;

&lt;h2&gt;
  
  
  The error message
&lt;/h2&gt;

&lt;p&gt;Error messages are not present in every scenario: we might be trying to understand why a script &lt;em&gt;passes&lt;/em&gt;, or why it takes longer than expected. But when we have access to an error message, we can use it to guide us.&lt;/p&gt;

&lt;p&gt;The error, in and of its own, is not always enough to understand what is going wrong with your script. Oftentimes, there can be multiple degrees of separation between the error and its root cause. For example: an &lt;a href="https://checklyhq.com/learn/headless/error-element-not-found" rel="noopener noreferrer"&gt;"Element not found"&lt;/a&gt; error might be alerting you to the fact that an element is not being found on the page, but that itself might be because the browser was made to load the wrong URL in the first place.&lt;/p&gt;

&lt;p&gt;Do not fall into the easy trap of reading the error message and immediately jumping to conclusions. Rather, take the error message, research it if needed, combine it with your knowledge of script and app under test and treat it as the first piece to the puzzle, rather than the point of arrival of your investigation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Good knowledge of the automation tool you are using will also help add more context to the error message itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Gaining visibility
&lt;/h2&gt;

&lt;p&gt;Given that Headless browser scripts will run without a GUI, a visual assessment of the state of the application needs additional steps. &lt;/p&gt;

&lt;p&gt;One possibility is to adding screenshots in specific parts of the script, to validate our assumptions on what might be happening at a given moment of the execution. For example, before and after a problematic click or page transition:&lt;/p&gt;

&lt;p&gt;For Playwright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;before_click.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;after_click.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Puppeteer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;before_click.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;after_click.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another way to better observe our script's execution is to run in headful mode:&lt;/p&gt;

&lt;p&gt;For Playwright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;slowMo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Puppeteer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;slowMo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then tweak the &lt;code&gt;slowMo&lt;/code&gt; option, which adds a delay in milliseconds between interactions, to make sure the execution is not too fast for us to follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Increasing logging
&lt;/h2&gt;

&lt;p&gt;Sometimes we need to try and see the execution through our automation tool's eyes. Added logging can help by taking us step-by-step through every command as it is executed.&lt;/p&gt;

&lt;p&gt;For Playwright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pw:api node script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Puppeteer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"puppeteer:*"&lt;/span&gt; node script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8iq6xu6mvmn8rrtfjqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8iq6xu6mvmn8rrtfjqo.png" alt="verbose playwright logs" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessing DevTools
&lt;/h2&gt;

&lt;p&gt;A wealth of information is available through the Chrome Developer Tools. We can configure our browser to start with the DevTools tab already open (this will automatically disable headless mode), which can be helpful when something is not working as expected. Careful inspection of the Console, Network and other tabs can reveal hidden errors and other important findings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7rewmq4smwr9qwii55b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7rewmq4smwr9qwii55b.png" alt="debugging with chrome devtools" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Playwright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;devtools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Puppeteer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;devtools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also use the console to directly try out a selector on the page in its current state, e.g. with &lt;code&gt;document.querySelector&lt;/code&gt; or &lt;code&gt;document.querySelectorAll&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c4jtnw60pfb6s27uf52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c4jtnw60pfb6s27uf52.png" alt="debugging selectors in browser console" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we are using Playwright, we can also run in debug mode with &lt;code&gt;PWDEBUG=console node script.js&lt;/code&gt;. This provisions a &lt;code&gt;playwright&lt;/code&gt; object in the browser which allows us to also try out &lt;a href="https://playwright.dev/docs/selectors" rel="noopener noreferrer"&gt;Playwright-specific selectors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nsjrbqxwvmfoj7ehwpl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nsjrbqxwvmfoj7ehwpl.png" alt="debugging playwright-specific selectors in browser console" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Playwright Inspector
&lt;/h2&gt;

&lt;p&gt;The Playwright Inspector is a GUI tool which exposes additional debugging functionality, and can be launched using &lt;code&gt;PWDEBUG=1 npm run test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Inspector allows us to easily step through each instruction of our script, while giving us clear information on the duration, outcome, and functioning of each. This can be helpful in &lt;a href="https://checklyhq.com/learn/headless/debugging-challenges" rel="noopener noreferrer"&gt;getting to the root cause&lt;/a&gt; of some of the more generic errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5vdwy0t2r0qaasy1qlj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5vdwy0t2r0qaasy1qlj.png" alt="playwright inspector debugging" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Inspector includes additional handy features such as selector generation and debugging, as well as script recording.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://checklyhq.com/learn/headless/debugging-challenges" rel="noopener noreferrer"&gt;Debugging challenges&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://checklyhq.com/learn/headless/basics-selectors" rel="noopener noreferrer"&gt;Working with selectors&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;cover image:&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/65541944@N07/13866761735" rel="noopener noreferrer"&gt;"Manual descent simulation in the centrifuge"&lt;/a&gt; &lt;em&gt;by&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/65541944@N07" rel="noopener noreferrer"&gt;AstroSamantha&lt;/a&gt; &lt;em&gt;is licensed under&lt;/em&gt; &lt;a href="https://creativecommons.org/licenses/by/2.0/?ref=ccsearch&amp;amp;atype=rich" rel="noopener noreferrer"&gt;CC BY 2.0&lt;/a&gt;&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>puppeteer</category>
      <category>playwright</category>
      <category>javascript</category>
    </item>
    <item>
      <title>OpenAPI/Swagger Monitoring</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Wed, 28 Apr 2021 14:04:24 +0000</pubDate>
      <link>https://dev.to/checkly/openapi-swagger-monitoring-4lo6</link>
      <guid>https://dev.to/checkly/openapi-swagger-monitoring-4lo6</guid>
      <description>&lt;p&gt;OpenAPI and Swagger help users design and document APIs in a way that is readable from both humans and machines. As a consequence, they can also be used to generate the code that will run the specified API - both on the provider and consumer side. Can we leverage this same principle to simplify API monitoring? After a brief first look at OpenAPI and Swagger, this article will show how we can quickly use them to monitor a new or existing API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The OpenAPI Specification
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://spec.openapis.org/oas/v3.1.0" rel="noopener noreferrer"&gt;OpenAPI Specification (OAS)&lt;/a&gt; specifies a standard, language-agnostic and machine-readable format to describe a web API in one or more files. Nowadays, it acts as a vendor-neutral standard for describing the structure and behaviour of HTTP-based APIs, and exists as part of the &lt;a href="https://www.openapis.org/" rel="noopener noreferrer"&gt;OpenAPI Initiative (OAI)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Following the OAS brings the following advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A description of the API that is readable from both humans and machines.&lt;/li&gt;
&lt;li&gt;The possibility of automatically generating documentation, implementation logic and even mock servers for testing.&lt;/li&gt;
&lt;li&gt;Early validation of the data flows happening within the API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For those new to the OAS and wanting to get a basic understanding without diving straight into the specification itself, the official &lt;a href="https://oai.github.io/Documentation/specification.html" rel="noopener noreferrer"&gt;OpenAPI Specification Explained&lt;/a&gt; guide is a great place to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swagger vs OpenAPI
&lt;/h2&gt;

&lt;p&gt;The OpenAPI Specification is based on the SmartBear SoftwareSmartBear Software, which had previously been a project driven by &lt;a href="https://smartbear.com" rel="noopener noreferrer"&gt;SmartBear Software&lt;/a&gt;. SmartBear donated the Swagger Specification to the Linux Foundation in 2015. In a sense, the OAS is the newer, fully open source-driven incarnation of the Swagger Specification.&lt;/p&gt;

&lt;p&gt;The name &lt;a href="https://swagger.io" rel="noopener noreferrer"&gt;Swagger&lt;/a&gt; today indicates a set of tools, both free and paid, that support users of the OpenAPI ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring the specification
&lt;/h2&gt;

&lt;p&gt;The core of our API specification will happen in the OpenAPI root document, normally named &lt;code&gt;openapi.json&lt;/code&gt; or &lt;code&gt;openapi.yml&lt;/code&gt;. We can choose between JSON and YAML formats as we like.&lt;/p&gt;

&lt;p&gt;The bare minimum we need to declare is the OpenAPI version, the basic information about our API, and finally the available endpoints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.0&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenAPI document for our new Payment API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.1&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;# Nothing here yet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An API without endpoints is not very useful. We can add our first, together with a &lt;code&gt;GET&lt;/code&gt; operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.0&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenAPI document for our new Payment API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.2&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# User account balance&lt;/span&gt;
  &lt;span class="s"&gt;/balance/{id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Retrieve the user's balance on the given account&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to define what the operation will look like. In this case, we will specify some constraints on the &lt;code&gt;id&lt;/code&gt; parameter, as well as the response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.0&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenAPI document for our new Payment API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.3&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# User account balance&lt;/span&gt;
  &lt;span class="s"&gt;/balance/{id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Retrieve the user's balance on the given account&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get the user's account balance&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retrieves the current account balance for the given user.&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get-balance&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
        &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uuid&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK"&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/balance"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we are explicitly defining which schemas the parameter and response should conform to. In the case of our response, we are referring to an &lt;br&gt;
&lt;a href="https://swagger.io/docs/specification/using-ref" rel="noopener noreferrer"&gt;externally defined schema&lt;/a&gt;. This is helpful when we need to reuse a schema definition multiple times across our spec file.&lt;/p&gt;

&lt;p&gt;As we proceed, we might add multiple endpoints, each with one or more operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.0&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenAPI document for our new Payment API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.4&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# User account balance&lt;/span&gt;
  &lt;span class="s"&gt;/balance/{id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Retrieve the user's balance on the given account&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get the user's account balance&lt;/span&gt;
      &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;/transactions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retrieve transaction&lt;/span&gt;
      &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Submit transaction&lt;/span&gt;
      &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can keep on building out our API spec as needed. The more precisely we describe our API in this file, the more useful this specification will become, whether we use it for documentation or code generation purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating documentation from OpenAPI
&lt;/h2&gt;

&lt;p&gt;Let's now take a look at a more complex, finished example: the &lt;a href="https://petstore.swagger.io/v2/swagger.json" rel="noopener noreferrer"&gt;Swagger PetStore demo&lt;/a&gt;. Even if we had written it ourselves, this description of a complete API could be overwhelming if we just tried to parse the file line by line. To first-time users, that could feel even more daunting. A better option would be to use the open-source toolset Swagger offers to generate human-readable, interactive API documentation from the file we already have.&lt;/p&gt;

&lt;p&gt;Pasting the content of the spec file to &lt;a href="https://editor.swagger.io" rel="noopener noreferrer"&gt;Swagger Editor&lt;/a&gt; will produce a preview of our &lt;a href="https://swagger.io/tools/swagger-ui" rel="noopener noreferrer"&gt;Swagger UI documentation&lt;/a&gt;. This is helpful for consumers of the API, who will be presented with an orderly documentation page breaking down each endpoint while also allowing users to test out different operations. &lt;/p&gt;

&lt;h2&gt;
  
  
  Generating boilerplate code from OpenAPI
&lt;/h2&gt;

&lt;p&gt;When writing a new API, we will oftentimes need to produce a certain amount of boilerplate code for both our provider (e.g. a server exposing our API for consumption) and consumer (e.g. an SDK for our API) applications. Once we have our OpenAPI file in place, we can use &lt;a href="https://swagger.io/tools/swagger-codegen" rel="noopener noreferrer"&gt;Swagger Codegen&lt;/a&gt; (also available within Swagger Editor) to automatically generate that code for us, saving us precious time and reducing the avenues for human error.&lt;/p&gt;

&lt;p&gt;The following snippet is an example of the code Swagger Codegen can generate starting from an OAS file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
   * Invokes the REST service using the supplied settings and parameters.
   * @param {String} path The base URL to invoke.
   * @param {String} httpMethod The HTTP method to use.
   * @param {Object.&amp;lt;String, String&amp;gt;} pathParams A map of path parameters and their values.
   * @param {Object.&amp;lt;String, Object&amp;gt;} queryParams A map of query parameters and their values.
   * @param {Object.&amp;lt;String, Object&amp;gt;} collectionQueryParams A map of collection query parameters and their values.
   * @param {Object.&amp;lt;String, Object&amp;gt;} headerParams A map of header parameters and their values.
   * @param {Object.&amp;lt;String, Object&amp;gt;} formParams A map of form parameters and their values.
   * @param {Object} bodyParam The value to pass as the request body.
   * @param {Array.&amp;lt;String&amp;gt;} authNames An array of authentication type names.
   * @param {Array.&amp;lt;String&amp;gt;} contentTypes An array of request MIME types.
   * @param {Array.&amp;lt;String&amp;gt;} accepts An array of acceptable response MIME types.
   * @param {(String|Array|ObjectFunction)} returnType The required type to return; can be a string for simple types or the
   * constructor for a complex type.
   * @param {module:ApiClient~callApiCallback} callback The callback function.
   * @returns {Object} The SuperAgent request object.
   */&lt;/span&gt;
  &lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;callApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;httpMethod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pathParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;collectionQueryParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headerParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bodyParam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authNames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accepts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;returnType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_this&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pathParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;superagent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;httpMethod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// apply authentications&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyAuthToRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authNames&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// set collection query parameters&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;collectionQueryParams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collectionQueryParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;collectionQueryParams&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionFormat&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;csv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// SuperAgent normally percent-encodes all reserved characters in a query parameter. However,&lt;/span&gt;
          &lt;span class="c1"&gt;// commas are used as delimiters for the 'csv' collectionFormat so they must not be encoded. We&lt;/span&gt;
          &lt;span class="c1"&gt;// must therefore construct and encode 'csv' collection query parameters manually.&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paramToString&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// All other collection query parameters should be treated as ordinary query parameters.&lt;/span&gt;
          &lt;span class="nx"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildCollectionParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionFormat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// set query parameters&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;httpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalizeParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on how much is described in a given API spec, it is possible to move even further and generate tests, mock servers and more. See &lt;a href="https://openapi.tools" rel="noopener noreferrer"&gt;openapi.tools&lt;/a&gt; for an up-to-date list of tools belonging to the OpenAPI ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generated monitoring checks with Checkly
&lt;/h2&gt;

&lt;p&gt;API monitoring is key to ensure that our endpoints are returning the correct results in an acceptable timeframe.  If we can generate APIs from a OAS description file, we can also generate monitoring checks for them. Checkly is &lt;a href="https://www.openapis.org/membership/members" rel="noopener noreferrer"&gt;a member of the OpenAPI initiative&lt;/a&gt;, and allows us to import an OpenAPI spec file and automatically creates one or more checks depending on the amount of information available. All it takes is a couple of clicks.&lt;/p&gt;

&lt;p&gt;When creating an API check, select the &lt;code&gt;import from Swagger / OpenAPI&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6y6atm4pxcz4cdbvymtk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6y6atm4pxcz4cdbvymtk.png" alt="screenshot of checkly check api creation openapi import button" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That will bring up the import dialog. This is where we give Checkly the URL to the Swagger/OpenAPI specification file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23mhjiunu6prx3z6s0pr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23mhjiunu6prx3z6s0pr.png" alt="screenshot of checkly check api creation openapi import dialog" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before anything is created on our behalf, Checkly will show us a summary of all the checks that will be imported. We can choose to import them all defined checks or just a subset.&lt;/p&gt;

&lt;p&gt;Notice that we can also select whether to import headers, query params and other settings or just to ignore them for now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjeufn9qsandy1rrhka56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjeufn9qsandy1rrhka56.png" alt="screenshot of checkly bulk openapi import" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we confirm, the checks will be created for us and will start running according to the schedule we have selected.&lt;/p&gt;

&lt;p&gt;Based on how comprehensively the file we imported described the API we want to monitor, we might have to tweak certain checks manually before they are fully set up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqm8g953eqhhksoxi3txc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqm8g953eqhhksoxi3txc.png" alt="screenshot of checkly checks - with failures" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This usually includes adding missing headers or body contents that might not have been explicitly defined in the imported file.&lt;/p&gt;

&lt;p&gt;We can open the newly created checks and change any of their settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ltmr9buwed2un7mautd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ltmr9buwed2un7mautd.png" alt="screenshot of checkly filled in check body" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a small amount of tinkering, or none at all, we should have all our checks up and running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5n7y7d8bv2fchyu25dq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5n7y7d8bv2fchyu25dq.png" alt="screenshot of checkly checks - all passing" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For APIs medium and large, and for any that is already described with an OAS file, this check-generating procedure can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save us a large amount of time otherwise spent in manual check setup.&lt;/li&gt;
&lt;li&gt;Reduce the chance for human error in check configuration.&lt;/li&gt;
&lt;li&gt;Help us ensure the right checks are set up according to our single source of truth.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://www.checklyhq.com/guides/api-monitoring/#api-monitoring-best-practices" rel="noopener noreferrer"&gt;Higher check coverage&lt;/a&gt; can ultimately give us higher confidence that our API is working as expected, and basing our monitoring on our OpenAPI specification enables us to get to higher coverage faster - without losing flexibility when building our checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building on our generated monitors
&lt;/h2&gt;

&lt;p&gt;As we look to increase our endpoint coverage across our APIs, we might want to explore the following resources:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learning more about &lt;a href="https://www.checklyhq.com/guides/api-monitoring" rel="noopener noreferrer"&gt;API monitoring&lt;/a&gt; basics and best practices.&lt;/li&gt;
&lt;li&gt;Defining our &lt;a href="https://www.checklyhq.com/guides/monitoring-as-code" rel="noopener noreferrer"&gt;API checks as code&lt;/a&gt; to scale our setup.&lt;/li&gt;
&lt;li&gt;Integrating our API monitoring with &lt;a href="https://www.checklyhq.com/guides/end-to-end-monitoring" rel="noopener noreferrer"&gt;E2E monitoring&lt;/a&gt; for our websites.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>api</category>
      <category>monitoring</category>
      <category>openapi</category>
    </item>
    <item>
      <title>API Monitoring for the JAMStack</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Fri, 09 Apr 2021 13:51:51 +0000</pubDate>
      <link>https://dev.to/checkly/api-monitoring-for-the-jamstack-l57</link>
      <guid>https://dev.to/checkly/api-monitoring-for-the-jamstack-l57</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.checklyhq.com/guides/api-monitoring" rel="noopener noreferrer"&gt;https://www.checklyhq.com/guides/api-monitoring&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Application Programming Interfaces (APIs) are used throughout software to define interactions between different software applications. In this article we focus on web APIs specifically, taking a look at how they fit in the JAMStack architecture and how we can set up API monitoring in order to make sure they don't break and respond fast.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftdtzrkdwtncslrqkhus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftdtzrkdwtncslrqkhus.png" alt="jamstack architecture diagram" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  APIs and the JAMStack
&lt;/h2&gt;

&lt;p&gt;With the rise of the &lt;a href="https://jamstack.org/" rel="noopener noreferrer"&gt;JAMStack&lt;/a&gt;, the already broadly used web APIs have been brought further into the spotlight and explicitly named as cornerstone of a new way of building web applications. In the JAMStack paradigm, applications rely on APIs (the &lt;em&gt;A&lt;/em&gt; in "JAM") returning structured data (JSON or XML) when queried via the HTML and Javascript-based frontend. &lt;/p&gt;

&lt;p&gt;The API calls might be aimed at internal services or at third-parties handling complex flows such as content management, authentication, merchant services and more. An example of third-party API could be &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;, which acts as payment infrastructure for a multitude of businesses.&lt;/p&gt;

&lt;p&gt;Given their importance in this new kind of web application, APIs both internal and external need to be tightly monitored, as failures and performance degradations will immediately be felt by the end-user.&lt;/p&gt;

&lt;h2&gt;
  
  
  API failures
&lt;/h2&gt;

&lt;p&gt;API endpoints can break in a variety of ways. The most obvious examples are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The endpoint is unresponsive/unreachable.&lt;/li&gt;
&lt;li&gt;The response is incorrect.&lt;/li&gt;
&lt;li&gt;The response time is too high.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of the above can result in the application becoming broken for the end-user. This applies to internal APIs and, especially in the case of JAMStack applications, to third parties as well. API checks allow us to monitor both by mimicking the end-user's own behaviour.&lt;/p&gt;

&lt;h2&gt;
  
  
  API checks
&lt;/h2&gt;

&lt;p&gt;If we were interested in just verifying a server or a virtual machine's availability, we could rely on a simple ping/uptime monitoring solution. API monitoring is more fine-grained than that though, as we need to validate functionality and performance on each API endpoint. API checks do exactly that, and they are composed of the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An HTTP request.&lt;/li&gt;
&lt;li&gt;One or more assertions, used to specify exactly what the response should look like, and fail the check if the criteria are not met.&lt;/li&gt;
&lt;li&gt;A threshold indicating the maximum acceptable response time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The more customisable the HTTP request is, the more cases can be covered, for example with authentication, headers and payloads. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is worth noting that in real-world scenarios, requests do not happen in a vacuum: they are often handling data retrieved previously, possibly by earlier API calls. Therefore, some mechanism to gather this data and inject it into the request is often needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's dive in deeper into each point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurable HTTP request
&lt;/h3&gt;

&lt;p&gt;There is a large variety of valid requests that a user might make to a given endpoint. Being able to customise all aspects of our test request is therefore fundamental. Key aspects are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods" rel="noopener noreferrer"&gt;Method&lt;/a&gt;, like &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, etc&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" rel="noopener noreferrer"&gt;Headers&lt;/a&gt;, like &lt;code&gt;Accept&lt;/code&gt;, &lt;code&gt;Authorization&lt;/code&gt;, &lt;code&gt;Content-Type&lt;/code&gt;, &lt;code&gt;Cookie&lt;/code&gt;, &lt;code&gt;User-Agent&lt;/code&gt;, etc&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Identifying_resources_on_the_Web#query" rel="noopener noreferrer"&gt;Query parameters&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdv8tsr1tteit68vpixbo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdv8tsr1tteit68vpixbo.png" alt="swagger api documentation screenshot" width="762" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Essentially, we are trying to craft a complete request for exact endpoint. Not only that, but we might want to have multiple requests set up to cover specific options or negative cases, too.&lt;/p&gt;

&lt;p&gt;One such case can be where user-specified parameters such as pagination and timeframes might largely change the response. This is exemplified by the &lt;code&gt;List Customers&lt;/code&gt; method in &lt;a href="https://stripe.com/docs/api/customers/list?lang=curl" rel="noopener noreferrer"&gt;Stripe's Customer API&lt;/a&gt;, which we can use to query elements in very different ways, such as by just specifying a limit of results or asking for all results linked to a specific creation date. In this case, both of the following cases are worth monitoring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.stripe.com/v1/customers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; sk_test_4eC39HqLyjWDarjtT1zdp7dc: &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.stripe.com/v1/customers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; sk_test_4eC39HqLyjWDarjtT1zdp7dc: &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1616519668 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we chose to set up a call using Javascript, for example, we could achieve the same call as in the first case above using &lt;a href="https://github.com/axios/axios" rel="noopener noreferrer"&gt;axios&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AUTH_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.stripe.com/v1/customers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;AUTH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit=3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Assertions
&lt;/h3&gt;

&lt;p&gt;To validate the API response, we should be able to check against&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Status code&lt;/li&gt;
&lt;li&gt;Headers&lt;/li&gt;
&lt;li&gt;Body&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's look at an example: creating a customer via the &lt;a href="https://stripe.com/docs/api/customers/create?lang=curl" rel="noopener noreferrer"&gt;Stripe Customer API&lt;/a&gt;. Since we are not the API's developers, we are assuming the result we get running call right now is correct and can be used to model our assertions. Let's run the following curl command in verbose mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-v&lt;/span&gt; https://api.stripe.com/v1/customers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; sk_test_4eC39HqLyjWDarjtT1zdp7dc: &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"My First Test Customer (created for API docs)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within the lengthy output we find the respose (in curl denoted by the '&amp;lt;' symbol), and in it all the important details we need for our assertions.&lt;/p&gt;

&lt;p&gt;First, we notice the successful status code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt; HTTP/2 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, we can see the headers, which we might want to check for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt; content-type: application/json
&amp;lt; content-length: 1190
&amp;lt; access-control-allow-credentials: true
&amp;lt; access-control-allow-methods: GET, POST, HEAD, OPTIONS, DELETE
&amp;lt; access-control-allow-origin: *
&amp;lt; access-control-expose-headers: Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required
&amp;lt; access-control-max-age: 300
&amp;lt; cache-control: no-cache, no-store
&amp;lt; request-id: req_S9P5NqvZXzvvS0
&amp;lt; stripe-version: 2019-02-19
&amp;lt; x-stripe-c-cost: 0
&amp;lt; strict-transport-security: max-age=31556926; includeSubDomains; preload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally the response body, which we might want to inspect to ensure the right data is being sent back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ 
  "id": "cus_JAp37QquOLWbRs",
  "object": "customer",
  "account_balance": 0,
  "address": null,
  "balance": 0,
  "created": 1616579618,
  [clipped]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could expand on our previous code example by add adding an assertion library, such as &lt;a href="https://www.chaijs.com/api/assert/" rel="noopener noreferrer"&gt;chai's&lt;/a&gt; or &lt;a href="https://jestjs.io/docs/expect" rel="noopener noreferrer"&gt;Jest expect&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AUTH_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.stripe.com/v1/customers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;AUTH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit=3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 1) assert again status code &lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 2) assert against header&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;has_more&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 3) assert against body&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are now asserting against all three point mentioned above. We could of course go on and add additional assertions against both headers and body.&lt;/p&gt;

&lt;h3&gt;
  
  
  Response time thresholds
&lt;/h3&gt;

&lt;p&gt;Having an endpoint return the correct result is only half the battle. It is imperative that the response reaches the user quickly enough not to disrupt any dependent workflow. In the worst case, where the response time exceeds what the end user is prepared to wait, a performance failure is undistinguishable from a functional one.&lt;/p&gt;

&lt;p&gt;The easiest way to handle this requirement would be to assert that the specific response time be lower than a certain value, or even just set a timeout for our axios request by adding the &lt;code&gt;timeout: 7500&lt;/code&gt; property in the previously shown request config.&lt;/p&gt;

&lt;p&gt;Instead of simply asserting against a specific response, we might want to set different thresholds: based on the nature of our service, a 2x slowdown might still leave it in what we define as an operational state, while a 10x one might not.&lt;/p&gt;

&lt;h2&gt;
  
  
  API monitoring best practices
&lt;/h2&gt;

&lt;p&gt;Now that we are clear on the key requirements for setting up API checks, let's think about what and how we should monitor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor every endpoint
&lt;/h3&gt;

&lt;p&gt;We want to monitor every API endpoint our application exposes. Remember that different HTTP methods define different API endpoints. For example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;GET /user/:id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PUT /user/:id&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The above count as two separate endpoints, even though the URL is the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cover key API parameters
&lt;/h3&gt;

&lt;p&gt;Some parameters can change the endpoint's response significantly. We shoud strive to have separate checks verifying that the endpoint is behaving correctly across different configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep checks focused &amp;amp; independent
&lt;/h3&gt;

&lt;p&gt;API monitoring checks must be organised as to minimise the time needed to identify resolve the underlying issue. This means we need to keep our checks focused on a specific case (vs trying to have a single check do many things) and independent from each other (vs building chains of checks that build on top of one another).&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduled global API checks
&lt;/h2&gt;

&lt;p&gt;Checkly specialises in API monitoring and allows users to run API checks on a schedule from &lt;a href="https://www.checklyhq.com/docs/monitoring/global-locations/" rel="noopener noreferrer"&gt;global locations&lt;/a&gt;. We can combine these checks with &lt;a href="https://www.checklyhq.com/docs/alerting/" rel="noopener noreferrer"&gt;custom alerting&lt;/a&gt; to be able to quickly respond and remediate potential API issues. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l04paiealxlon2oflr3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l04paiealxlon2oflr3.png" alt="checkly check dashboard" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A Checkly API check is comprised of the following components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Main HTTP request
&lt;/h3&gt;

&lt;p&gt;The most basic building block of Checkly's API check is the main HTTP request. This can be fully configured in its method, url, parameters and body to fully reproduce a real-world web API call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffprfwvw6hkypzy3mka8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffprfwvw6hkypzy3mka8g.png" alt="checkly check creation process" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Assertions
&lt;/h3&gt;

&lt;p&gt;Assertions allow us to check for every key aspect of the response. A check with one or more failing assertions will enter failing state and trigger any connected alert channel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmmqdydecyuotpf8gn4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmmqdydecyuotpf8gn4n.png" alt="setting up assertions in checkly" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, we are checking against:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The status code, expected to be &lt;code&gt;200&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The id of one of the customers returned as part of the response's JSON body. Here we could assert a specific value, but in this case we are happy with just verifying that the field is not empty.&lt;/li&gt;
&lt;li&gt;The value of the &lt;code&gt;Content-Encoding&lt;/code&gt; header, expected to equal &lt;code&gt;gzip&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Response time limits
&lt;/h3&gt;

&lt;p&gt;Response time limits enable us to set different thresholds to decide exactly which response time maps to a hard failure, a pass or a degradation. We can use transitions between these states to trigger different kinds of alerts using our preferred channels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8r8d4w0cbyv4e2rgya09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8r8d4w0cbyv4e2rgya09.png" alt="setting up response time limits in an api check" width="800" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup and teardown scripts
&lt;/h3&gt;

&lt;p&gt;Checkly is highly programmable and allows users to run scripts before and after the main HTTP request of an API check.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frglfhavdvecaimxxzu3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frglfhavdvecaimxxzu3y.png" alt="programmable setup scripts" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setup scripts run before our check and give us access to properties like the URL, headers and query parameters, enabling us to set up all the prerequisites for a successful request. Some examples could be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetching a token from a different API endpoint.&lt;/li&gt;
&lt;li&gt;Setting up test data on the target system.&lt;/li&gt;
&lt;li&gt;Formatting data to be sent as part of the request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Teardown scripts run after the request has executed, bur right before the assertions. They are useful for manipulating the response (for example to remove sensitive information) or removing any test data on the target system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving our monitoring
&lt;/h2&gt;

&lt;p&gt;As we increase our monitoring coverage across our APIs, we can also increase the efficacy of our setup by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Importing existing &lt;a href="https://www.checklyhq.com/docs/api-checks/request-settings/#import-a-swagger--openapi-specification" rel="noopener noreferrer"&gt;Swagger/OpenAPI specs&lt;/a&gt; or even &lt;a href="https://www.checklyhq.com/docs/api-checks/request-settings/#import-a-curl-request" rel="noopener noreferrer"&gt;cURL commands&lt;/a&gt; using built-in functionality.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.checklyhq.com/guides/monitoring-as-code" rel="noopener noreferrer"&gt;Defining our API checks as code&lt;/a&gt; to scale our setup while lowering maintenance needs.&lt;/li&gt;
&lt;li&gt;Combining our API checks with &lt;a href="https://www.checklyhq.com/guides/e2e-monitoring" rel="noopener noreferrer"&gt;E2E monitoring&lt;/a&gt; for any website or web app service whose API we might be monitoring.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Banner image:&lt;/em&gt; “rover 200 framing line” &lt;em&gt;by&lt;/em&gt; spencer_cooper &lt;em&gt;is licensed under&lt;/em&gt; CC BY-ND 2.0&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monitoring</category>
      <category>api</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>End to end monitoring</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Mon, 29 Mar 2021 18:20:13 +0000</pubDate>
      <link>https://dev.to/checkly/end-to-end-monitoring-20bh</link>
      <guid>https://dev.to/checkly/end-to-end-monitoring-20bh</guid>
      <description>&lt;p&gt;&lt;em&gt;This article originally appeared in &lt;a href="https://www.checklyhq.com/guides/end-to-end-monitoring" rel="noopener noreferrer"&gt;Checkly's Guides&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;End-to-end monitoring uses headless browser automation tools like Puppeteer and Playwright to continuously test your website's key user flows. This article summarises the most important points on this topic and gets you up and running in 10 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless browser testing
&lt;/h2&gt;

&lt;p&gt;Over the course of the last decade, especially thanks to tools such as &lt;a href="https://www.selenium.dev/" rel="noopener noreferrer"&gt;Selenium&lt;/a&gt; and (more recently) &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt;, &lt;strong&gt;automated End-to-End testing (E2E testing) has become widespread across industries&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Broadly speaking, &lt;strong&gt;E2E testing entails running fully automated test suites with the goal of catching bugs before they hit production&lt;/strong&gt; and, therefore, negatively affect the user experience. These test suites need to be carefully scripted using dedicated tools, as well as to be made stable and fast enough to test the most important end-user flows on every build, PR or commit, depending on the application under test and the organisation's automation maturity.&lt;/p&gt;

&lt;p&gt;The industry has learned to struggle with the challenges this approach presents: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Long-running suites.&lt;/li&gt;
&lt;li&gt;Test flakiness.&lt;/li&gt;
&lt;li&gt;Expensive test infrastructure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;All of the above lead to higher costs and slower delivery.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The appearance of mature &lt;strong&gt;headless browser automation tools, such as &lt;a href="https://pptr.dev" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; and &lt;a href="https://playwright.dev" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;, offers a response&lt;/strong&gt; to many of the above issues by allowing testing in the browser without its GUI, which yields higher speed and stability coupled with lower resource consumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  E2E monitoring (AKA synthetic monitoring)
&lt;/h2&gt;

&lt;p&gt;While this nimbler, more reliable kind of test is already a big improvement for pre-production testing, it enables a completely new approach in production monitoring: we can now &lt;strong&gt;continuously run E2E tests against our production systems&lt;/strong&gt;. This enables us to have real-time feedback on the status of our website's key user flows from a user's perspective. This is E2E monitoring, also known as &lt;em&gt;synthetic monitoring&lt;/em&gt; or &lt;em&gt;active monitoring&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This comes with a significant, often underestimated advantage: it allows us to &lt;strong&gt;catch all those things that might break in production that can't be caught during pre-production testing&lt;/strong&gt;. We are now running directly against the system that the end-user is actually interacting with, and will be able to monitor its behaviour in real time.&lt;/p&gt;

&lt;p&gt;What could this look like in practice? Let's look at an e-commerce example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring a web shop
&lt;/h2&gt;

&lt;p&gt;A few key flows for an e-commerce websites could be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Logging in&lt;/li&gt;
&lt;li&gt;Finding a product through search&lt;/li&gt;
&lt;li&gt;Adding products to the basket and checking out&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's see how to set them up - for this example, we will do that on our &lt;a href="https://danube-webshop.herokuapp.com" rel="noopener noreferrer"&gt;demo web shop&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa12kanm2s3dsa2pnoq4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa12kanm2s3dsa2pnoq4m.png" alt="demo web shop screenshot" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Playwright E2E tests
&lt;/h3&gt;

&lt;p&gt;Using Playwright, we can script our three E2E scenarios as follows. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Login scenario:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// launch the browser and open a new page&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// navigate to our target web page&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://danube-webshop.herokuapp.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// click on the login button and go through the login procedure&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#n-email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user@email.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#n-password2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supersecure1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#goto-signin-btn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// wait until the login confirmation message is shown&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#login-message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// close the browser and terminate the session&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Search scenario:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// launch the browser and open a new page&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bookList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The Foreigner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The Transformation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;For Whom the Ball Tells&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Baiting for Robot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// navigate to our target web page&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://danube-webshop.herokuapp.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// search for keyword&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.topbar &amp;gt; input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.topbar &amp;gt; input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;for&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#button-search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.shop-content &amp;gt; ul &amp;gt; .preview:nth-child(1) &amp;gt; .preview-title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// halt immediately if results do not equal expected number&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;resultsNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.preview-title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bookList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// remove every element found from the original array...&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;resultsNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`.preview:nth-child(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) &amp;gt; .preview-title`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bookList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultTitle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;bookList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...then assert that the original array is now empty&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// close the browser and terminate the session&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Checkout scenario:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// launch the browser and open a new page&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navigationPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// navigate to our target web page&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://danube-webshop.herokuapp.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// add the first item to the cart&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`.preview:nth-child(1) &amp;gt; .preview-author`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.detail-wrapper &amp;gt; .call-to-action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// wait until navigation is complete&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;navigationPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// navigate to cart and proceed&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#cart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.cart &amp;gt; .call-to-action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// fill out checkout info&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Max&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-surname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mustermann&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Charlottenstr. 57&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-zipcode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10117&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Berlin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Firma GmbH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.checkout &amp;gt; form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#asap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// confirm checkout&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.checkout &amp;gt; .call-to-action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// wait until the order confirmation message is shown&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#order-confirmation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// close the browser and terminate the session&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These can be run on our own machine without issues with &lt;a href="https://playwright.dev/docs/intro" rel="noopener noreferrer"&gt;very little preparation&lt;/a&gt; with a simple &lt;code&gt;node script.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring application performance
&lt;/h2&gt;

&lt;p&gt;A web application's performance plays a primary role in the user experience it delivers. From the user's perspective, a fully functional application that is not performant quickly becomes indistinguishable from a broken one.&lt;/p&gt;

&lt;p&gt;Using Playwright together with browser APIs or additional performance libraries, our end-to-end monitoring setup can be easily extended to include application performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring execution time
&lt;/h3&gt;

&lt;p&gt;An effective and granular way to gauge performance is to measure how long our scenario takes to execute. A very simple way to achieve this is to just time our script's execution with &lt;code&gt;time node script.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Oftentimes it pays to be more granular. For example, we might want to measure the durations of certain segments of a given flow and assert against them. We can do all this in our script. For example, in the case of our longer checkout example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// we add an assertion library&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// launch the browser and open a new page&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navigationPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// get first timestamp&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tFirstNavigationStarts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// navigate to our target web page&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://danube-webshop.herokuapp.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// get second timestamp&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tFirstNavigationEnds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// add the first item to the cart&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.preview:nth-child(1) &amp;gt; .preview-author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.detail-wrapper &amp;gt; .call-to-action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="c1"&gt;// wait until the order confirmation message is shown&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#order-confirmation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// get thirds timestamp&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tScenarioEnds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// calculate timings&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dNavigation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tFirstNavigationEnds&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;tFirstNavigationStarts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dScenario&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tScenarioEnds&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;tFirstNavigationStarts&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// assert against the timings we have measured&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isBelow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dNavigation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1750&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Initial navigation took longer than 1.75s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isBelow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dScenario&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Total scenario took longer than 3s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// close the browser and terminate the session&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also use Web Performance APIs such as &lt;a href="https://www.w3.org/TR/navigation-timing/" rel="noopener noreferrer"&gt;Navigation Timing&lt;/a&gt; and &lt;a href="https://www.w3.org/TR/resource-timing-1/" rel="noopener noreferrer"&gt;Resource Timing&lt;/a&gt;, as well as libraries such as Google Lighthouse. For more examples, see our &lt;a href="https://www.checklyhq.com/learn/headless/basics-performance/" rel="noopener noreferrer"&gt;dedicated performance guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  End to end application monitoring
&lt;/h2&gt;

&lt;p&gt;Unlike headful tools, headless ones tend to not be very resource-hungry, which makes it easier to move our scripts to the cloud. Checkly runs on top of AWS Lambda, and enables us to quickly copy-paste our script and set it up to run on a schedule from locations around the world.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi8uob0qabdaev8r9g8us.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi8uob0qabdaev8r9g8us.png" alt="check creation on checkly screenshot" width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can move our scripts to separate checks to keep them &lt;a href="https://dev.to/learn/headless/valuable-tests/#keep-tests-independent"&gt;independent&lt;/a&gt; - we want to optimise for parallelisation and clarity of feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4663j9ou8y8zfyf8zibm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4663j9ou8y8zfyf8zibm.png" alt="checkly dashboard screenshot" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As soon as a check runs red, we are alerted in real time and can &lt;strong&gt;intervene before the issue impacts our users&lt;/strong&gt;. Alerting can be set up with all the industry-standard channels like Pagerduty, Opsgenie, Slack, email, SMS and more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7tii30e5dg6d9eur5v1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7tii30e5dg6d9eur5v1.png" alt="alert channels on checkly" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  On-demand checking
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Active monitoring and event-triggered testing do not exclude one another.&lt;/strong&gt; You might want to have checks kicked off every time you deploy to production, or on every merge, PR or commit, or you might also want to run against your staging or development server. The choice has to be made based on your workflow and on your automation strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD
&lt;/h2&gt;

&lt;p&gt;Tests can be kicked off of CI pipelines. You might want to use different hooks (for e.g. smoke vs regression testing) in different stages and against different targets. &lt;a href="https://www.checklyhq.com/docs/cicd" rel="noopener noreferrer"&gt;Checkly supports all major CI servers&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Develop-preview-test
&lt;/h2&gt;

&lt;p&gt;If you are using provider like Vercel, you can automatically trigger your checks to run on deployed PRs, too, to reap the benefits of the &lt;a href="https://rauchg.com/2020/develop-preview-test" rel="noopener noreferrer"&gt;develop-preview-test approach&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls
&lt;/h2&gt;

&lt;p&gt;We learned things the hard way so you do not have to. When starting out, keep an eye out for the following pitfalls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Non-independent tests: tests that rely on one another in any way (e.g. order of execution, test data) are hard to parallelise, resulting in longer execution times and potentially higher flakiness. &lt;a href="https://dev.to/learn/headless/valuable-tests/#keep-tests-independent"&gt;Keep your tests independent&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Long, unfocused tests: checking too much in a single test will make failures harder to debug. &lt;a href="https://dev.to/learn/headless/valuable-tests/#keep-tests-focused"&gt;Break it up instead&lt;/a&gt;, and enjoy the added parallelisation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Messing up your own metrics, KPIs: remember, if you are not running against production, you want to make sure your E2E monitoring checks or tests are filtered out of your analytics. This is &lt;a href="https://www.checklyhq.com/docs/monitoring/whitelisting" rel="noopener noreferrer"&gt;rather easy to do&lt;/a&gt;, with most headless browser tools normally identifying themselves as such from the start.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Banner image: detail from &lt;a href="https://www.flickr.com/photos/30928442@N08/3771689308" rel="noopener noreferrer"&gt;"Outdoor Gas Installation"&lt;/a&gt; by &lt;a href="https://www.flickr.com/photos/30928442@N08" rel="noopener noreferrer"&gt;christian.senger&lt;/a&gt; is licensed under &lt;a href="https://creativecommons.org/licenses/by-sa/2.0/?ref=ccsearch&amp;amp;atype=rich" rel="noopener noreferrer"&gt;CC BY-SA 2.0&lt;/a&gt;&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>javascript</category>
      <category>playwright</category>
      <category>testing</category>
    </item>
    <item>
      <title>Monitoring-as-Code</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Tue, 16 Mar 2021 13:28:49 +0000</pubDate>
      <link>https://dev.to/checkly/monitoring-as-code-47bc</link>
      <guid>https://dev.to/checkly/monitoring-as-code-47bc</guid>
      <description>&lt;p&gt;&lt;em&gt;This article originally appeared in &lt;a href="https://www.checklyhq.com/guides/monitoring-as-code" rel="noopener noreferrer"&gt;Checkly's Guides&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The trend of declaring infrastructure as code has been picking up steam over the last few years, offering a way for DevOps teams to transparently manage and scale cloud infrastructure. Why should the way we manage monitoring be any different? In this article, we address this point and illustrate it with a practical example of Monitoring-as-Code on Checkly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure-as-Code
&lt;/h2&gt;

&lt;p&gt;Historically, IT infrastructure has been provisioned manually, both on premise and in the cloud. This presented several challenges, including fragmented workflows, lack of transparency and scalability issues. In response to these problems, the last few years have seen a shift to the Infrastructure-as-Code (IaC) paradigm, in which large-scale systems are declared in configuration files as code.&lt;/p&gt;

&lt;p&gt;A new generation of tools has emerged to serve this use case, the most notable example of which is &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;HashiCorp Terraform&lt;/a&gt;. Terraform provides a CLI workflow allowing users to specify what the final infrastructure setup should look like, and then takes care of all the intermediate steps needed to get there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-aws.98f0eb80.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-aws.98f0eb80.png" alt="provisioning aws infrastructure - code snippet" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform can be used to provision infrastructure on many different cloud vendors thanks to its provider ecosystem: each provider maps to the vendor's API, exposing different resources in a domain-specific language known as &lt;a href="https://www.terraform.io/docs/language/syntax/configuration.html" rel="noopener noreferrer"&gt;HCL&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring the IaC way
&lt;/h2&gt;

&lt;p&gt;Setting up monitoring can present some of the same issues as provisioning infrastructure. This becomes apparent when we move past the initial rollout or proof-of-concept and onboard multiple products and/or teams, and see our monitoring setup rapidly grow in scope - along with its maintenance needs, that is. &lt;/p&gt;

&lt;p&gt;Monitoring-as-Code learns from IaC and brings your monitoring config closer to your application and your development workflows. How? By having it also declared as code, much like you would do with any kind of IT infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Monitoring-as-Code
&lt;/h2&gt;

&lt;p&gt;What does one gain when moving from a manual to a Monitoring-as-Code approach? The &lt;strong&gt;main advantages&lt;/strong&gt; are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Better scalability through faster provisioning and easier maintenance.&lt;/li&gt;
&lt;li&gt;Better history and documentation: config files can be checked into source control.&lt;/li&gt;
&lt;li&gt;Shared monitoring setup visibility (and easier shared ownership) in DevOps teams.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Monitoring-as-Code with Checkly
&lt;/h2&gt;

&lt;p&gt;Users who have just started out will be familiar with creating checks, groups, alert channels and other resources through the Checkly UI. The official Terraform provider enables them to instead declare exactly what their active monitoring setup should look like, and have it provisioned by Terraform in just a few seconds - regardless of whether that means creating tens, hundreds or thousands of resources.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://registry.terraform.io/providers/checkly/checkly/latest" rel="noopener noreferrer"&gt;find the Checkly Terraform provider&lt;/a&gt; on the official Terraform registry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-provider.ebfdf552.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-provider.ebfdf552.png" alt="official Checkly Terraform provider on Terraform Registry" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring an e-commerce website - as code
&lt;/h2&gt;

&lt;p&gt;How does this all look like in practice? Let's find out by creating a small monitoring setup for our &lt;a href="https://danube-webshop.herokuapp.com" rel="noopener noreferrer"&gt;demo e-commerce website&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up our Terraform project
&lt;/h3&gt;

&lt;p&gt;For our example we will be creating browser checks using &lt;a href="https://playwright.dev" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; scripts we have previously written as part of our Playwright guides.&lt;/p&gt;

&lt;p&gt;First will be our login scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// launch the browser and open a new page&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// navigate to our target web page&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://danube-webshop.herokuapp.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// click on the login button and go through the login procedure&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#n-email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user@email.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#n-password2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supersecure1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#goto-signin-btn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// wait until the login confirmation message is shown&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#login-message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// close the browser and terminate the session&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...then our search scenario...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// launch the browser and open a new page&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bookList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The Foreigner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The Transformation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;For Whom the Ball Tells&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Baiting for Robot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// navigate to our target web page&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://danube-webshop.herokuapp.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// search for keyword&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.topbar &amp;gt; input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.topbar &amp;gt; input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;for&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#button-search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.shop-content &amp;gt; ul &amp;gt; .preview:nth-child(1) &amp;gt; .preview-title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// halt immediately if results do not equal expected number&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;resultsNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.preview-title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultsNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bookList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// remove every element found from the original array...&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;resultsNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`.preview:nth-child(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) &amp;gt; .preview-title`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bookList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultTitle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;bookList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...then assert that the original array is now empty&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// close the browser and terminate the session&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and finally our checkout scenario.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// launch the browser and open a new page&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navigationPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// navigate to our target web page&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://danube-webshop.herokuapp.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// add the first item to the cart&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`.preview:nth-child(1) &amp;gt; .preview-author`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.detail-wrapper &amp;gt; .call-to-action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// wait until navigation is complete&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;navigationPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// navigate to cart and proceed&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#cart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.cart &amp;gt; .call-to-action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// fill out checkout info&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Max&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-surname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mustermann&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Charlottenstr. 57&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-zipcode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10117&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Berlin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#s-company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Firma GmbH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.checkout &amp;gt; form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#asap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// confirm checkout&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.checkout &amp;gt; .call-to-action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// wait until the order confirmation message is shown&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#order-confirmation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// close the browser and terminate the session&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's start off by creating a brand new folder:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir checkly-terraform-example &amp;amp;&amp;amp; cd $_&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;To keep things easy, we create a subdirectory...&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir scripts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;...and copy all our scripts from above into separate files, for example &lt;code&gt;login.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next up, we want to create our &lt;code&gt;main.tf&lt;/code&gt; file and include the basic configuration as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"checkly_api_key"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;checkly&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkly/checkly"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.8.1"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"checkly"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;api_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkly_api_key&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are ready to initialise our project and have the Checkly Terraform provider set up for us. That is achieved by running:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;After a few seconds, you should see a similar message to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ragog@macpro learn-terraform % terraform init

Initializing the backend...

Initializing provider plugins...
- Finding checkly/checkly versions matching "0.8.1"...
- Installing checkly/checkly v0.8.1...
- Installed checkly/checkly v0.8.1 (signed by a HashiCorp partner, key ID 4E5AC4D95E185A57)

...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating our first browser checks
&lt;/h3&gt;

&lt;p&gt;In the same file, right below our initial instructions, we can now add resources one after the other. They will be browser checks based on the Playwright scripts we previously stored in the &lt;code&gt;scripts&lt;/code&gt; directory. Here is what each resource could look like:&lt;/p&gt;

&lt;p&gt;Login resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"checkly_check"&lt;/span&gt; &lt;span class="s2"&gt;"login"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Login E2E"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BROWSER"&lt;/span&gt;
  &lt;span class="nx"&gt;activated&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;should_fail&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;frequency&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;double_check&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ssl_check&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;use_global_alert_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;locations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"us-west-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"eu-central-1"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/scripts/login.js"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Search resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"checkly_check"&lt;/span&gt; &lt;span class="s2"&gt;"search"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Search E2E"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BROWSER"&lt;/span&gt;
  &lt;span class="nx"&gt;activated&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;should_fail&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;frequency&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
  &lt;span class="nx"&gt;double_check&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ssl_check&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;use_global_alert_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;locations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"us-west-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"eu-central-1"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/scripts/search.js"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checkout resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"checkly_check"&lt;/span&gt; &lt;span class="s2"&gt;"checkout"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Checkout E2E"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BROWSER"&lt;/span&gt;
  &lt;span class="nx"&gt;activated&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;should_fail&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;frequency&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
  &lt;span class="nx"&gt;double_check&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ssl_check&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;use_global_alert_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;locations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"us-west-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"eu-central-1"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/scripts/checkout.js"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that our Terraform project has been initialised and we have added some resources, we can generate a Terraform plan by running &lt;code&gt;terraform plan&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Terraform will determine all the needed changes to be performed to replicate our monitoring configuration on Checkly. In doing so, we will be asked for our Checkly API key, which we can find under our account settings as shown below. Not on Checkly yet? &lt;a href="https://app.checklyhq.com/signup" rel="noopener noreferrer"&gt;Register a free account&lt;/a&gt; and enjoy your free monthly checks!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-api.57d19365.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-api.57d19365.png" alt="screenshot of how to get api key from checkly dashboard" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can expose this as an environment variable in order to avoid having to copy-paste it all the time: &lt;code&gt;export TF_VAR_checkly_api_key=&amp;lt;YOUR_API_KEY&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ragog@macpro learn-terraform % terraform plan

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # checkly_check.checkout will be created
  + resource "checkly_check" "checkout" {
      + activated                 = true
      + degraded_response_time    = 15000
      + double_check              = true
      + frequency                 = 60
      + id                        = (known after apply)
      + locations                 = [
          + "eu-central-1",
          + "us-west-1",
        ]
      + max_response_time         = 30000
      + name                      = "Checkout E2E"
      + script                    = &amp;lt;&amp;lt;-EOT
            const { chromium } = require("playwright");

            ...

Plan: 3 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now finally apply our changes with &lt;code&gt;terraform apply&lt;/code&gt;. We might be asked for one final confirmation in the command prompt, after which we will be greeted by the following confirmation message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...

checkly_check.checkout: Creating...
checkly_check.login: Creating...
checkly_check.search: Creating...
checkly_check.checkout: Creation complete after 3s [id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]
checkly_check.login: Creation complete after 3s [id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]
checkly_check.search: Creation complete after 4s [id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logging in to our Checkly account, we will see the dashboard has been populated with our three checks, which will soon start executing on their set schedules.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-checks.021702d3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-checks.021702d3.png" alt="terraform-created checks on checkly dashboard" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring API correctness and performance
&lt;/h3&gt;

&lt;p&gt;Browser checks are now there to keep us informed on the status of our key website flows. What about our APIs, though? Whether they make up the foundation of our service or they are consumed directly by the customer, we need to ensure our endpoints are working as expected. This is easily achieved by setting up API check resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"checkly_check"&lt;/span&gt; &lt;span class="s2"&gt;"webstore-list-books"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"list-books"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"API"&lt;/span&gt;
  &lt;span class="nx"&gt;activated&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;should_fail&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;frequency&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;double_check&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ssl_check&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;use_global_alert_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;degraded_response_time&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
  &lt;span class="nx"&gt;max_response_time&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;

  &lt;span class="nx"&gt;locations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"eu-central-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"us-west-1"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://danube-webshop.herokuapp.com/api/books"&lt;/span&gt;
    &lt;span class="nx"&gt;follow_redirects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;assertion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"STATUS_CODE"&lt;/span&gt;
      &lt;span class="nx"&gt;comparison&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EQUALS"&lt;/span&gt;
      &lt;span class="nx"&gt;target&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;assertion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"JSON_BODY"&lt;/span&gt;
      &lt;span class="nx"&gt;property&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;.length"&lt;/span&gt;
      &lt;span class="nx"&gt;comparison&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EQUALS"&lt;/span&gt;
      &lt;span class="nx"&gt;target&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"30"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now once more run &lt;code&gt;terraform plan&lt;/code&gt;, followed by &lt;code&gt;terraform apply&lt;/code&gt; to see the new check on Checkly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-api-check.fb6125f4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-api-check.fb6125f4.png" alt="terraform-created api check on checkly" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Alerting
&lt;/h3&gt;

&lt;p&gt;Now that we have our checks in place, we want to set up alerting to ensure we are informed as soon as a failure takes place. Alert channels can be declared as resources, just like the checks. Let's add the following to our &lt;code&gt;main.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"checkly_alert_channel"&lt;/span&gt; &lt;span class="s2"&gt;"alert-email"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;YOUR_EMAIL_ADDRESS&amp;gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;send_recovery&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; 
  &lt;span class="nx"&gt;send_failure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;send_degraded&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are setting up things so that we are alerted when our check starts failing, as well as when it recovers. But we still need to decide which checks will subscribe to this channel, and therefore be able to trigger the alerts. This is done by adding the following inside the resource declaration of each check, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"checkly_check"&lt;/span&gt; &lt;span class="s2"&gt;"login"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Login E2E"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"BROWSER"&lt;/span&gt;
  &lt;span class="nx"&gt;activated&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;should_fail&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;frequency&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;double_check&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ssl_check&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;use_global_alert_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;locations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"us-west-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"eu-central-1"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;module}&lt;/span&gt;&lt;span class="s2"&gt;/scripts/login.js"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;alert_channel_subscription&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;channel_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;checkly_alert_channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alert-email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="nx"&gt;activated&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going through the usual &lt;code&gt;terraform plan&lt;/code&gt; and &lt;code&gt;terraform apply&lt;/code&gt; sequence will apply the changes on our Checkly account:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-alerts.61aa9bdb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.checklyhq.com%2Fguides%2Fimages%2Fguides-terraform-alerts.61aa9bdb.png" alt="terraform-created alert on checkly" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are now fully up and running with our monitoring-as-code setup. Our checks will run on a schedule, informing us promptly if anything were to go wrong. Rapidly getting to know about failures in our API and key website flows will allow us to react fast and mitigate impact on our users, ensuring a better experience with our product.&lt;/p&gt;

&lt;p&gt;You can find the complete setup described in this guide on our &lt;a href="https://github.com/checkly/guides-monitoring-as-code" rel="noopener noreferrer"&gt;dedicated repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expanding our setup
&lt;/h3&gt;

&lt;p&gt;As our setup expands, we might want to deploy additional tools to make our lives easier. We could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Iterate over existing Playwright scripts and &lt;a href="https://blog.checklyhq.com/scaling-puppeteer-playwright-on-checkly-with-terraform#iterating-through-scripts-for-shorter-config" rel="noopener noreferrer"&gt;create multiple checks while declaring only one resource&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.checklyhq.com/scaling-puppeteer-playwright-on-checkly-with-terraform#grouping-checks-together" rel="noopener noreferrer"&gt;Group checks together&lt;/a&gt; to better handle them in large numbers.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.checklyhq.com/scaling-puppeteer-playwright-on-checkly-with-terraform#reducing-code-duplication-with-snippets" rel="noopener noreferrer"&gt;Use code snippets&lt;/a&gt; to avoid code duplication and reduce maintenance.&lt;/li&gt;
&lt;li&gt;Move your workflow to &lt;a href="https://www.terraform.io/cloud" rel="noopener noreferrer"&gt;Terraform Cloud&lt;/a&gt; to easily collaborate with your team when managing your Monitoring-as-Code configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;banner image:&lt;/em&gt; "Electric Grid" by Duncan Rawlinson - Duncan.co is licensed under CC BY-NC 2.0&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>devops</category>
      <category>testing</category>
      <category>iac</category>
    </item>
    <item>
      <title>Generating PDFs (invoices, manuals and more) from web pages using Puppeteer/Playwright</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Wed, 16 Dec 2020 15:50:14 +0000</pubDate>
      <link>https://dev.to/checkly/generating-pdfs-invoices-manuals-and-more-from-web-pages-using-puppeteer-playwright-3l07</link>
      <guid>https://dev.to/checkly/generating-pdfs-invoices-manuals-and-more-from-web-pages-using-puppeteer-playwright-3l07</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://theheadless.dev" rel="noopener noreferrer"&gt;theheadless.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Puppeteer and Playwright can be used to create PDFs from webpages. This opens up interesting automation scenarios for tasks such as archiving, generating invoices, writing manuals, books and more.&lt;/p&gt;

&lt;p&gt;This article introduces this functionality and shows how we can customise the PDF to fit our needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating a PDF file
&lt;/h2&gt;

&lt;p&gt;After loading a page, we use the &lt;code&gt;page.pdf()&lt;/code&gt; command to convert it to a PDF.&lt;/p&gt;

&lt;p&gt;With Puppeteer:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;With Playwright:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Note that we need to pass the &lt;code&gt;path&lt;/code&gt; option to have the PDF file actually saved to disk.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WARNING:&lt;br&gt;
This feature is currently only supported in Chromium headless in both Puppeteer and Playwright.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tweaking the result
&lt;/h2&gt;

&lt;p&gt;It is important to take a quick look at the official docs for &lt;code&gt;page.pdf()&lt;/code&gt; (&lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v5.3.1&amp;amp;show=api-pagepdfoptions" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; or &lt;a href="https://playwright.dev/#version=v1.6.1&amp;amp;path=docs%2Fapi.md&amp;amp;q=pagepdfoptions" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;), as it is almost certain that we will want to tweak the appearance of our page in the resulting PDF.&lt;/p&gt;

&lt;p&gt;In certain cases, our webpage might look significantly different in our PDF compared to our browser. Depending on the case, it can pay off to experiment with the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We might need to set option &lt;code&gt;printBackground&lt;/code&gt; to true in case graphical components appear to be missing in the generated PDF.&lt;/li&gt;
&lt;li&gt;By default, &lt;code&gt;page.pdf()&lt;/code&gt; will generate a PDF with adjusted colors for printing. Setting the CSS property &lt;code&gt;-webkit-print-color-adjust: exact&lt;/code&gt; will force rendering of the original colors.&lt;/li&gt;
&lt;li&gt;Calling &lt;code&gt;page.emulateMedia('screen')&lt;/code&gt; changes the CSS media type of the page.&lt;/li&gt;
&lt;li&gt;Setting either &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; or &lt;code&gt;format&lt;/code&gt; to the appropriate value might be needed for the page to be displayed optimally.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Customising header and footer
&lt;/h2&gt;

&lt;p&gt;We can also have custom headers and footers added to our pages, displaying values such as title, page number and more. Let's see how this looks on your &lt;a href="https://theheadless.dev" rel="noopener noreferrer"&gt;favourite website&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;With Puppeteer:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;With Playwright:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We are including the following template files for our header...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;#header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#777&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;-webkit-print-color-adjust&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nc"&gt;.title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nc"&gt;.date&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt; -
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and footer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;#footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nc"&gt;.content-footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#777&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;-webkit-print-color-adjust&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content-footer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Page &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pageNumber"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt; of &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"totalPages"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first page of the generated PDF looks as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh6sebcq24dfw4p73ilpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fh6sebcq24dfw4p73ilpu.png" alt="generated pdf screenshot example" width="800" height="1033"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TIP:&lt;br&gt;
Chromium sets a default padding for header and footer. You will need to &lt;a href="https://github.com/puppeteer/puppeteer/issues/4132#issuecomment-475110167" rel="noopener noreferrer"&gt;override it&lt;/a&gt; in your CSS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Further considerations
&lt;/h2&gt;

&lt;p&gt;We can easily transform existing web pages into PDF format, just as we have shown in our example. An even more interesting use case is about generating a brand new document: now we can use our existing HTML and CSS skills to produce high-quality PDFs, often eliminating the need for LaTeX or similar tools.&lt;/p&gt;

&lt;p&gt;See points 2 and 3 of the following section for practical examples of this approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pocket Admin's article on &lt;a href="https://pocketadmin.tech/en/puppeteer-generate-pdf/" rel="noopener noreferrer"&gt;generating PDF from HTML&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Florian Mößle's guide to &lt;a href="https://medium.com/@fmoessle/use-html-and-puppeteer-to-create-pdfs-in-node-js-566dbaf9d9ca" rel="noopener noreferrer"&gt;generating invoices with Puppeteer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A great example of Puppeteer's PDF generation feature: &lt;a href="https://twitter.com/li_haoyi" rel="noopener noreferrer"&gt;Li Haoyi&lt;/a&gt;'s &lt;a href="https://www.handsonscala.com/index.html" rel="noopener noreferrer"&gt;Hands On Scala&lt;/a&gt; book. See the &lt;a href="https://github.com/handsonscala/build" rel="noopener noreferrer"&gt;build pipeline&lt;/a&gt; behind it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Banner image: &lt;a href="https://www.flickr.com/photos/60849810@N05/17139137680" rel="noopener noreferrer"&gt;"Students working with a printing press, Working Men's College"&lt;/a&gt; by &lt;a href="https://www.flickr.com/photos/60849810@N05" rel="noopener noreferrer"&gt;State Library Victoria Collections&lt;/a&gt; is licensed under &lt;a href="https://creativecommons.org/licenses/by-nc/2.0/?ref=ccsearch&amp;amp;atype=rich" rel="noopener noreferrer"&gt;CC BY-NC 2.0&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Challenging automation flows</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Fri, 11 Dec 2020 15:25:35 +0000</pubDate>
      <link>https://dev.to/checkly/challenging-automation-flows-2bg</link>
      <guid>https://dev.to/checkly/challenging-automation-flows-2bg</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://theheadless.dev" rel="noopener noreferrer"&gt;theheadless.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While automation tools are fundamental to modern software development, they also have the innate potential to be used for malicious purposes. This applies to Puppeteer and Playwright, too.&lt;/p&gt;

&lt;p&gt;As a consequence, some user flows are made purposely hard to automate to defend against threat actors. Some examples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;New user signup&lt;/li&gt;
&lt;li&gt;Social login through providers like Google, Facebook etc.&lt;/li&gt;
&lt;li&gt;"Forgot password" and similar reset scenarios&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are several means through which automation is made more difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  Captchas
&lt;/h2&gt;

&lt;p&gt;Captchas are a popular measure many websites take in order to counter automation for sensitive user flows, with Google's ubiquitous &lt;a href="https://www.google.com/recaptcha/intro/v3.html" rel="noopener noreferrer"&gt;reCAPTCHA&lt;/a&gt; being perhaps the most known example. Captchas work by challenging users with tasks that are trivial for humans and hard for bots, like finding objects belonging to the same category in an image or reading obfuscated strings of text.&lt;/p&gt;

&lt;p&gt;It is beyond the point of a test to try and solve a captcha: we are not trying to verify that the captcha itself works, rather to get past the captcha so we can test the functionality of our target platform. Also, if we were able to automatically solve a captcha, that would defeat the whole purpose of including one in our application.&lt;/p&gt;

&lt;p&gt;For pre-production environments, we can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do" rel="noopener noreferrer"&gt;Set captcha to always pass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Have captchas removed&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bot detection
&lt;/h2&gt;

&lt;p&gt;Certain platforms might have built-in mechanisms to tell manual and automated traffic apart, with the goal of shutting down the latter. These could be based on traffic volume analysis, HTTP fingerprinting, IP blacklisting or even on user behaviour analysis (e.g. the way the user moves the mouse cursor). Headless browsers will normally identify themselves as such in their User-Agent request headers, making detection trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 
(KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari 537.36
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bot detection could be deactivated for pre-production environments in order to allow automation. For production environments, a secret may be included in the User-Agent (or similar mechanism) for the system to recognise test bots and allow them through:&lt;/p&gt;

&lt;p&gt;With Puppeteer :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUserAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari 537.36 Secret/&amp;lt;MY_SECRET&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Playwright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari 537.36 Secret/&amp;lt;MY_SECRET&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automation-resistant UIs
&lt;/h2&gt;

&lt;p&gt;Certain industries are particularly affected by malicious automation attempts, especially where high-volume automation offers an unfair advantage. Examples of this include online betting and gaming.&lt;/p&gt;

&lt;p&gt;In these cases, UI automation is specifically made more difficult by utilising components that are harder to interact with, and in some cases by randomising attribute values for key elements in order to prevent referencing. Even if this measure does not defeat all attempts, it can make it excruciatingly hard to produce reliable automated scripts, which might be enough to discourage a large number of attackers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt;&lt;br&gt;
Whenever looking at production environments, keep in mind that adding exceptions to any of the methods listed above might open new attack vectors for malicious users. Always conduct proper risk analysis before proceeding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://wiki.owasp.org/index.php/Category:Automated_Threat" rel="noopener noreferrer"&gt;OWASP's Automated Threat list&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Banner image: "&lt;a href="https://www.flickr.com/photos/99649389@N02/16214903806" rel="noopener noreferrer"&gt;Rusty Machine&lt;/a&gt;" by &lt;a href="https://www.flickr.com/photos/drainrat/" rel="noopener noreferrer"&gt;darkday&lt;/a&gt;. is licensed under &lt;a href="https://creativecommons.org/licenses/by/2.0/?ref=ccsearch&amp;amp;atype=rich" rel="noopener noreferrer"&gt;CC BY 2.0&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testautomation</category>
      <category>testing</category>
    </item>
    <item>
      <title>Puppeteer vs Selenium vs Playwright, a speed comparison</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Fri, 04 Dec 2020 11:37:00 +0000</pubDate>
      <link>https://dev.to/checkly/puppeteer-vs-selenium-vs-playwright-a-speed-comparison-569p</link>
      <guid>https://dev.to/checkly/puppeteer-vs-selenium-vs-playwright-a-speed-comparison-569p</guid>
      <description>&lt;p&gt;When we decided to build Checkly's &lt;a href="https://www.checklyhq.com/product/synthetic-monitoring/" rel="noopener noreferrer"&gt;browser checks&lt;/a&gt;, we chose to do so with Puppeteer, an open-source headless browser automation tool, later adding Playwright, too. We wanted to support users with synthetic monitoring and testing to let them know whether their websites worked as expected at any given moment. Speed was a primary concern in our case.&lt;/p&gt;

&lt;p&gt;Yet, determining which automation tool is generally faster is far from simple. Therefore we decided to run our own benchmarks to see how newcomers &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; and &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; measured against the veteran &lt;a href="https://webdriver.io/" rel="noopener noreferrer"&gt;WebDriverIO&lt;/a&gt; (using Selenium and the DevTools &lt;a href="https://webdriver.io/docs/automationProtocols.html" rel="noopener noreferrer"&gt;automation protocols&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Among the results of our benchmark were also some &lt;strong&gt;unexpected findings&lt;/strong&gt;, like Puppeteer being significantly faster on shorter scripts and WebDriverIO showing larger than expected variability in the longer scenarios. Read below to know more about the results and how we obtained them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of content&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why compare these automation tools?&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Methodology, or how we ran the benchmark&lt;/p&gt;

&lt;p&gt;a. General guidelines&lt;br&gt;
b. Technical setup&lt;br&gt;
c. Measurements&lt;br&gt;
d. What we did not measure (yet)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The results&lt;/p&gt;

&lt;p&gt;a. Running against a demo website&lt;br&gt;
b. Running against a real-world web application&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;a. Takeaways&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why compare these automation tools?
&lt;/h2&gt;

&lt;p&gt;A benchmark including Puppeteer/Playwright and Selenium is pretty much an apples-and-oranges comparison: these tools have significantly different scopes, and anyone evaluating them should be aware of their differences before speed is considered.&lt;/p&gt;

&lt;p&gt;Still, most of us having worked with Selenium for many years, we were keen to understand if these newer tools were indeed any faster.&lt;/p&gt;

&lt;p&gt;It is also important to note that WebDriverIO is a higher-level framework with plenty of useful features, which can drive automation on multiple browsers using different tools under the hood.&lt;/p&gt;

&lt;p&gt;Still, our previous experience showed us that most Selenium users who chose JavaScript used WebDriverIO to drive their automated scripts, and therefore we chose it over other candidates. We were also quite interested in testing out the new &lt;a href="https://webdriver.io/blog/2019/09/16/devtools.html" rel="noopener noreferrer"&gt;DevTools mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another important goal for us was to see how Playwright, for which we recently &lt;a href="https://blog.checklyhq.com/we-now-support-playwright/" rel="noopener noreferrer"&gt;added support&lt;/a&gt; on Checkly, compared to our beloved Puppeteer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology, or how we ran the benchmark
&lt;/h2&gt;

&lt;p&gt;Feel free to skip this section in case you want to get straight to the results. We still recommend you take the time to go through it, so that you can better understand exactly what the results mean.&lt;/p&gt;

&lt;h3&gt;
  
  
  General guidelines
&lt;/h3&gt;

&lt;p&gt;A benchmark is useless if the tools being tested are tested under significantly different conditions. To avoid this, we put together and followed these guidelines:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Resource parity&lt;/strong&gt;: Every test was run sequentially on the same machine while it is "at rest", i.e. no heavy workloads were taking place in the background during the benchmark, potentially interfering with the measurements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple execution&lt;/strong&gt;: Scripts were run as shown in the "Getting started" documentation of each tool, e.g. for Playwright: node script.js and with minimal added configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparable scripts&lt;/strong&gt;: We strived to minimise differences in the scripts that were used for the benchmark. Still, some instructions had to be added/removed/tweaked to achieve stable execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latest everything&lt;/strong&gt;: We tested all the tools in their latest available version at the time of the publishing of this article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same browser&lt;/strong&gt;: All the scripts ran against headless Chromium.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;See the below section for additional details on all points.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical setup
&lt;/h3&gt;

&lt;p&gt;For each benchmark, we gathered data from &lt;strong&gt;1000 successful sequential executions&lt;/strong&gt; of the same script.&lt;/p&gt;

&lt;p&gt;In the case of Selenium benchmarks, our scripts ran against a standalone server, i.e. we did not start a new server from scratch for each run (even though we always used clean sessions), as some frameworks do. We made this choice to limit overhead on execution time.&lt;/p&gt;

&lt;p&gt;We ran all tests on the latest-generation MacBook Pro 16" running macOS Catalina 10.15.7 (19H2) with the following specs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Model Identifier: MacBookPro16,1&lt;br&gt;
Processor Name: 6-Core Intel Core i7&lt;br&gt;
Processor Speed: 2,6 GHz&lt;br&gt;
Number of Processors: 1&lt;br&gt;
Total Number of Cores: 6&lt;br&gt;
L2 Cache (per Core): 256 KB&lt;br&gt;
L3 Cache: 12 MB&lt;br&gt;
Hyper-Threading Technology: Enabled&lt;br&gt;
Memory: 16 GB&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We used the following dependencies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="mailto:bench-wdio@1.0.0"&gt;bench-wdio@1.0.0&lt;/a&gt; /Users/ragog/repositories/benchmarks/scripts/wdio-selenium&lt;br&gt;
├── @wdio/&lt;a href="mailto:cli@6.9.1"&gt;cli@6.9.1&lt;/a&gt;&lt;br&gt;
├── @wdio/&lt;a href="mailto:local-runner@6.9.1"&gt;local-runner@6.9.1&lt;/a&gt;&lt;br&gt;
├── @wdio/&lt;a href="mailto:mocha-framework@6.8.0"&gt;mocha-framework@6.8.0&lt;/a&gt;&lt;br&gt;
├── @wdio/&lt;a href="mailto:spec-reporter@6.8.1"&gt;spec-reporter@6.8.1&lt;/a&gt;&lt;br&gt;
├── @wdio/&lt;a href="mailto:sync@6.10.0"&gt;sync@6.10.0&lt;/a&gt;&lt;br&gt;
├── &lt;a href="mailto:chromedriver@87.0.0"&gt;chromedriver@87.0.0&lt;/a&gt;&lt;br&gt;
└── &lt;a href="mailto:selenium-standalone@6.22.1"&gt;selenium-standalone@6.22.1&lt;/a&gt;&lt;br&gt;
&lt;a href="mailto:scripts@1.0.0"&gt;scripts@1.0.0&lt;/a&gt; /Users/ragog/repositories/benchmarks/scripts&lt;br&gt;
├── &lt;a href="mailto:playwright@1.6.2"&gt;playwright@1.6.2&lt;/a&gt;&lt;br&gt;
└── &lt;a href="mailto:puppeteer@5.5.0"&gt;puppeteer@5.5.0&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can find the scripts we used, together with the individual results they produced, in the dedicated &lt;a href="https://github.com/checkly/headless-benchmarks" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measurements
&lt;/h3&gt;

&lt;p&gt;Our results will show the following values, all calculated across 1000 runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mean execution time (in seconds)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard deviation (in seconds)&lt;/strong&gt;: A measure of the variability in execution time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coefficient of variation (CV)&lt;/strong&gt;: A unitless coefficient showing the variability of results in relation to the mean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P95 (95th-percentile measurement)&lt;/strong&gt;: The highest value left when the top 5% of a numerically sorted set of collected data is discarded. Interesting to understand what a non-extreme but still high data point could look like.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What we did not measure (yet):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;: Unreliable scripts quickly get useless, no matter how fast they execute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallelisation efficiency&lt;/strong&gt;: Parallel execution is very important in the context of automation tools. In this case, though, we first wanted to understand the speed at which a single script could be executed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed in non-local environments&lt;/strong&gt;: Like parallelisation, cloud execution is also an important topic which remains outside of the scope of this first article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource usage&lt;/strong&gt;: The amount of memory and computing power needed can determine where and how you are able to run your scripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned, as we might explore these topics in upcoming benchmarks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;p&gt;Below you can see the aggregate results for our benchmark. You can find the full data sets in our &lt;a href="https://github.com/checkly/headless-benchmarks" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running against a demo website
&lt;/h3&gt;

&lt;p&gt;Our first benchmark ran against our &lt;a href="https://danube-webshop.herokuapp.com/" rel="noopener noreferrer"&gt;demo website&lt;/a&gt;. Hosted on Heroku, this web page is built using Vue.js and has a tiny Express backend. In most cases, no data is actually fetched from the backend, and the frontend is instead leveraging &lt;a href="https://javascript.info/localstorage" rel="noopener noreferrer"&gt;client-side data storage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this first scenario, performing a &lt;a href="https://theheadless.dev/posts/e2e-login" rel="noopener noreferrer"&gt;quick login procedure&lt;/a&gt;, we expected an execution time of just a few seconds, great for highlighting potential differences in startup speed between the actual tools.&lt;/p&gt;

&lt;p&gt;The aggregate results are as follows:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8eys6pz3dv6zy9szeep1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8eys6pz3dv6zy9szeep1.png" alt="chart results aggregate for demo website" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first thing that catches one's attention is the large difference between the average execution time for Playwright and Puppeteer, with the latter being almost 30% faster and showing less variation in its performance. This left us wondering whether this was due to a higher startup time on Playwright's side. We parked this and similar question to avoid scope creep for this first benchmark.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg0jfpu1e144ftffyr828.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg0jfpu1e144ftffyr828.png" alt="playwright vs puppeteer execution time comparison&amp;lt;br&amp;gt;
" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second surprise was the lower overall variability shown in the WebDriverIO runs. Also interesting is just how close the results are: the chart shows the lines crossing each other continuously, as the automation protocol does not seem to make a sizeable difference in execution time in this scenario.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Feulyv4vlshyacjrk4p3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Feulyv4vlshyacjrk4p3k.png" alt="WebDriverIO with WebDriver vs WebDriverIO with DevTools execution time comparison" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Less surprising is perhaps that running Puppeteer without any added higher-level framework helps us shave off a significant amount of execution time on this very short script.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjou7erhrdsmztdocw8n7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjou7erhrdsmztdocw8n7.png" alt="Puppeteer vs WebDriverIO with DevTools execution time comparison" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Running against a real-world web application
&lt;/h3&gt;

&lt;p&gt;Previous experience has taught us that the difference between a demo environment and the real world gets almost always underestimated. We were therefore very keen to have the benchmarks run against a production application. In this case we chose our own, which runs a Vue.js frontend and a backend which heavily leverages AWS.&lt;/p&gt;

&lt;p&gt;The script we ran looks a lot like a classic E2E test: it logged into Checkly, configured an API check, saved it and deleted it immediately. We were looking forward to this scenario, but each of us had different expectations on what the numbers would look like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fswo9qchasg5ijjk5z7yr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fswo9qchasg5ijjk5z7yr.png" alt="aggregate benchmark results for our Checkly check creation scenario" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case the difference in execution time between Playwright and Puppeteer has all but vanished, with the former now coming up on top and displaying a slightly lower variability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffdvo21vby9gd9sxf1v91.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffdvo21vby9gd9sxf1v91.png" alt="Playwright vs Puppeteer execution timing" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Proportionally, the difference between the newer tools and both flavours of WebDriverIO is also lower. It is worth noting that the latter two are now producing more variable results compared to the previous scenario, while Puppeteer and Playwright are now displaying smaller variations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvc4kmevwib9n5jzxvhv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvc4kmevwib9n5jzxvhv5.png" alt="Playwright vs WebDriverIO with Selenium execution timing" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interestingly enough, our original test for this scenario included injecting cookies into a brand new session to be able to skip the login procedure entirely. This approach was later abandoned as we encountered issues on the Selenium side, with the session becoming unresponsive after a certain number of cookies had been loaded. WebDriverIO handled this reliably, but the cookie injection step exploded the variability in execution time, sometimes seemingly hanging for longer than five seconds.&lt;/p&gt;

&lt;p&gt;We can now step back and compare the execution times across scenarios:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz9trlgiyds7tr5fa7pwc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz9trlgiyds7tr5fa7pwc.png" alt="Mean execution timings across benchmark scenarios comparison" width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have doubts about the results? Run your own benchmark! You can use our benchmarking scripts shared above. Unconvinced about the setup? Feel free to submit a PR to help make this a better comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;First off, let us rank the tools from fastest to slowest for both testing scenarios:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3pkkeyv8czalr0ppqq3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3pkkeyv8czalr0ppqq3k.png" alt="Tool performance ranking" width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This first benchmark brought up some interesting findings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Even though Puppeteer and Playwright sport similar APIs, &lt;strong&gt;Puppeteer seems to have a sizeable speed advantage on shorter scripts&lt;/strong&gt; (close to 30% in our observations).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Puppeteer and Playwright&lt;/strong&gt; scripts show &lt;strong&gt;faster execution time&lt;/strong&gt; (close to 20% in E2E scenarios) ** compared to the Selenium and DevTools WebDriverIO flavours**.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With WebDriverIO, WebDriver and DevTools&lt;/strong&gt; automation protocols &lt;strong&gt;showed comparable execution times&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When running lots of quicker scripts, if there is no need to run cross-browser, it might be worth to run Puppeteer to save time. On longer E2E scenarios, the difference seems to vanish.&lt;/li&gt;
&lt;li&gt;It pays off to consider whether one can run a more barebones setup, or if the convenience of WebDriverIO's added tooling is worth waiting longer to see your results.&lt;/li&gt;
&lt;li&gt;Fluctuations in execution time might not be a big deal in a benchmark, but in the real world they could pile up and slow down a build. Keep this in mind when choosing an automation tool.&lt;/li&gt;
&lt;li&gt;Looking at the progress on both sides, we wonder if the future will bring DevTools to the forefront, or if WebDriver will keep enjoying its central role in browser automation. We suggest keeping an eye on both technologies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Speed is important, but it can't tell the whole story. Stay tuned, as we surface new and practical comparisons that tell us more about the tools we love using.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;banner image:&lt;/em&gt; "Evening View of Takanawa". Utagawa Hiroshige, &lt;em&gt;1835, Japan&lt;/em&gt;. &lt;a href="https://data.ukiyo-e.org/chazen/images/2297b17df8f8fe22e3a1919362b18865.jpg" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>testing</category>
      <category>testautomation</category>
    </item>
    <item>
      <title>Intro to measuring page performance with Puppeteer &amp; Playwright</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Thu, 26 Nov 2020 13:30:19 +0000</pubDate>
      <link>https://dev.to/checkly/intro-to-measuring-page-performance-with-puppeteer-playwright-oli</link>
      <guid>https://dev.to/checkly/intro-to-measuring-page-performance-with-puppeteer-playwright-oli</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://theheadless.dev" rel="noopener noreferrer"&gt;theheadless.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The need for fast and responsive applications has never been greater because of the move from &lt;a href="https://gs.statcounter.com/platform-market-share/desktop-mobile-tablet/worldwide/2019" rel="noopener noreferrer"&gt;desktop to mobile&lt;/a&gt;. Still, web applications have been increasing in &lt;a href="https://httparchive.org/reports/page-weight" rel="noopener noreferrer"&gt;complexity and size&lt;/a&gt;, with rising load times. It is therefore clear why the topic of webpage performance is more popular today than it likely ever was.&lt;/p&gt;

&lt;p&gt;This article aims at giving a practical introduction to the whys and hows of web performance, without getting lost in the depth or breadth of this massive topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why performance matters
&lt;/h2&gt;

&lt;p&gt;The time it takes for a service to become usable, as well as its general responsiveness, bear a lot of weight on the user's perception of that service. Helpful features, great design and other prominent characteristics all become irrelevant when an online service is so slow that users navigate away. &lt;/p&gt;

&lt;p&gt;You can build the best web application in the world, but be mindful that each user will have a specific amount of time they are willing to invest in your service to solve their problems. Exceed that amount, and you risk losing them to a different, more performant solution. This is even truer for new users, who haven't yet been given proof of the quality of your service, and are essentially investing their time up-front, hoping for a return.&lt;/p&gt;

&lt;h3&gt;
  
  
  A competitive differentiator
&lt;/h3&gt;

&lt;p&gt;There is a brighter side to the topic: if low performance can sink an online platform, high performance can very well help it rise to the top. Speed and responsiveness can be a differentiating characteristic for a service, prompting users to choose it over the competition. Therefore an investment in this area will almost always pay off. Some notorious real-world examples from known businesses include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pinterest decreasing wait time for their users, &lt;a href="https://medium.com/@Pinterest_Engineering/driving-user-growth-with-performance-improvements-cfc50dafadd7" rel="noopener noreferrer"&gt;increasing both traffic and conversions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Zalando applying small improvements in load time and finding a direct correlation with &lt;a href="https://engineering.zalando.com/posts/2018/06/loading-time-matters.html" rel="noopener noreferrer"&gt;increased revenue per session&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The BBC discovering that every extra second that a page took to load led to 10% of &lt;a href="https://www.creativebloq.com/features/how-the-bbc-builds-websites-that-scale" rel="noopener noreferrer"&gt;users leaving the page&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Measuring performance
&lt;/h2&gt;

&lt;p&gt;Given the importance of page performance, it is no coincidence that browsers expose a ton of insights into &lt;a href="https://web.dev/metrics/" rel="noopener noreferrer"&gt;performance metrics&lt;/a&gt;. Being aware of how your application scores against these &lt;em&gt;across time&lt;/em&gt; will provide you the feedback you need to keep it performant for your users. There are several approaches that can be combined to achieve the best results:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Real user monitoring&lt;/em&gt; to understand what performance actual end-users of your service are experiencing.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Synthetic monitoring&lt;/em&gt; to proactively gather intel on service performance, as well as to find issues before users stumble into them.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Performance testing&lt;/em&gt; to avoid releasing performance regression to production in the first place.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Regular audits&lt;/em&gt; to get an overview of your page's performance and suggestions on how to improve it, e.g. with tools such as &lt;a href="https://developers.google.com/web/tools/lighthouse" rel="noopener noreferrer"&gt;Google Lighthouse&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Performance with headless tools
&lt;/h2&gt;

&lt;p&gt;As much as we should be striving to build performant applications, we should commit to monitoring and testing performance to enable continuous feedback and rapid intervention in case of degradation. Puppeteer and Playwright give us a great toolkit to power both synthetic monitoring and performance testing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Access to the Web Performance APIs, especially &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming" rel="noopener noreferrer"&gt;PerformanceNavigationTiming&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming" rel="noopener noreferrer"&gt;PerformanceResourceTiming&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Whenever testing against Chromium, access to the Chrome DevTools Protocol for traffic inspection, network emulation and more.&lt;/li&gt;
&lt;li&gt;Easy interoperability with performance libraries from the Node.js ecosystem.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Web Performance APIs
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.w3.org/TR/navigation-timing/" rel="noopener noreferrer"&gt;Navigation Timing&lt;/a&gt; and the &lt;a href="https://www.w3.org/TR/resource-timing-1/" rel="noopener noreferrer"&gt;Resource Timing&lt;/a&gt; performance APIs are &lt;a href="https://www.w3.org/" rel="noopener noreferrer"&gt;W3C&lt;/a&gt; specifications. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Navigation_and_resource_timings" rel="noopener noreferrer"&gt;MDN docs&lt;/a&gt; very clearly define the scope of both:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Navigation timings are metrics measuring a browser's document navigation events. Resource timings are detailed network timing measurements regarding the loading of an application's resources. Both provide the same read-only properties, but navigation timing measures the main document's timings whereas the resource timing provides the times for all the assets or resources called in by that main document and the resources' requested resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can use the Navigation Timing API to retrieve timestamps of key events in the page load timeline. &lt;/p&gt;

&lt;p&gt;With Puppeteer:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;With Playwright:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The Resource Timing API allows us to zoom in to single resources and get accurate information about how quickly they are being loaded. For example, we could specifically look at our website's logo:&lt;/p&gt;

&lt;p&gt;With Puppeteer:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;With Playwright:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Chrome DevTools for performance
&lt;/h3&gt;

&lt;p&gt;The Chrome DevTools Protocol offers many great performance tools for us to leverage together with Puppeteer and Playwright.&lt;/p&gt;

&lt;p&gt;One important example is network throttling, through which we can simulate the experience of users accessing our page with different network conditions.&lt;/p&gt;

&lt;p&gt;With Puppeteer:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;With Playwright:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The DevTools Protocol is quite extensive. We recommend exploring the &lt;a href="https://chromedevtools.github.io/devtools-protocol/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; and getting a comprehensive overview of its capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional performance libraries
&lt;/h3&gt;

&lt;p&gt;Lighthouse can easily be used programmatically with Playwright and Puppeteer to gather values and scores for different metrics, like &lt;a href="https://web.dev/interactive/" rel="noopener noreferrer"&gt;Time To Interactive (TTI)&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;With Puppeteer:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;With Playwright:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The comprehensive &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Performance" rel="noopener noreferrer"&gt;MDN Web Performance documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/learn/#performance" rel="noopener noreferrer"&gt;web.dev's performance section&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://addyosmani.com/blog/puppeteer-recipes/" rel="noopener noreferrer"&gt;Web Performance Recipes With Puppeteer&lt;/a&gt; by Addy Osmani&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/aslushnikov/getting-started-with-cdp" rel="noopener noreferrer"&gt;Getting started with Chrome DevTools Protocol&lt;/a&gt; by Andrey Lushnikov&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/tools/lighthouse#get-started" rel="noopener noreferrer"&gt;Get Started with Google Lighthouse&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Banner image: &lt;a href="https://www.flickr.com/photos/51867225@N08/5177851910" rel="noopener noreferrer"&gt;"Speed of Light"&lt;/a&gt; by &lt;a href="https://www.flickr.com/photos/51867225@N08" rel="noopener noreferrer"&gt;Rhys A.&lt;/a&gt;, licensed under CC BY 2.0&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Keeping tests valuable</title>
      <dc:creator>Tim Nolet 👨🏻‍🚀</dc:creator>
      <pubDate>Fri, 20 Nov 2020 11:10:32 +0000</pubDate>
      <link>https://dev.to/checkly/keeping-tests-valuable-2nkb</link>
      <guid>https://dev.to/checkly/keeping-tests-valuable-2nkb</guid>
      <description>&lt;p&gt;Headless browsers can be leveraged for testing in a variety of ways, and different scenarios do command different approaches. That being said, there are some general pointers most should follow in order to keep their tests valuable.&lt;/p&gt;

&lt;p&gt;Here, we define &lt;em&gt;valuable&lt;/em&gt; as &lt;em&gt;sustainably expressing meaningful, truthful information about the state of a system&lt;/em&gt;. A test that does not reliably fulfill these criteria should be fixed, if possible, or simply removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep tests short
&lt;/h2&gt;

&lt;p&gt;If they run against a real-world product with a UI that is evolving over time, scripts will need to be regularly updated. This brings up two important points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Most scripts are not write-and-forget, so each script we write is one more script we will have to maintain.&lt;/li&gt;
&lt;li&gt;Like all cases where code and refactoring are involved, &lt;em&gt;how&lt;/em&gt; we write scripts can have a significant influence on how long this maintenance effort takes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Taking example from good software engineering practices, our scripts should strive for &lt;em&gt;simplicity, conciseness and readability&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplicity&lt;/strong&gt;: keep in mind the goal of the script, and keep away from overly complex solutions whenever possible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Conciseness&lt;/strong&gt;: simply put, do not be overly verbose and keep scripts as short as they can be.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Readability&lt;/strong&gt;: follow general &lt;a href="https://blog.pragmaticengineer.com/readable-code/" rel="noopener noreferrer"&gt;best practices&lt;/a&gt; around writing code that is easy to read.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The faster we can read and understand a script we (or a teammate) wrote in the past, the quicker we can interpret its results and get to work on updating it and making it relevant again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TIP:&lt;/strong&gt; Do not underestimate the compounding effect of having messy scripts across a large test base.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep tests focused
&lt;/h2&gt;

&lt;p&gt;Automated tests are effective if they:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Correctly verify the status of the target functionality.&lt;/li&gt;
&lt;li&gt;Return within a reasonable amount of time.&lt;/li&gt;
&lt;li&gt;Produce a result that can be easily interpreted by humans.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last point is often overlooked. Scripts by themselves have no meaning if their results mean nothing to whoever is looking at them. Ideally, we want the opposite: interpreting a test success or failure should be close to instantaneous and give us a clear understanding of what is working and what is not.&lt;/p&gt;

&lt;p&gt;Oftentimes this is impeded by the tendency to have tests do too much. We can draw an example from two scenarios running against our &lt;a href="https://danube-webshop.herokuapp.com/" rel="noopener noreferrer"&gt;test site&lt;/a&gt;: &lt;a href="https://theheadless.dev/posts/e2e-checkout.html" rel="noopener noreferrer"&gt;E2E Checkout&lt;/a&gt; and &lt;a href="https://theheadless.dev/posts/e2e-coupon.html" rel="noopener noreferrer"&gt;E2E Coupon&lt;/a&gt;. While these two have part of their flow in common, and we might be tempted to combine them to avoid a certain degree of duplication, merging them into a single test would obfuscate the meaning of a test failure as we would be testing two different features. If that combined test were to run red, we would be unable to tell whether the entire checkout is not working as expected, or whether users are just unable to redeem coupon codes. Unless we were to devote additional time to diving deep into the failure — which is exactly what we are trying to avoid.&lt;/p&gt;

&lt;p&gt;We can avoid this pitfall by making sure our tests are verifying only one feature each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TIP:&lt;/strong&gt; Always check the assertions in your test: if they are spanning more than one feature, you would likely be better off splitting your test into multiple different ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep tests independent
&lt;/h2&gt;

&lt;p&gt;In an effort to remove duplication, tests are often made dependent on the previous execution of one or more other tests. An example could be the following sequence of tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test1: 
- creates user1 
- asserts user1 has been created 
test2: 
- logs in with user1 
- asserts user1 has logged in successfully 
test3: 
- goes through checkout as user1 
- asserts that checkout for user1 was successful
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the success of each subsequent test depends on previous tests as much as on its own assertions. In other words, previous tests are driving a test’s success criteria. This form of coupling can create headaches for us in the shape of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lower test result readability:&lt;/strong&gt; we might need to backtrack and look into previous tests when trying to understand what a subsequent one is doing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harder maintenance:&lt;/strong&gt; changes might need to be applied across different scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of flexibility and parallelisation:&lt;/strong&gt; tests need to run sequentially in a specific order to work&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Having each test encapsulate all it needs to give us a meaningful answer is fundamental to avoid the above issues. In case of our example, a solution could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test1:
- creates user1
- asserts user1 has been created
test2:
- (setup: creates user1)
- logs in with user1
- asserts user1 has logged in successfully
test3:
- (setup: creates user1)
- (setup: logs in with user1)
- goes through checkout as user1
- asserts that checkout for user1 was successful
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tests can be run in any order, even at the same time.&lt;/li&gt;
&lt;li&gt;In the interest of code quality, we would be factoring out setup phases in order to easily reuse them across tests. &lt;a href="https://jestjs.io/docs/en/setup-teardown" rel="noopener noreferrer"&gt;Jest&lt;/a&gt;’s beforeEach and beforeAll hooks are examples of useful helper functions that can help us achieve that.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;TIP:&lt;/strong&gt; If the system you are testing allows it, provision/deprovision your users through APIs in your setup and teardown phases to save time and increase test reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Tests should be reliable and informative in order to be useful.&lt;/li&gt;
&lt;li&gt;Keep tests short and focused on testing one feature.&lt;/li&gt;
&lt;li&gt;Keep tests independent to maximise their parallelisation potential and reduce total runtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Gergely Orosz on &lt;a href="https://blog.pragmaticengineer.com/readable-code/" rel="noopener noreferrer"&gt;writing readable code&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://theheadless.dev/posts/valuable-tests" rel="noopener noreferrer"&gt;https://theheadless.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>e2e</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
