<?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: Checkly</title>
    <description>The latest articles on DEV Community by Checkly (@checkly).</description>
    <link>https://dev.to/checkly</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%2Forganization%2Fprofile_image%2F737%2Fa63c82ec-a184-4c9f-af43-d819507a927e.png</url>
      <title>DEV Community: Checkly</title>
      <link>https://dev.to/checkly</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/checkly"/>
    <language>en</language>
    <item>
      <title>The Advent of Monitoring, Day 3: Easy Monitoring for Self-Hosted Projects with Checkly</title>
      <dc:creator>Sara Miteva</dc:creator>
      <pubDate>Thu, 14 Dec 2023 13:42:33 +0000</pubDate>
      <link>https://dev.to/checkly/the-advent-of-monitoring-day-3-easy-monitoring-for-self-hosted-projects-with-checkly-38c9</link>
      <guid>https://dev.to/checkly/the-advent-of-monitoring-day-3-easy-monitoring-for-self-hosted-projects-with-checkly-38c9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the third part of our 12-day Advent of Monitoring series. In this series, Checkly's engineers will share practical monitoring tips from their own experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written by Daniel Paulus, Checkly's Director of Engineering.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When it comes to running self-hosted services or side projects, monitoring is key. But, who has the time to set up a complex monitoring system?&lt;/p&gt;

&lt;p&gt;We want to deliver cool software and not be busy with configuring Prometheus servers or Grafana Dashboards. That’s where synthetic monitoring fits in perfectly – it's straightforward to set up and reliably keeps an eye on things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Multi-Step API Checks
&lt;/h2&gt;

&lt;p&gt;I love to use Checkly for this because of the great Developer Experience it gives me. It's particularly handy with its latest feature: multi-step API checks (still in beta). Multi step checks allow you to write code and use HTTP requests to implement an arbitrary number of requests, organized in steps, and custom logic to monitor any HTTP based service. This is ideal for more advanced monitoring needs without getting too complicated.&lt;/p&gt;

&lt;p&gt;The task at hand was to set up basic monitoring for my self-hosted ClickHouse database. For those who want to jump straight in, all the necessary code is available on &lt;a href="https://github.com/danielpaulus/clickhouse-monitoring"&gt;GitHub&lt;/a&gt;. Just clone it, set up ch_pass (clickhouse password), ch_user (clickhouse user name) and ch_url (’&lt;a href="https://IP:port%E2%80%99"&gt;https://IP:port’&lt;/a&gt;) account variables in your checkly account and run &lt;code&gt;npx checkly deploy&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you prefer a step-by-step guide, here’s how I did it:&lt;/p&gt;

&lt;p&gt;We will use Clickhouse’ HTTP interface to run queries against it to check the database health.&lt;/p&gt;

&lt;p&gt;First, create a Checkly project. It's as easy as running &lt;code&gt;npm create checkly&lt;/code&gt; and following the instructions. Then, you'll need four files in your &lt;strong&gt;checks&lt;/strong&gt; directory:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;clickhouse.check.ts:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { MultiStepCheck, Frequency } from "checkly/constructs";
import path from "node:path";

//this defines a check. The actual code is in the spec files
new MultiStepCheck("clickhouse-check-1", {
  name: "Clickhouse Version Check",
  runtimeId: "2023.09",
  frequency: Frequency.EVERY_10M,
  locations: ["us-east-1", "eu-west-1"],
  code: {
    entrypoint: path.join(__dirname, "clickhouse.spec.ts"),
  },
});

new MultiStepCheck("clickhouse-check-2", {
  name: "Clickhouse Free Diskspace",
  runtimeId: "2023.09",
  frequency: Frequency.EVERY_12H,
  locations: ["us-east-1", "eu-west-1"],
  code: {
    entrypoint: path.join(__dirname, "clickhouse-disk.spec.ts"),
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;clickhouse.spec.ts contains a very basic set of checks. First it pings the Clickhouse health endpoint to check if the server is running at all. If that succeeds, we run the most basic query, a SELECT version() and make sure the version string is returned as expected. Now we know that Clickhouse is responsive and ready to go.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { test, expect } from "@playwright/test";
import { baseUrl, queryClickhouse } from "./utils";

const clickHouseVersion = "23.9.1.1854";

async function pingClickhouse(request) {
  const response = await request.get(`${baseUrl}/ping`);
  expect(response.ok()).toBeTruthy();
  const msg = await response.text();
  expect(msg.trim()).toBe("Ok.");
}

test("check clickhouse", async ({ request }) =&amp;gt; {
  await test.step("ping clickhouse", async () =&amp;gt; {
    await pingClickhouse(request);
  });

  await test.step("check clickhouse version", async () =&amp;gt; {
    const response = await queryClickhouse("SELECT version()", request);
    expect(response[0]).toBe(clickHouseVersion);
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;clickhouse-disk.spec.ts is a very simple check to make sure Clickhouse has at least 5GB of free disk space left. If not, the check will fail and we will get an alert from Checkly. Clickhouse offers a lot of interesting metrics through it’s query interface so you can monitor many other things. Take a look: &lt;a href="https://clickhouse.com/blog/clickhouse-debugging-issues-with-system-tables"&gt;https://clickhouse.com/blog/clickhouse-debugging-issues-with-system-tables&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { test, expect } from "@playwright/test";
import { queryClickhouse } from "./utils";

const diskSpaceQuery = `
SELECT 
    name, 
    free_space / 1024 / 1024 / 1024 AS free_space_gb
FROM system.disks;

`;

test("check clickhouse", async ({ request }) =&amp;gt; {
  await test.step("check clickhouse diskspace", async () =&amp;gt; {
    const response = await queryClickhouse(diskSpaceQuery, request);
    const row = response[0].split("\t");
    console.log(`Disk ${row[0]} has ${row[1]}GB free`);
    //make sure we got more than 5GB left
    expect(parseFloat(row[1])).toBeGreaterThan(5);
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;utils.js contains a very simple Clickhouse response parser.. I know there is a lot of room for improvement here. But you know, I want to build apps not monitoring ;-) As you can see, you need to set up the environment variables for user name, password and the Clickhouse http url in your Checkly env variables for this to work.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { expect } from "@playwright/test";
export const baseUrl = process.env.ch_url;

export async function queryClickhouse(query, request) {
  const buff = Buffer.from(query, "utf-8");
  const response = await request.post(`${baseUrl}`, {
    headers: {
      "X-ClickHouse-User": process.env.ch_user,
      "X-ClickHouse-Key": process.env.ch_pass,
    },
    data: buff,
  });

  const buf = await response.body();
  expect(response.ok()).toBeTruthy();
  const res = new String(buf).toString();
  const resArray = res.split("\n");
  resArray.pop();
  return resArray;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have your files ready, run &lt;code&gt;npx checkly login&lt;/code&gt; to login to your account and then give it a try by running &lt;code&gt;npx checkly test&lt;/code&gt;. If that works and you Clickhouse cluster responds, it is time to unite testing with monitoring and deploy your checks to Checkly for continuous active monitoring. Deploy your checks using &lt;code&gt;npx checkly deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And we’re done! This setup will continuously monitor your ClickHouse database, ensuring it’s up and running all the time. How cool is it that you can put the monitoring code next to your application code and deploy changes that easily?&lt;/p&gt;

&lt;p&gt;In case your service does encounter an issue, it's important to get notified quickly. Checkly offers various alerting methods, including SMS, phone calls, Slack, and Pagerduty. You can set these up by checking out &lt;a href="https://www.checklyhq.com/docs/alerting-and-retries/alert-channels/"&gt;Checkly's Alert Channels documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With Checkly, setting up monitoring for your self-hosted projects is a breeze, giving you both peace of mind and more time to focus on other aspects of your projects.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the third part of our 12-day Advent of Monitoring series. In this series, Checkly's engineers will share practical monitoring tips from their own experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written by Daniel Paulus, Checkly's Director of Engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>monitoring</category>
    </item>
    <item>
      <title>The Advent of Monitoring, Day 2: Debugging Dashboard Outages with Checkly's API Checks</title>
      <dc:creator>Sara Miteva</dc:creator>
      <pubDate>Tue, 12 Dec 2023 17:39:38 +0000</pubDate>
      <link>https://dev.to/checkly/the-advent-of-monitoring-day-2-debugging-dashboard-outages-with-checklys-api-checks-21gp</link>
      <guid>https://dev.to/checkly/the-advent-of-monitoring-day-2-debugging-dashboard-outages-with-checklys-api-checks-21gp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the second part of our 12-day Advent of Monitoring series. In this series, Checkly's engineers will share practical monitoring tips from their own experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written by Daniel Paulus, Checkly's Director of Engineering.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We encountered a tricky issue with our public dashboards: they were experiencing sporadic outages, happening about once every two days. The infrequency and unpredictability of these outages made them particularly challenging to diagnose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Hypothesis
&lt;/h2&gt;

&lt;p&gt;Initially, we tried to correlate the outages with our logs, looking at the failure times reported by our browser check failures. Unfortunately, this method didn't yield any useful insights.&lt;/p&gt;

&lt;p&gt;This is when we tried to use high-frequency API checks, running every 10 seconds. Our existing setup involved a browser check that ran hourly, which was sufficient under normal circumstances but not detailed enough to catch these intermittent issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatGPT to the Rescue
&lt;/h3&gt;

&lt;p&gt;The issue with our public dashboards is, of course, already fixed for a long time, so to simulate a similar scenario, I used ChatGPT4 to generate a simple nodeJS server for me using the following prompt:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Write a simple nodejs based rest endpoint that always returns a body "OK" and&lt;br&gt;
a 200 status code without using a framework. &lt;br&gt;
The endpoint should return right away. &lt;br&gt;
There should be a loop that runs every 10s and with a 0.1% chance,&lt;br&gt;
will cause the endpoint to return HTTP 500 for a time window of 30-60 seconds.&lt;br&gt;
Afterwards it will go back to returning 200.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now all I had to do was to save the resulting code to a file and then run it with NodeJS. To publicly expose it for testing purposes a simple&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ngrok http 3000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;was the perfect solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring API Checks with Checkly
&lt;/h2&gt;

&lt;p&gt;Next, I configured two API checks with Checkly, one running every 5 minutes and one running every 10 seconds.&lt;/p&gt;

&lt;p&gt;The screenshots really illustrate the benefits of temporarily or permanently using higher frequency checks. The 5 min frequency check did not detect all the error windows, and also does not help with understanding how long the service was down. Things might seem much better here compared to when you start probing the API every 10s.&lt;/p&gt;

&lt;p&gt;5 minutes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tTxIAZiv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/147me1klljqmk6cxsawj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tTxIAZiv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/147me1klljqmk6cxsawj.png" alt="monitoring results after 5 minutes" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;10 seconds:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PiXJbXmT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5dx68sy6irncar916ygz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PiXJbXmT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5dx68sy6irncar916ygz.png" alt="monitoring results after 10 seconds" width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The results are quite revealing. The high-frequency API check shows almost exactly when the issue started, when it stopped and how long it took. It identifies all the 30-60s random errors. In real-world scenarios, this makes it much easier to find the correct logs and more importantly, also understand how big the problem actually is.&lt;/p&gt;

&lt;p&gt;By leveraging Checkly's API checks for high-resolution failure timing, we were able to identify and subsequently address a problem that our standard monitoring approach had missed. This experience underscored the importance of synthetic monitoring with higher frequency checks, especially for issues that occur sporadically. With Checkly's help, we were able to root cause and fix the dashboard's sporadic outages and could enhance the reliability of our service.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the second part of our 12-day Advent of Monitoring series. In this series, Checkly's engineers will share practical monitoring tips from their own experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written by Daniel Paulus, Checkly's Director of Engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>devops</category>
      <category>api</category>
    </item>
    <item>
      <title>The Advent of Monitoring, Day 1: What Are Synthetics and Why They Are Needed</title>
      <dc:creator>Sara Miteva</dc:creator>
      <pubDate>Mon, 11 Dec 2023 15:55:31 +0000</pubDate>
      <link>https://dev.to/checkly/the-advent-of-monitoring-day-1-what-are-synthetics-and-why-they-are-needed-2f4i</link>
      <guid>https://dev.to/checkly/the-advent-of-monitoring-day-1-what-are-synthetics-and-why-they-are-needed-2f4i</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first part of Checkly's 12-day Advent of Monitoring series. In this series, Checkly's engineers will share practical monitoring tips from their own experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written by &lt;strong&gt;Daniel Paulus&lt;/strong&gt;, Checkly's Director of Engineering.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hey there! Here is my take on what &lt;strong&gt;&lt;em&gt;synthetic monitoring&lt;/em&gt;&lt;/strong&gt; means and why it’s awesome!&lt;/p&gt;

&lt;p&gt;I think it’s a very complicated word for a very straightforward concept. In fact, I am convinced, that once you've used it, &lt;em&gt;you will never want to live without it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let’s start first with &lt;strong&gt;e2e testing&lt;/strong&gt;: In essence, what you do is, define a series of steps interacting with your web app or REST API to simulate real user behavior.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;web apps&lt;/strong&gt;, that means your test clicks buttons and types text while making assertions along the way.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;REST APIs&lt;/strong&gt;, you check that the actual HTTP requests are fast enough and contain the expected responses. This way, you make sure &lt;strong&gt;core user flows are working as expected&lt;/strong&gt; when you test your code before deploying it to production.&lt;/p&gt;

&lt;p&gt;Any refactoring that might break a ton of unit tests will not affect e2e tests unless the core user flow is actually broken. So, they give you the ultimate signal if your system behaves as it should. If you use a modern e2e testing framework like Playwright, your tests will also be quite fast and not flaky, unlike Selenium or other old testing tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Mf9MqKOP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tetcazqjn2sk9m48gdew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Mf9MqKOP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tetcazqjn2sk9m48gdew.png" alt="e2e testing" width="436" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How does all of this relate to synthetics?
&lt;/h2&gt;

&lt;p&gt;Well, the general idea is to &lt;strong&gt;promote those e2e tests you have created, to continuously run against your production services&lt;/strong&gt;! Now you have the best signal there is to understand if users can actually use your service, all the time, in your production environment. You will know when things break before your users inform you. Consider also the following questions you are finally getting answers to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The unit tests all worked, but &lt;strong&gt;does the feature actually work on production&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Is the auth provider having an outage and &lt;strong&gt;people cannot log in&lt;/strong&gt;, although your own software is correct?&lt;/li&gt;
&lt;li&gt;Is any other &lt;strong&gt;third-party dependency&lt;/strong&gt; that is slow or broken?&lt;/li&gt;
&lt;li&gt;It works in Germany, but does it work in California? Is there some &lt;strong&gt;regional blocking in place&lt;/strong&gt;?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Take a look at the screenshot below, for example. You can immediately spot which part of the system is affected. Compare that to a list of error logs or multiple metrics dashboards and you will understand what I mean when I say synthetics give you a clear signal compared to all the noise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mjQg_9W9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hl5hl51g81spau7ldbs9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mjQg_9W9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hl5hl51g81spau7ldbs9.png" alt="spotting a monitoring alert" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ultimately, synthetic monitoring means monitoring through simulated users, or testing in production, and is like your app's guardian angel. It's always there, quietly keeping watch, ensuring everything runs smoothly for your users.&lt;/p&gt;

&lt;p&gt;For those in the software development scene, it's an invaluable ally, keeping your app top-notch and your users happy. With a modern tool like Checkly, leveraging &lt;strong&gt;Monitoring as Code&lt;/strong&gt;, you can repurpose your existing e2e tests to run against production by writing them twice.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the first part of Checkly's 12-day Advent of Monitoring series. In this series, Checkly's engineers will share practical monitoring tips from their own experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written by &lt;strong&gt;Daniel Paulus&lt;/strong&gt;, Checkly's Director of Engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>devops</category>
    </item>
    <item>
      <title>Using Playwright to Monitor Third-Party Resources That Could Impact User Experience</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Wed, 15 Feb 2023 16:03:57 +0000</pubDate>
      <link>https://dev.to/checkly/using-playwright-to-monitor-third-party-resources-that-could-impact-user-experience-2lek</link>
      <guid>https://dev.to/checkly/using-playwright-to-monitor-third-party-resources-that-could-impact-user-experience-2lek</guid>
      <description>&lt;p&gt;Today’s web consists of lots of 3rd party resources. Let it be your fonts, transformed and optimized media assets, or analytics and ad scripts, many sites out there include resources that they don’t own. Your website probably has a lot of those dependencies, too!&lt;/p&gt;

&lt;p&gt;And while implementing third-party resources has downsides for performance and &lt;a href="https://csswizardry.com/2019/05/self-host-your-static-assets/" rel="noopener noreferrer"&gt;you should self-host your assets when possible&lt;/a&gt;, sometimes relying on external files is unavoidable. But be warned — using them can sometimes &lt;strong&gt;cause real headaches.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://chriscoyier.net/2023/02/03/end-to-end-tests-with-content-blockers/" rel="noopener noreferrer"&gt;Chris Coyier pointed out&lt;/a&gt;, external resources are often integrated too tightly into your own code, or implemented the wrong way, so that things can really go sideways. Ad blockers could block your external scripts or a service vendor could be down. Both situations can result in frontend downtime, poor UX, or entirely broken functionality on your end.&lt;/p&gt;

&lt;p&gt;And while Chris asks if you could detect the cases of broken sites due to ad blockers with end-to-end testing, I think it’s more about all the 3rd party code running on the internet.&lt;/p&gt;

&lt;p&gt;But heck yeah, testing and monitoring of failing external resources is possible!&lt;/p&gt;

&lt;p&gt;Let’s have a look at an example that could be affected by 3rd party code and ways to monitor your stack with Playwright to guarantee that no external providers or ad blockers can mess with your business. 🫡&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of problematic external resource usage
&lt;/h2&gt;

&lt;p&gt;But first, let’s look at patterns that can break your sites.&lt;/p&gt;

&lt;p&gt;In my experience, there are two challenging 3rd party resource scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slow and render-blocking resources
&lt;/h3&gt;

&lt;p&gt;Years ago, I worked in an ecommerce startup and we were serving ad banners via an external provider. It was a quick solution to receive click statistics while providing marketers with a comfortable way to handle images on the site.&lt;/p&gt;

&lt;p&gt;The banners were implemented with synchronous script elements above the fold.&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://some-provider.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we had plenty of these elements embedded in a slideshow. The scripts would load and replace containers with content coming from elsewhere. It worked beautifully… until it didn’t.&lt;/p&gt;

&lt;p&gt;A synchronous script element stops the HTML parser and forces it to wait for the script to be downloaded and executed. The browser only continues parsing and rendering once this is done so that this script resulting in an embedded image can literally block anything on your site.&lt;/p&gt;

&lt;p&gt;And that’s what exactly happened to me back in the day.&lt;/p&gt;

&lt;p&gt;The script was loaded fairly quickly from Berlin, Germany and we didn’t notice any delays in rendering. But one day our third-party vendor experienced an outage. And interestingly, requests wouldn’t fail but just hang and time out eventually.&lt;/p&gt;

&lt;p&gt;The result: a browser started rendering our site, stopped at the slideshow and waited for the scripts to time out. Our customers were looking at a white page until this happened and our site was entirely broken. &lt;strong&gt;A third-party vendor brought our frontend down&lt;/strong&gt; because we implemented synchronous scripts. Be aware that every external dependency is another risk for your sites!&lt;/p&gt;

&lt;p&gt;That’s why it’s generally better to self-host your assets and avoid critical render-blocking elements. No matter if you’re using ad tech, error monitoring scripts, or external stylesheets, make sure to double-check what happens when these resources fail. I doubt you want your system to be affected because of someone else's downtime.&lt;/p&gt;

&lt;p&gt;That’s not the only situation where third-party resources can lead to trouble, though. Let’s come back to Chris’ example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tightly coupled JavaScript dependencies
&lt;/h3&gt;

&lt;p&gt;Chris mentioned that he experiences broken sites on a regular basis. Some sites would fail in essential situations like making an order. The culprit: a thrown JavaScript exception.&lt;/p&gt;

&lt;p&gt;That’s interesting because it’s unlikely that web developers aren’t testing their most important functionality. So what’s happening?&lt;/p&gt;

&lt;p&gt;If you’re an ad blocker user there’s a high chance that this tiny browser extension is the reason. And that’s not the ad blocker’s fault. It’s doing what it’s supposed to do — blocking trackers (most likely third-party scripts). But the issue is often that developers expect these scripts to be loaded. For example, if your website’s JavaScript expects a &lt;code&gt;sendTracking&lt;/code&gt; method to be available in the global window object, an ad blocker can easily mess with the site and cause JavaScript to fall on its nose.&lt;/p&gt;

&lt;p&gt;Here’s Chris’ 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="c1"&gt;// https://tracking-website.com/tracking-script.js&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trackThing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// report data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// /script/index.js&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// this throws an exception if `tracking-script.js` was blocked&lt;/span&gt;
  &lt;span class="nf"&gt;trackThing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page loaded or something&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="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;tracking-script.js&lt;/code&gt; fails or is blocked by a browser extension your code throws an exception and could blow up your entire app. If you’re implementing external JavaScript functionality, you should make sure that things are still functional in error cases because I bet you prefer processing an order over tracking it.&lt;/p&gt;

&lt;p&gt;But how can you go around these two problems and guarantee that you won’t be running into frontend downtimes based on synchronous resources or “death by ad blocker” scenarios in the future?&lt;/p&gt;

&lt;h2&gt;
  
  
  How to test and monitor risky third-party resources with Playwright
&lt;/h2&gt;

&lt;p&gt;Let’s take a simple HTML example running on my localhost:&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="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&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;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"IE=edge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Frontend downtime&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;
&lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"&lt;/span&gt; &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&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;h1&amp;gt;&lt;/span&gt;Hello world&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"&lt;/span&gt;
&lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"&lt;/span&gt;
&lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrCreateInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#myAlert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&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;It includes Bootstrap’s CSS and JavaScript, renders the headline “Hello world” and tries to access &lt;code&gt;bootstrap.Alert&lt;/code&gt; which is available in the global window scope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note that in this case, both external resources are render-blocking.&lt;/strong&gt; The stylesheet has to be loaded before anything appears on the screen and even though the script is placed at the end of the document, it will still block rendering (there’s just nothing to render after it).&lt;/p&gt;

&lt;p&gt;A quick Playwright test for this page could look like this:&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="c1"&gt;// @ts-check&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&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/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;has the correct title&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;page&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;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;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// evaluate the largest contentful paint&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;largestContentfulPaint&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;evaluate&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;new&lt;/span&gt; &lt;span class="nc"&gt;PerformanceObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&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;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// the last entry is the largest contentful paint&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;largestPaintEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largestPaintEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&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;largest-contentful-paint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;buffered&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;span class="p"&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="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largestContentfulPaint&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;await&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;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Frontend downtime/&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;Playwright navigates to localhost and &lt;a href="https://www.checklyhq.com/learn/headless/basics-performance/#largest-contentful-paint-api-largest-contentful-paint" rel="noopener noreferrer"&gt;evaluates the popular largest contentful paint metric by injecting custom JavaScript into the page&lt;/a&gt;. When you run the script with &lt;code&gt;npx playwright test&lt;/code&gt; you’ll see the following in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running 1 test using 1 worker
[chromium] › example.spec.js:4:1 › has the correct title
CLP: 116.39999999850988ms

1 passed (837ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wonderful! The largest contentful paint is a hundred milliseconds long and everything works. But what if the Bootstrap resources on &lt;code&gt;jsdeliver.net&lt;/code&gt; are slow to respond?&lt;/p&gt;

&lt;h3&gt;
  
  
  How to emulate slow third-party resources with Playwright
&lt;/h3&gt;

&lt;p&gt;Let’s find out and delay requests by ten seconds that aren’t fetching resources from the same origin with &lt;a href="https://playwright.dev/docs/api/class-page#page-route" rel="noopener noreferrer"&gt;Playwright’s &lt;code&gt;page.route&lt;/code&gt; method&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="c1"&gt;// @ts-check&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&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/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;works with slows resources&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;page&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;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="s2"&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;requestURL&lt;/span&gt; &lt;span class="o"&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;request&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;url&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;requestURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/http:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="sr"&gt;localhost:8080/&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&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;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setTimeout&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="s2"&gt;`Delaying &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;requestURL&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="nf"&gt;resolve&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;span class="mi"&gt;10000&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="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;http://localhost:8080&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;largestContentfulPaint&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;evaluate&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;new&lt;/span&gt; &lt;span class="nc"&gt;PerformanceObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&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;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// the last entry is the largest contentful paint&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;largestPaintEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largestPaintEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&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;largest-contentful-paint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;buffered&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;span class="p"&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="s2"&gt;`CLP: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largestContentfulPaint&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Expect a title "to contain" a substring.&lt;/span&gt;
  &lt;span class="k"&gt;await&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;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Frontend downtime/&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;The test still passes but its test duration skyrocketed from 800ms to 10 seconds and similarly, the largest contentful paint also joined at the ten seconds mark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running 1 test using 1 worker
[chromium] › example.spec.js:4:1 › has the correct title
Delaying https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css
Delaying https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js

CLP: 10186.10000000149ms

1 passed (11.0s)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is going on? &lt;a href="https://www.youtube.com/watch?v=18Cc5Ejgu2o&amp;amp;list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc&amp;amp;index=11" rel="noopener noreferrer"&gt;Let’s create a Playwright trace and debug this test.&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%2Flh6.googleusercontent.com%2F9Es1t47UzsKOwnByrLbyJ0iztC6W3ZL3t51S3mIWof3gQWCtKaL1WeYkKemRIKzmhB868slQB7F_1E6t_7zjtqG8Vad9z9YsJ08uOew72z9zR4KTvsTUyFvWV8FfWKF6VGjN4XJyUOL39ldpLkBZpcY" 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%2Flh6.googleusercontent.com%2F9Es1t47UzsKOwnByrLbyJ0iztC6W3ZL3t51S3mIWof3gQWCtKaL1WeYkKemRIKzmhB868slQB7F_1E6t_7zjtqG8Vad9z9YsJ08uOew72z9zR4KTvsTUyFvWV8FfWKF6VGjN4XJyUOL39ldpLkBZpcY" alt="Playwright Trace showing a page.goto call taking 10s" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two things to note in this Playwright trace: first, the test duration increased to ten seconds because Playwright’s &lt;code&gt;page.goto&lt;/code&gt; waits for the page &lt;code&gt;onload&lt;/code&gt; event which is heavily delayed by the slowed-down stylesheet.&lt;/p&gt;

&lt;p&gt;And second, and probably worse than a slow test case, the user experience suffered tremendously because the page only started rendering after ten seconds.&lt;/p&gt;

&lt;p&gt;After running a quick Playwright test, you now know that there are 3rd party resources that could really harm your overall performance and you can check if they’re necessary or if you should implement them in another way.&lt;/p&gt;

&lt;p&gt;But what if these requests fail or are blocked by browser extensions?&lt;/p&gt;

&lt;h3&gt;
  
  
  How to block requests with Playwright
&lt;/h3&gt;

&lt;p&gt;So far we’ve only been delaying third-party resources, but what happens when we block them entirely? Luckily, &lt;a href="https://playwright.dev/docs/api/class-page#page-route" rel="noopener noreferrer"&gt;blocking resources is quickly done with &lt;code&gt;page.route&lt;/code&gt;&lt;/a&gt;, too.&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;works with blocked 3rd party resources&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;page&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;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="s2"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestURL&lt;/span&gt; &lt;span class="o"&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;request&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;url&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;requestURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/http:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="sr"&gt;localhost:8080/&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;span class="k"&gt;else&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="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// monitor emitted page errors&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&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;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pageerror&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;error&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;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&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;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;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&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;errors&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;errors&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="nf"&gt;toBe&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run the script the test case fails because the page emits &lt;code&gt;pageerrors&lt;/code&gt;. The inline JavaScript tried to call a &lt;code&gt;Bootstrap&lt;/code&gt; method that wasn’t available because of resource blocking similar to how an ad blocker would do it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running 1 tests using 1 workers
[chromium] › example.spec.js:43:1 › works with blocked 3rd party resources
[
  Error [ReferenceError]: bootstrap is not defined at http://localhost:8080/:18:11
]
1) [chromium] › example.spec.js:43:1 › works with blocked 3rd party resources ====================

Error: expect(received).toBe(expected) // Object.is equality
    Expected: 0
    Received: 1

    60 |
    61 |   console.log(errors)
  &amp;gt; 62 |   expect(errors.length).toBe(0)
       |                         ^
    63 | })
    64 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tracking page errors is a handy way to monitor your JavaScript in a synthetic lab setup. In the case of ad blockers and canceled requests, you could still perform your end-to-end tests and evaluate if a blocked script affects your site at all.&lt;/p&gt;

&lt;p&gt;Either way, &lt;code&gt;page.route&lt;/code&gt; is an easy-to-use way to emulate network conditions with Playwright.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what’s next?
&lt;/h2&gt;

&lt;p&gt;Here’s the takeaway: &lt;strong&gt;whenever you implement and rely on resources you don’t control, make sure that they’re not affecting or blowing up your application!&lt;/strong&gt; People use browser extensions, networks are flaky, services go down… many things that can go wrong.&lt;/p&gt;

&lt;p&gt;Know how your app handles slow responses or requests that might become victims of ad blockers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Note: This post’s code snippets implement page request handling on a test-case basis for simplicity. &lt;a href="https://www.youtube.com/watch?v=2O7dyz6XO2s" rel="noopener noreferrer"&gt;I recommend looking into Playwright’s fixtures&lt;/a&gt; to not clutter your tests and focus on your end-to-end logic. Work with a nicely abstracted &lt;code&gt;pageWithSlowThirdParties&lt;/code&gt; object instead!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But how should you go about these two scenarios then? Should you run all your deployment end-to-end tests with changed third-party network settings?&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend testing vs. monitoring
&lt;/h3&gt;

&lt;p&gt;End-to-end testing with resource constraints is a great step to gain confidence and guarantee that your deployed code works no matter if your dependencies are slow or unavailable. But honestly, testing end-to-end is only the first step to real confidence because between today’s deployment and tomorrow plenty of other things can go wrong.&lt;/p&gt;

&lt;p&gt;While end-to-end testing guarantees that your site works at one moment in time and helps to evaluate regressions in your source code, if you’re relying on third-party resources, your third-party provider’s downtime can still affect your site. And that’s sometimes unavoidable but at least, you should be aware of your dependencies’ effects on your site and bet on end-to-end monitoring. Be the first one to know when your vendors are struggling!&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;First, if you’re relying on resources that you don’t own, check how their unavailability could affect your frontend. Opt for self-hosting your assets, and if that’s impossible, rely on asynchronous and fail-safe implementations.&lt;/p&gt;

&lt;p&gt;Second, test your site during deployments and stop, if new and risky dependencies were added to your frontend. A script element pointing to another CDN is quickly implemented. Be aware of your external resources and how they affect your application.&lt;/p&gt;

&lt;p&gt;And third, if you have to rely on external resources, start monitoring your frontend, run your end-to-end tests on a schedule, and ensure that your site—and also those external resources!—are up and running. Don’t be me, coming to work only to learn that your customers have been looking at a blank screen 30 seconds long for the last 8 hours.&lt;/p&gt;

&lt;p&gt;And maybe you want to give Checkly a try, &lt;a href="https://www.checklyhq.com/product/synthetic-monitoring/?utm_medium=organic-social&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel"&gt;we’ll run your Playwright tests for you&lt;/a&gt;. 😉&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>Playwright Tips From the Checkly Community</title>
      <dc:creator>Stefan Judis</dc:creator>
      <pubDate>Wed, 01 Feb 2023 12:38:26 +0000</pubDate>
      <link>https://dev.to/checkly/playwright-tips-from-the-checkly-community-d96</link>
      <guid>https://dev.to/checkly/playwright-tips-from-the-checkly-community-d96</guid>
      <description>&lt;p&gt;The Checkly community recently came together to talk Playwright &lt;a href="https://www.checklyhq.com/slack?utm_medium=referral&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel&amp;amp;utm_content=playwright-ama"&gt;in our public Slack&lt;/a&gt;! My fellow Playwright ambassador &lt;a href="https://www.linkedin.com/in/linkedjohnhill/" rel="noopener noreferrer"&gt;John Hill&lt;/a&gt; and I invited everyone to ask questions and share tips about Microsoft’s stellar end-to-end testing tool. &lt;/p&gt;

&lt;p&gt;And there were plenty of them! Let’s have a look at my favorite questions (and learnings) coming from the community! Are you ready for a mixed bag of Playwright tricks? Let’s go!&lt;/p&gt;

&lt;h2&gt;
  
  
  How should you handle parallelism in your end-to-end tests?
&lt;/h2&gt;

&lt;p&gt;In general, it’s best practice to parallelize as many tests as possible to avoid seeing your test duration going through the roof. No one likes to wait thirty minutes for a green deployment light and &lt;strong&gt;a test suite that takes forever (or flakiness) is the main reason your test efforts fail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But as usual in software development, things are complicated and every complex project comes with its own challenges when it comes to testing. &lt;/p&gt;

&lt;p&gt;A tricky case is test cases that involve resource updates. If all your tests run in parallel it’s easy to discover race conditions where one test creates or deletes a resource while another one is testing it, too! This problem is tricky to solve and there is no ideal solution but &lt;a href="https://twitter.com/tim_nolet/" rel="noopener noreferrer"&gt;our CTO Tim Nolet&lt;/a&gt; shared a possible approach:&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%2Fd736pp7ejkld88cect4m.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%2Fd736pp7ejkld88cect4m.png" alt="There is not one easy fix here. One thing we do with E2E tests here at Checkly is: 1) run non-destructive tests in full parallel. These can be unit tests but also E2E tests that don't rely on state. 2) run destructive / stateful tests separately and strictly serial. You can speed this up by using sharding and using a shard indicator to create / destroy any databases you might spin up." width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For non-destructive tests that are not relying on a state in your database, Tim recommends going all in with parallel testing. Your test cases could range from checking that a modal pops up after a button click or previews that are rendered when someone interacts with a text field. All these tests can run independently and in parallel.&lt;/p&gt;

&lt;p&gt;With stateful tests, it’s a different story, though. If resource creation and updates are at play, it’s sometimes unavoidable to run tests sequentially to avoid one test messing with another one.&lt;/p&gt;

&lt;p&gt;But how do you separate and run these different tests in your Playwright project?&lt;/p&gt;

&lt;p&gt;To implement Tim’s recommendation in your project, you have to familiarize yourself with how Playwright handles parallelism.&lt;/p&gt;

&lt;p&gt;In general, &lt;strong&gt;Playwright runs different test files in parallel, and all tests in a file are run sequentially&lt;/strong&gt;. But watch out, the &lt;code&gt;fullyParallel&lt;/code&gt; global config changes this behavior and runs every test case in parallel regardless of where it’s defined.&lt;/p&gt;

&lt;p&gt;The default behavior lets you run tests that are in a single file sequential. For example, if you want to create, update and delete resources after another, having tests in a single file guarantees execution order. &lt;/p&gt;

&lt;p&gt;That’s a good start, but unfortunately, it doesn’t guarantee that other spec files aren’t interfering with a similar resource and this again could lead to false positives. Luckily Playwright is highly configurable and you can disable parallelism via &lt;a href="https://playwright.dev/docs/test-parallel#disable-parallelism" rel="noopener noreferrer"&gt;the CLI or Playwright config&lt;/a&gt; by defining the number of workers running your tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The global &lt;code&gt;workers&lt;/code&gt; configuration enables you to run tests with different parallelization settings. Independent and stateless tests can be run fully parallel whereas sequential state-dependent tests can run in a sequence.&lt;/p&gt;

&lt;p&gt;To split tests, &lt;a href="https://playwright.dev/docs/test-annotations" rel="noopener noreferrer"&gt;leverage custom annotations&lt;/a&gt; or file path conventions to run Playwright in full parallelized or sequential mode. The example &lt;code&gt;package.json&lt;/code&gt; below leverages different directories to separate parallel from sequential test cases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"parallel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:parallel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx playwright test tests/parallel/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:sequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx playwright test tests/sequence/ --workers=1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@playwright/test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.29.2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, you can run &lt;code&gt;npm run test:parallel&lt;/code&gt; and &lt;code&gt;npm run:sequence&lt;/code&gt; without running into test conflicts. By separating the stateless and stateful tests, you’ll be able to reduce test collisions and run independent tests as quickly as possible.&lt;/p&gt;

&lt;p&gt;But end-to-end tests that interact with the same resources are only one cause for flakiness. There are plenty of others!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are common reasons for flaky tests and how can you avoid them?
&lt;/h2&gt;

&lt;p&gt;There are many reasons for flaky tests. &lt;a href="https://twitter.com/rag0g" rel="noopener noreferrer"&gt;Giovanni Rago&lt;/a&gt;, our Head of Customer Solutions, shared his top flakiness offenders.&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%2Fnp95fv4spv1w4q1ki1k5.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%2Fnp95fv4spv1w4q1ki1k5.png" alt="I'd say my top 3 in causes for flakiness is as follows: 1. scripts with sub optimal waiting (yes, there's plenty of cases out there for using explicit waiting, unfortunately) 2. non-deterministic behaviour or similar curve balls the target system throws your way 3. test data or test setup issues" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As Gio states, suboptimal waiting is a common problem in UI and end-to-end testing. &lt;/p&gt;

&lt;p&gt;To test your application it has to have reached a particular state, JavaScript probably needs to be loaded or potential API calls had to resolve. There are multiple ways to go about testing your UIs.&lt;/p&gt;

&lt;p&gt;But before going into explicit waiting scenarios, make sure to check Playwright’s &lt;a href="https://www.youtube.com/watch?v=j-QLpb6Tmg0&amp;amp;list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc&amp;amp;index=5" rel="noopener noreferrer"&gt;auto-waiting mechanisms&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=j-QLpb6Tmg0&amp;amp;list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc&amp;amp;index=5" rel="noopener noreferrer"&gt;web-first assertions&lt;/a&gt;. With all the built-in auto-waiting mechanisms you rarely need to worry about explicit waiting. For example, if you want to test a UI flow that includes a component that only becomes visible eventually, Playwright actions such as &lt;code&gt;click&lt;/code&gt; wait for elements to become visible.&lt;/p&gt;

&lt;p&gt;These features enable you to test highly asynchronous UIs with code that looks synchronous.&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="c1"&gt;// Playwright retries to click this button until it works or times out&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;getByRole&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="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But as Gio says, sometimes these built-in waiting mechanisms aren’t enough and you have to wait for an explicit event to happen or a UI state to be reached. John Hill takes things to another level and recommends watching specific network requests to make functionality deterministic. &lt;/p&gt;

&lt;p&gt;If you click a button, that triggers an API call, you can wait for the API call to resolve and test if the UI reacted accordingly. &lt;/p&gt;

&lt;p&gt;As John states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your browser has a HTTP API Backend, the #1 way to add determinism to your tests is to wait on the specific network requests to resolve.&lt;br&gt;
99% of the flake I’ve experienced when testing a flaky frontend app comes from a flaky and unreliable backend. &lt;br&gt;
&lt;strong&gt;The frontend tests are just the bearer of bad news.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to check an example of this approach, &lt;a href="https://github.com/akhenry/openmct-yamcs/blob/80b9d96979a27fec4f60e1cc3f1c84b4162eab65/tests/e2e/yamcs/network.e2e.spec.js#L33" rel="noopener noreferrer"&gt;here’s the code John and the team at Open MCT run to test UIs request-dependent at Nasa&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can you emulate custom keyboard interactions in Playwright?
&lt;/h2&gt;

&lt;p&gt;If you’re working on a SaaS product and aim for stellar UX, you might implement keyboard shortcuts and interactions. These could be challenging to test, but luckily, Playwright also supports a way to control a virtual keyboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://playwright.dev/docs/api/class-keyboard" rel="noopener noreferrer"&gt;Playwright’s keyboard functionality enables you to interact with input elements and pages&lt;/a&gt; like a real user.&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="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Meta+KeyA&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="nx"&gt;keyboard&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="s1"&gt;This is a line of text&lt;/span&gt;&lt;span class="se"&gt;\n&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;This functionality is great for shortcuts but also valuable for highly custom and complex components. One example is a JS-based code editor like Monaco.&lt;/p&gt;

&lt;p&gt;Millions of developers use Monaco because it’s built into VS Code (and app.checklyhq.com 😉). But how does it work under the hood and how would you test a custom editor if it’s embedded in your application?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://microsoft.github.io/monaco-editor/playground.html" rel="noopener noreferrer"&gt;Open the Monaco Playground&lt;/a&gt; to look at Microsoft’s open-source editor component. When you inspect the editor, you’ll see that to offer syntax highlighting, there are divs and spans all over the editor component. But how could you edit and test these and interact with the editor?&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%2Fhtuoiniw65s5raa21h5a.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%2Fhtuoiniw65s5raa21h5a.png" alt="Monaco editor with DevTools" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Monaco and other code editor components usually include a hidden or transparent textarea to catch keyboard events and make things more accessible. The textarea keystrokes are captured, canvas elements render code suggestions, and spans/divs display highlighted code. There’s a lot of magic involved and it’s pretty challenging to test.&lt;/p&gt;

&lt;p&gt;Luckily, Playwright’s virtual keyboard helps out here and as the Playwright core team member Max Schmitt shared, &lt;a href="https://github.com/microsoft/playwright/issues/14126" rel="noopener noreferrer"&gt;&lt;code&gt;page.keyboard&lt;/code&gt; is at your service to interact with Monaco&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;goto&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://microsoft.github.io/monaco-editor/playground.html&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;monacoEditor&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;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.monaco-editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;nth&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;monacoEditor&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="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;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Meta+KeyA&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="nx"&gt;keyboard&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="s1"&gt;This is a line of text&lt;/span&gt;&lt;span class="se"&gt;\n&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="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;png&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This functionality allows testing and simulating keyboard interactions going beyond filling an input with a string value. Press specific key combinations, jump to the beginning of a line, or hit the “delete” key three times… It’s up to you to mimic your users’ keyboard behavior!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Related Reading: &lt;a href="https://blog.checklyhq.com/customizing-monaco/?utm_medium=referral&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel&amp;amp;utm_content=playwright-ama"&gt;How we added custom languages, code completion and highlighting to the Monaco editor&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real users are an unpredictable bunch, though. How can you guarantee that your application isn’t breaking when you “attack” it with your end-to-end tests?&lt;/p&gt;

&lt;h2&gt;
  
  
  How can you catch JavaScript errors in long end-to-end testing flows?
&lt;/h2&gt;

&lt;p&gt;It’s always good to monitor if your application throws JavaScript exceptions when users interact with it. Luckily, reacting to JavaScript errors is straightforward in Playwright by leveraging emitted page events such as &lt;code&gt;pageerror&lt;/code&gt;. React and count exceptions with a few lines of code.&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="c1"&gt;// Log all uncaught errors to the terminal&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;pageerror&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exception&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="s2"&gt;`Uncaught exception: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exception&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Related video: if you want to learn how these work, &lt;a href="https://www.youtube.com/watch?v=fYDgtLpRhq0&amp;amp;list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc&amp;amp;index=14" rel="noopener noreferrer"&gt;have a look at one of our Playwright tips on YouTube&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;John Hill again puts things to the next level and shared how &lt;a href="https://github.com/nasa/openmct/blob/70074c52c85b728e7d2ef7c623943ba76e8a8089/e2e/baseFixtures.js#L130-L154" rel="noopener noreferrer"&gt;they implement JavaScript error tracking with Playwright fixtures&lt;/a&gt;. His approach allows you to collect all JS errors until the end of your test and only fail it then.&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="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="na"&gt;failOnConsoleError&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="na"&gt;option&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="cm"&gt;/**
    * Extends the base page class to enable console log error detection.
    * @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion}
    */&lt;/span&gt;
   &lt;span class="na"&gt;page&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="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;failOnConsoleError&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&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;// Capture any console errors during test execution&lt;/span&gt;
       &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&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;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;console&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;msg&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;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
       &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&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="c1"&gt;// Assert against console errors during teardown&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;failOnConsoleError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;soft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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="s2"&gt;`Console error detected: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;_consoleMessageToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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="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;The example leverages fixtures and soft assertions and if you’re curious, have a look at &lt;a href="https://github.com/nasa/openmct/blob/70074c52c85b728e7d2ef7c623943ba76e8a8089/e2e/baseFixtures.js#L130-L154" rel="noopener noreferrer"&gt;John’s provided example code&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;These were my favorite Playwright learnings from our Community AMA session and I continue to be amazed at how far end-to-end testing has come. If you want to keep learning about Playwright, &lt;a href="https://www.youtube.com/watch?list=PLMZDRUOi3a8NtMq3PUS5iJc2pee38rurc" rel="noopener noreferrer"&gt;check out our Checkly YouTube channel&lt;/a&gt; where I share all these nitty-gritty tricks as I discover them. And if you have any questions, &lt;a href="https://www.checklyhq.com/slack?utm_medium=referral&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel&amp;amp;utm_content=playwright-ama"&gt;don’t hesitate to drop into Slack, I’m happy to help&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;And lastly, you might know that testing is only the beginning of shipping stellar products. If you want to start monitoring your apps and be confident that your site works at all times, this is what we do at Checkly. &lt;a href="https://www.checklyhq.com/?utm_medium=referral&amp;amp;utm_source=dev.to&amp;amp;utm_campaign=devrel&amp;amp;utm_content=playwright-ama"&gt;Sign up and run Playwright tests at any time from around the world&lt;/a&gt;. It’s free and pretty cool, trust me! 😉&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>playwright</category>
      <category>testing</category>
    </item>
    <item>
      <title>How low-level API calls can stabilize your end-to-end tests</title>
      <dc:creator>Nico Domino</dc:creator>
      <pubDate>Tue, 21 Jun 2022 14:57:44 +0000</pubDate>
      <link>https://dev.to/checkly/how-low-level-api-calls-can-stabilize-your-end-to-end-tests-1o8m</link>
      <guid>https://dev.to/checkly/how-low-level-api-calls-can-stabilize-your-end-to-end-tests-1o8m</guid>
      <description>&lt;p&gt;We're heavy end-to-end monitoring users here at Checkly and always experiment with how to architect our tests the best way. Over the past months, we've settled on a few workflows that make it much easier to spin up new tests, avoid code duplication, and make the entire test setup easier to manage.&lt;/p&gt;

&lt;p&gt;One of those strategies is to strictly separate concerns in our tests.&lt;/p&gt;

&lt;p&gt;For example, instead of having one check that creates and deletes a resource in the UI, we've split these actions into multiple test runs. One browser check tests the resource creation, and another one the resource deletion. But then, if one test is restricted to resource creation, when do you delete and clean up the resource? And also, if a test only checks the deletion, where do you create the resource in the first place?&lt;/p&gt;

&lt;p&gt;This situation is when working on the API level helps. &lt;strong&gt;We introduced a custom API client to prepare and tear down all the UI test cases using the underlying API.&lt;/strong&gt; A few lines of code speeded up the process and made tests more reliable.&lt;/p&gt;

&lt;p&gt;We successfully use API calls in our UI tests to execute any prerequisite and postrequisite actions. Below we'll go into more detail on how we split up a large Playwright test codebase and how an API client helped us get there!&lt;/p&gt;

&lt;p&gt;Sounds intriguing? Read on!&lt;/p&gt;

&lt;h2&gt;
  
  
  Plain API calls in end-to-end browser tests
&lt;/h2&gt;

&lt;p&gt;Previously, our Playwright UI tests would create a Checkly dashboard and delete it all in one browser session. This structure led to issues where errors in the "create" phase would immediately cause the test to fail and not execute the test's "delete" portion. We littered our test account with dummy dashboards. To solve this problem, we split our "create" and "delete" tests into separate check runs.&lt;/p&gt;

&lt;p&gt;But by decoupling these functionalities, we couldn't rely on a particular setup or clean-up while running the tests. For example, the "delete" test won't garbage collect the "create" test, and the "create" test won't generate entities for the "delete" test to remove.&lt;/p&gt;

&lt;p&gt;What if we could work on the API directly to ensure our browser checks are running correctly without leaving traces behind? We introduced a custom API client to stabilize our Playwright tests.&lt;/p&gt;

&lt;p&gt;Below is an excerpt of our "Delete Dashboard'' check. The test imports &lt;code&gt;checklyApi&lt;/code&gt; from our &lt;code&gt;simpleChecklyApiClient&lt;/code&gt; snippet and uses the API client to create a Checkly dashboard so that we can test its deletion in the UI.&lt;/p&gt;

&lt;p&gt;This way, the check focuses on one functionality in a nice and clean way!&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="c1"&gt;// DELETE DASHBOARD&lt;/span&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;checklyApi&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;./snippets/simpleChecklyApiClient&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dashboardId&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create a dashboard via API, to be deleted right after&lt;/span&gt;
    &lt;span class="nx"&gt;dashboardId&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;checklyApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dashboards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;customUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;https://customdashboard.example.com&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dashboard Creation E2E Test&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;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;// Try to delete the dashboard using Playwright within a browser session&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error deleting dashboard&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;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// If anything fails in that process,&lt;/span&gt;
    &lt;span class="c1"&gt;// we explicitly delete the dashboard to ensure&lt;/span&gt;
    &lt;span class="c1"&gt;// this Check has no left-over side effects&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;dashboardId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;checklyApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dashboards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dashboardId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what happens if the end-to-end test fails to delete the dashboard?&lt;/p&gt;

&lt;p&gt;We introduced a &lt;code&gt;finally&lt;/code&gt; block to ensure that even if something goes wrong, we'll always clean up the test run by attempting to delete the dashboard with our API client.&lt;/p&gt;

&lt;h2&gt;
  
  
  A custom Node.js API wrapper
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;checklyApi&lt;/code&gt; is a JavaScript class wrapper around &lt;a href="https://www.npmjs.com/package/axios" rel="noopener noreferrer"&gt;axios&lt;/a&gt;. It includes methods for HTTP verbs that are preconfigured with our &lt;code&gt;baseUrl&lt;/code&gt; and an API key. The object also comes with additional convenience methods that target explicit resources in our API (i.e. &lt;code&gt;/checks&lt;/code&gt;, &lt;code&gt;/check-groups&lt;/code&gt;, etc.). The client is easy to use and tailored to our test scenarios.&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="c1"&gt;// CHECKLY API HELPER&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&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;API_ROOT&lt;/span&gt; &lt;span class="o"&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;E2E_API_ROOT_PRODUCTION&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&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;E2E_CHECKLY_API_KEY_PRODUCTION&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ACCOUNT_ID&lt;/span&gt; &lt;span class="o"&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;E2E_CHECKLY_ACCOUNT_ID_PRODUCTION&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_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="s2"&gt;X-Checkly-Account&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChecklyApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;buildPath&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_ROOT&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;`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;requestMethodWithNoData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&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="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;axios&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;method&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;buildPath&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="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;requestMethodWithData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;axios&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;method&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;buildPath&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="nx"&gt;data&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;async&lt;/span&gt; &lt;span class="nf"&gt;$get&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="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;requestMethodWithNoData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;config&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="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;$post&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;requestMethodWithData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&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="c1"&gt;// More convenience HTTP Methods&lt;/span&gt;

  &lt;span class="nx"&gt;dashboards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/v1/dashboards`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dashboardId&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/v1/dashboards/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dashboardId&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="p"&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;checklyApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChecklyApi&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;Having all the API client logic centralized in this helper class allows us to make adjustments in a single place and reuse the client across all of our checks. Like in the "&lt;strong&gt;Delete Dashboard&lt;/strong&gt;" example check above, the usage is as simple as:&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="nx"&gt;checklyApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dashboards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dashboardId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for completeness, here is the opposite check "&lt;strong&gt;Create Dashboard&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="c1"&gt;// CREATE DASHBOARD&lt;/span&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;checklyApi&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;./snippets/simpleChecklyApiClient&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;dashboardUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://testdashboard.example.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;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;let&lt;/span&gt; &lt;span class="nx"&gt;dashboardIdToDelete&lt;/span&gt;

  &lt;span class="k"&gt;try&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;// Navigate the UI and try to create a dashboard&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="c1"&gt;// Retrieve check ID from the creation response triggered by UI interactions&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dashboardId&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="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;waitForResponse&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="k"&gt;return &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="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nx"&gt;response&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;method&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;POST&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;dashboardIdToDelete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dashboardId&lt;/span&gt;

    &lt;span class="c1"&gt;// Dashboard should be visible in the list&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;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`a[href="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dashboardUrl&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="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &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="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;error&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&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;dashboardIdToDelete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Housekeeping, delete the created dashboard via API&lt;/span&gt;
      &lt;span class="nx"&gt;checklyApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dashboards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dashboardId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both Playwright tests leverage plain API calls to set up and tear down, but there's a crucial difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to extract resource ids using Playwright request interception
&lt;/h2&gt;

&lt;p&gt;Combining API calls with end-to-end testing can sometimes be challenging. Setup steps are generally easier to realize than teardowns. But why's that?&lt;/p&gt;

&lt;p&gt;When you create resources on the API level, you're in control of the data, requests and responses. But if you want to clean up and delete resources created in a browser session, this data might not be available to you.&lt;/p&gt;

&lt;p&gt;This is when &lt;a href="https://www.checklyhq.com/learn/headless/request-interception/" rel="noopener noreferrer"&gt;Playwright request interceptors&lt;/a&gt; shine!&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="c1"&gt;// Retrieve check ID from the creation response&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dashboardId&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="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;waitForResponse&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="k"&gt;return &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="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;response&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;method&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;POST&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We run our UI tests as usual and listen to our app requests using waitForResponse to evaluate what resources need to be deleted in the teardown phase. By monitoring the network activity, we can snatch all the required resource ids and delete anything created in your Playwright test using the API.&lt;/p&gt;

&lt;p&gt;There are no traces, no matter if the UI test fails or succeeds!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Writing fast and stable end-to-end tests can be a real challenge, but keeping tests lean and separating them into smaller chunks helps us to keep our test coverage high without slowing us down.&lt;/p&gt;

&lt;p&gt;Our tests are now easier to debug, they share more code, and everything runs faster and in parallel. And even though making API calls in our end-to-end tests felt odd initially, &lt;strong&gt;we're not creating sequential test waterfalls anymore, thanks to a tiny Node.js API wrapper.&lt;/strong&gt; Most of our Playwright tests are idempotent, so that we can run them continuously. And the best part: we're sure everything's cleaned up, no matter how many times we run our tests. This is how it should be!&lt;/p&gt;

&lt;p&gt;This practice is only the tip of the iceberg. We're continuously improving our monitoring setup and will share more tricks here on the blog. If you want to learn more about end-to-end monitoring, &lt;a href="https://www.checklyhq.com/guides/end-to-end-monitoring" rel="noopener noreferrer"&gt;we've plenty of other resources available&lt;/a&gt;. Until then, happy testing!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>playwright</category>
      <category>testing</category>
    </item>
    <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>
  </channel>
</rss>
