<?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: Peter Perlepes</title>
    <description>The latest articles on DEV Community by Peter Perlepes (@igneel64).</description>
    <link>https://dev.to/igneel64</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F86953%2F180d0595-46c1-4972-a7f9-cb5652e5c3e9.jpg</url>
      <title>DEV Community: Peter Perlepes</title>
      <link>https://dev.to/igneel64</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/igneel64"/>
    <language>en</language>
    <item>
      <title>Row-level access for your Airtable-powered application with Clerk</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Tue, 31 Aug 2021 06:27:01 +0000</pubDate>
      <link>https://dev.to/clerk/row-level-access-for-your-airtable-powered-application-with-clerk-2ib6</link>
      <guid>https://dev.to/clerk/row-level-access-for-your-airtable-powered-application-with-clerk-2ib6</guid>
      <description>&lt;h3&gt;
  
  
  Haven't you heard about Airtable ? 🤔
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://airtable.com/"&gt;Airtable&lt;/a&gt; is an online platform to access, manage, and collaborate on relational or spreadsheet-like information. The folks at Airtable have done an amazing job on both the user experience and the technical aspects of the product. As a no-code tool – for most use cases – it can help with your whole team’s efficiency around data management.&lt;/p&gt;

&lt;p&gt;Airtable is a great choice as a database for any kind of resource you want to serve in an application, but lacks the granular access management capabilities that most web applications need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apartment Hunting Application 🏘
&lt;/h2&gt;

&lt;p&gt;As an example, consider an apartment hunting application where realtors need to add and manage the most attractive apartments for each of their clients. Each apartment will be listed in a single table, and you need to make sure that clients can only access the apartments selected for them. To achieve that, we can leverage some Clerk magic to provide &lt;strong&gt;authenticated user access&lt;/strong&gt; to only certain rows in your Airtable apartment hunt database.&lt;/p&gt;

&lt;p&gt;The full code repository can be found in the &lt;a href="https://github.com/clerkinc/clerk-airtable-apartment-hunt"&gt;clerk-airtable-apartment-hunt&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup for the Apartment Hunt 🛠
&lt;/h2&gt;

&lt;p&gt;To kickstart the apartment hunt project, you can start by creating your Airtable account and then use the &lt;a href="https://airtable.com/templates/everyday-life/expPfTzGnfpwjgWlS/apartment-hunting"&gt;Apartment Hunting Template&lt;/a&gt; from the template gallery.&lt;/p&gt;

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

&lt;p&gt;In the created dataset you will need to add a column that represents the email that the Apartment has been assigned to. Go ahead and create the Email column of type “Email”.&lt;/p&gt;

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

&lt;p&gt;Setting the column's type to Email adds more capabilities to the field, including validation that the email address is valid.&lt;/p&gt;

&lt;p&gt;For the sake of our example, you can go ahead and fill the Email column with the email address you will use to access your Apartment Hunting application. For me, it's &lt;a href="mailto:peter@clerk.dev"&gt;peter@clerk.dev&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Clerk application 🥊
&lt;/h2&gt;

&lt;p&gt;If you are new to Clerk you will need to &lt;a href="https://accounts.clerk.dev/sign-up"&gt;create an account&lt;/a&gt; on our platform, then follow the steps to create a new application.&lt;/p&gt;

&lt;p&gt;After you create an account and a new application for this example, you can move on to the repository setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;p&gt;To run the full example locally, you will need to follow a few small steps. First, go ahead and clone the example application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/clerkinc/clerk-airtable-apartment-hunt.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go inside your project folder and copy the &lt;code&gt;.env.example&lt;/code&gt; file to a &lt;code&gt;.env.local&lt;/code&gt; file.&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="nb"&gt;cp&lt;/span&gt; .env.example .env.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Clerk Environment Variables
&lt;/h3&gt;

&lt;p&gt;You will need the Frontend API value which can be found on the &lt;a href="https://dashboard.clerk.dev/"&gt;dashboard&lt;/a&gt; on your development instance's home page. Set this value as the &lt;code&gt;NEXT_PUBLIC_CLERK_FRONTEND_API&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next you will need the Clerk API key which can also be found on your dashboard under &lt;strong&gt;Settings ➜ API keys&lt;/strong&gt;. Add that as &lt;code&gt;CLERK_API_KEY&lt;/code&gt; in your &lt;code&gt;.env.local&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Finally your .env.local file should look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AIRTABLE_API_KEY=keyojbaeZ5KBe9JMR
AIRTABLE_BASE_ID=appBMXDYAGWAgvH8S
NEXT_PUBLIC_CLERK_FRONTEND_API=clerk.2ct1o.leet.lcl.dev
CLERK_API_KEY=test_avDIYjpk0SqaTGF1Wx8MdrEHZIkg2zSObU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you just need to install the project dependencies with &lt;code&gt;yarn install&lt;/code&gt; inside the project folder, then &lt;code&gt;yarn dev&lt;/code&gt; to start the application locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Clerk provides authenticated access to your data 🔐
&lt;/h2&gt;

&lt;p&gt;To authorize Airtable data access with Clerk, we introduce a thin and customizable access management layer over the Airtable API in our backend.*&lt;/p&gt;

&lt;p&gt;&lt;em&gt;*The Airtable Rest API does not restrict us from calling it directly from the browser, but it is not recommended since we would need expose sensitive information. For more information, please see this community &lt;a href="https://community.airtable.com/t/can-i-use-the-airtable-api-securely-from-the-browser/28810"&gt;forum answer&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the Apartment Hunting application, &lt;code&gt;@clerk/nextjs&lt;/code&gt; takes care of the frontend of user authentication. For apartment data access, we use Next.js API routes to interact with the Airtable API in a secure manner. These routes use &lt;code&gt;@clerk/nextjs/api&lt;/code&gt; to determine the signed in user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only showing apartments assigned to the current user 🙋
&lt;/h3&gt;

&lt;p&gt;To make sure users only have access to the properties assigned to them, we create a &lt;code&gt;/api/apartments&lt;/code&gt; endpoint to fetch this information. The code for this endpoint can be seen below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WithSessionProp&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NextApiRequest&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;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&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="cm"&gt;/** 
       * Get the user email from the userId attached on the request.
       */&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&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;user&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;ClerkInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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;primaryEmailAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emailAddresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailAddress&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;emailAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primaryEmailAddressId&lt;/span&gt;
        &lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;emailAddress&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="cm"&gt;/** Use the email to retrieve the assigned apartments. */&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apartments&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;getApartmentsByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;primaryEmailAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apartments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;res&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="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&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="cm"&gt;/** 
 * Only allow authenticated access or respond with status code 403 Forbidden.
 * Add the req.session attribute on the NextApiRequest object
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;requireSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;requireSession&lt;/code&gt; helper guarantees that an authenticated user is accessing the endpoint, and also populates &lt;code&gt;req.session&lt;/code&gt; attribute on the request object coming from Next.js.&lt;/p&gt;

&lt;p&gt;In this endpoint, we retrieve the primary email address of the authenticated user and use it to fetch only apartments assigned to this email. Here, we only check for the primary email address of the user, but since Clerk also supports multiple email addresses per account, you could adjust the logic accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only allow assigned users to modify the apartment status 🙅
&lt;/h3&gt;

&lt;p&gt;In a similar manner, we want to restrict editing the apartment status to only the assigned user. The logic for restricting that access can be seen below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WithSessionProp&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NextApiRequest&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;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PUT&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;apartment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="cm"&gt;/** We make sure prevent a user with different account to update the visitation status. */&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&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;ClerkInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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;primaryEmailAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emailAddresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailAddress&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;emailAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primaryEmailAddressId&lt;/span&gt;
      &lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;emailAddress&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="cm"&gt;/** We check if the persisted apartment email matches the requesters. */&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;persistedApartment&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;getApartmentById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apartment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


      &lt;span class="cm"&gt;/** If the emails do not match, return 401 Unauthorized */&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;primaryEmailAddress&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;persistedApartment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;res&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="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;break&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;results&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;updateApartment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apartment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;res&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="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;break&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;requireSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the same manner as the apartment fetch, we only allow authenticated access by using the &lt;code&gt;requireSession&lt;/code&gt; middleware. We perform an extra check with the signed in user's email address to ensure they are assigned to the apartment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap 🏖
&lt;/h2&gt;

&lt;p&gt;This was just a simple example of how Clerk can be used to add row-level access an to application that uses Airtable as it's database. While we built this example, we were really impressed with how powerful Airtable can be at managing project data, with little to no code involved.&lt;/p&gt;

&lt;p&gt;In the same manner, Clerk abstracts away the intricacies of authentication and user management, allowing a robust solution to be deployed with little code, and users to managed with no code through our dashboard.&lt;/p&gt;

&lt;p&gt;If you have any feedback, are running into trouble, or just want to share what you've built - we'd love to hear from you! Reach out to us on Twitter &lt;a href="https://twitter.com/ClerkDev"&gt;@ClerkDev&lt;/a&gt;, on our community &lt;a href="https://discord.gg/hF9U4sHN"&gt;Discord server&lt;/a&gt;, or through any of our &lt;a href="https://www.clerk.dev/support"&gt;support channels&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
      <category>javascript</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Build a premium recipes app with Clerk and Firebase🔥</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Mon, 23 Aug 2021 16:34:56 +0000</pubDate>
      <link>https://dev.to/clerk/build-a-premium-recipes-app-with-clerk-and-firebase-2e35</link>
      <guid>https://dev.to/clerk/build-a-premium-recipes-app-with-clerk-and-firebase-2e35</guid>
      <description>&lt;p&gt;&lt;em&gt;Firebase is among the top Platform-as-a-Service (PaaS) providers for web and mobile applications. It packs tons of powerful and well designed features for developers to spin up a fully fledged application with minimal effort, like storage, analytics, and authentication.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Clerk integrates directly with Firebase, so developers can easily add our beautiful Sign Up, Sign In, and User Profile UIs to their Firebase application. The integration allows developers to use all the full feature set of Firebase without compromising on user management.&lt;/p&gt;

&lt;p&gt;In this post, we are going to show you a full example integrating Clerk with Firebase, to make a "premium" recipe showcase application. The recipe data is going to be stored in Firebase Firestore and will only be available to authenticated users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---6WC_uEa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3u5r6zrb9j3rhe869mr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---6WC_uEa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3u5r6zrb9j3rhe869mr.png" alt="Recipes app" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full code for this guide is available in the &lt;a href="https://github.com/clerkinc/clerk-firebase-starter"&gt;clerk-firebase-starter&lt;/a&gt; repository, and includes instructions for how to set up Firebase and connect it to Clerk. The application demo is live at &lt;a href="https://fir-clerk.web.app/"&gt;https://fir-clerk.web.app/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you would like to read the documentation before getting started, please refer to our &lt;a href="https://docs.clerk.dev/frontend/integrations/firebase"&gt;Firebase integration documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the Firebase project 🏕
&lt;/h2&gt;

&lt;p&gt;To start off, we need a Firebase Web project. Go to the &lt;a href="https://console.firebase.google.com/"&gt;Firebase Console&lt;/a&gt; and create a new project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ktCN0hhO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsc5vrizeo0c4nusa0to.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ktCN0hhO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsc5vrizeo0c4nusa0to.png" alt="new project" width="333" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After giving it a valid name and confirming, you will find yourself in the Firebase dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Firestore and adding recipes 🍳
&lt;/h2&gt;

&lt;p&gt;From the Firebase dashboard, you can go ahead and create a new Firestore Database for our example project. The Firestore instance will serve as our database where we will store and retrieve our recipes. If you want to learn more about Firestore, you can take a look at the starter &lt;a href="https://firebase.google.com/docs/firestore"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--086tLuNz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/geqxnsfqrg4g1l0tq7gn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--086tLuNz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/geqxnsfqrg4g1l0tq7gn.gif" alt="project creation" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During the database instance creation, you should choose the storage location somewhere close to your users. Also keep in mind that by selecting the production mode ruleset, by default you have disabled any reads/writes to your database from outside the platform. We are gonna change that right after!&lt;/p&gt;

&lt;p&gt;If you are not familiar with Firebase &lt;a href="https://firebase.google.com/docs/rules"&gt;Security Rules&lt;/a&gt;, they are basically a Domain-specific language to limit the access to important data in Firebase storage solutions. As we mentioned previously, since these are premium recipes, only authenticated users will be allowed to view them.&lt;/p&gt;

&lt;p&gt;To allow authenticated users to read any database but not write, you can use the security rule shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rules_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firestore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/documents &lt;/span&gt;&lt;span class="err"&gt;{
&lt;/span&gt;    &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;document&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="nx"&gt;allow&lt;/span&gt; &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;if&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;auth&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding recipes 👨‍🍳
&lt;/h2&gt;

&lt;p&gt;To add a few recipes, go ahead and create a &lt;code&gt;recipes&lt;/code&gt; collection with recipes of your liking, but please conform to the same attribute schema shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0qiWvuDW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zjim4kicheliv3bb99ny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0qiWvuDW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zjim4kicheliv3bb99ny.png" alt="recipes data" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it's helpful, here is the TypeScript type for each recipe document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Recipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/** The recipe description */&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/** The amount of guilty you should feel */&lt;/span&gt;
  &lt;span class="nl"&gt;calories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/** Cooking time in minutes */&lt;/span&gt;
  &lt;span class="nl"&gt;cookingTimeMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/** Publicly accessible image full URL */&lt;/span&gt;
  &lt;span class="nl"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/** Number of ingredients needed */&lt;/span&gt;
  &lt;span class="nl"&gt;ingredientsNum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/** The title of the recipe */&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;After adding a few recipes, you are all set from the data side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Firebase Authentication 🔒
&lt;/h2&gt;

&lt;p&gt;Since this is a new project, you will need to enable the Authentication feature. No further action is needed, since Clerk will handle the rest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RHMhw-zb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/626a1pb9xuk58my7b0rh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RHMhw-zb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/626a1pb9xuk58my7b0rh.png" alt="firebase auth" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling the Firebase integration on Clerk 👇
&lt;/h2&gt;

&lt;p&gt;If you are new to Clerk you will need to &lt;a href="https://accounts.clerk.dev/sign-up"&gt;create an account&lt;/a&gt; on our platform, then follow the steps to create a new application.&lt;/p&gt;

&lt;p&gt;After you create an account and a new application for this example, you can follow &lt;a href="https://docs.clerk.dev/frontend/integrations/firebase"&gt;these instructions&lt;/a&gt; to enable the Firebase integration on Clerk for your application instance.&lt;/p&gt;

&lt;p&gt;With that done, you are now able to authenticate Clerk users in your Firebase application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show me the code 👩‍💻
&lt;/h2&gt;

&lt;p&gt;To run the full example locally, you will need to follow a few small steps. First, go ahead and clone our Firebase starter application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:clerkinc/clerk-firebase-starter.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go inside your project folder and copy the .env.example file into a .env.local file.&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="nb"&gt;cp&lt;/span&gt; .env.example .env.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take the Frontend API value which can be found on your &lt;a href="https://dashboard.clerk.dev/"&gt;application dashboard&lt;/a&gt; and add it as the &lt;code&gt;NEXT_PUBLIC_CLERK_FRONTEND_API&lt;/code&gt; value. Your .env.local file should look something like:&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;NEXT_PUBLIC_CLERK_FRONTEND_API&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;clerk.sample.api.lcl.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final configuration step is to replace the firebase.web.ts config file with one from your own Firebase project. You can find a specification for the &lt;a href="https://firebase.google.com/docs/web/setup?sdk_version=v8#config-object"&gt;config object&lt;/a&gt; in Firebase's documentation.&lt;/p&gt;

&lt;p&gt;After you create new Firebase Web project, you will be able to find the required values under &lt;strong&gt;Project settings ➜ General&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Now you are ready to install the dependencies and run your project in development mode. Go to the root directory of the project and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and after completion&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application is now running in your local environment and you can experience the same functionality as the live demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the magic happens 💫
&lt;/h2&gt;

&lt;p&gt;If you take away the application setup, the integration is seamless and works out of the box with just a few copy &amp;amp; paste steps across Clerk and Firebase. Here's how it works:&lt;/p&gt;

&lt;p&gt;Let us go over the way the integration works in your web application code and what are the actions you need to authenticate a Firebase user with Clerk.&lt;/p&gt;

&lt;p&gt;The firebase application object houses the &lt;code&gt;.auth()&lt;/code&gt; namespace which includes methods to authenticate a user. One of those methods is &lt;code&gt;signInWithCustomToken&lt;/code&gt;, which allows third-party providers like Clerk to pass authenticated user data to Firebase.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where does this "custom token" come from ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After setting up Firebase integration on Clerk, you can retrieve the necessary "custom token" by calling the &lt;code&gt;getToken&lt;/code&gt; method on the Clerk User object.&lt;/p&gt;

&lt;p&gt;Combined, it's just two lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firebaseToken&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;clerkUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firebase&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;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;signInWithCustomToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firebaseToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From that point on, your user is authenticated and can complete all the actions that require privileges of an identified Firebase user.&lt;/p&gt;

&lt;p&gt;You can see this in action in our &lt;a href="https://github.com/clerkinc/clerk-firebase-starter/blob/main/client/hooks/useRecipes.ts"&gt;useRecipes hook&lt;/a&gt; implementation. If you remove these two lines, the request will fail since the Clerk user will not also be authenticated in Firebase. (Remember, we set a Security Rule - &lt;code&gt;allow read: if request.auth != null;&lt;/code&gt; - which restricts access to authenticated users.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving forward ⚡
&lt;/h2&gt;

&lt;p&gt;This end to end example showed how you can use Clerk and Firebase together for a new web project. Firebase is an exceptional development platform and we are really excited to see what you build with this integration.&lt;/p&gt;

&lt;p&gt;If you have any feedback, and running into trouble, or just want to share what you've built - we'd love to hear from you! Reach out to us on Twitter &lt;a href="https://twitter.com/ClerkDev"&gt;@ClerkDev&lt;/a&gt;, on our community &lt;a href="https://discord.gg/hF9U4sHN"&gt;Discord server&lt;/a&gt;, or through any of our &lt;a href="https://www.clerk.dev/support"&gt;support channels&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>firebase</category>
      <category>authentication</category>
    </item>
    <item>
      <title>How HttpOnly cookies help mitigate XSS attacks 🍪</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Fri, 20 Aug 2021 08:14:08 +0000</pubDate>
      <link>https://dev.to/clerk/how-httponly-cookies-help-mitigate-xss-attacks-14af</link>
      <guid>https://dev.to/clerk/how-httponly-cookies-help-mitigate-xss-attacks-14af</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; HttpOnly cookies do not prevent &lt;a href="https://owasp.org/www-community/attacks/xss/"&gt;cross-site scripting (XSS)&lt;/a&gt; attacks, but they do lessen the impact and prevent the need to sign out users after the XSS is patched. HttpOnly cookies are not a substitute for XSS prevention measures.&lt;/p&gt;

&lt;p&gt;Our very first architecture decision at Clerk was to use HttpOnly cookies for session management. It has long been understood that HttpOnly cookies help mitigate cross-site scripting (XSS) attacks, and we felt it was important to include this best-practice directly in our product.&lt;/p&gt;

&lt;p&gt;But while there's strong consensus that using HttpOnly cookies is a best practice, we've found many developers are unsure of how they help with XSS. We think this stems from the guidance, which often just says what to do rather than explaining why:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A cookie with the HttpOnly attribute is inaccessible to the JavaScript &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie"&gt;Document.cookie API&lt;/a&gt;; it is sent only to the server. For example, cookies that persist server-side sessions don't need to be available to JavaScript, and should have the HttpOnly attribute. This precaution helps mitigate cross-site scripting (XSS) attacks.&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies"&gt;Mozilla MDN Web Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cookies [...] SHOULD be tagged to be inaccessible via JavaScript (HttpOnly).&lt;br&gt;
&lt;a href="https://pages.nist.gov/800-63-3/sp800-63b.html#711-browser-cookies"&gt;NIST 800-63B&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The attack vector ⚔
&lt;/h2&gt;

&lt;p&gt;After reading this guidance, you might be surprised to learn that HttpOnly cookies do not prevent XSS attacks.&lt;/p&gt;

&lt;p&gt;Instead, HttpOnly cookies are helpful when you assume an XSS attack will happen and want to &lt;strong&gt;lessen the impact&lt;/strong&gt;. Ultimately, they mitigate XSS attacks by making it easier for organizations to respond.&lt;/p&gt;

&lt;p&gt;The specific threat HttpOnly cookies protect against is called session &lt;strong&gt;token exfiltration&lt;/strong&gt;, which is a fancy way of saying that the attacker is able to steal a user's session token.&lt;/p&gt;

&lt;p&gt;When a session token is stored in a cookie without the HttpOnly flag, the token can be stolen during an XSS attack with &lt;code&gt;document.cookie&lt;/code&gt;. This is problematic because session tokens are the primary mechanism used by backends to authenticate a user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Once an attacker has access to a session token, they can often act on behalf of a user until that token expires or is revoked&lt;/strong&gt;. Actions can be taken remotely - even if the user is no longer visiting the page with the XSS vulnerability - which can serve to dramatically increase the surface area of the attack.&lt;/p&gt;

&lt;p&gt;Conversely, when a session token is stored in a cookie with the HttpOnly flag, that token cannot be directly exfiltrated during an XSS attack. This minimizes the surface area of the XSS attack and makes it easier for organizations to respond.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responding to XSS attacks - without HttpOnly cookies
&lt;/h2&gt;

&lt;p&gt;When an organization is responding to an XSS attack, the first step is always patching the XSS vulnerability.&lt;/p&gt;

&lt;p&gt;If HttpOnly cookies were not used, organizations should then assume that session tokens were exfiltrated. This means that - even with the XSS vulnerability patched - the attacker may still have the ability to act on behalf of users.&lt;/p&gt;

&lt;p&gt;The next step is revoking the session of any user who may have been subjected to the XSS vulnerability, since those are the users who may have had their session tokens exfiltrated. These users will need to sign in again next time they visit the website.&lt;/p&gt;

&lt;p&gt;Lastly, the organization will need to reverse any actions the attacker took on behalf of their users, from the time the vulnerability began to the time session tokens were revoked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responding to XSS attacks - with HttpOnly cookies 🍪
&lt;/h2&gt;

&lt;p&gt;With HttpOnly cookies, organizations still need to patch the XSS vulnerability and reverse any actions taken on behalf of their users, but they do not need to revoke sessions and ask users to sign in again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about localStorage and sessionStorage? 🤔
&lt;/h2&gt;

&lt;p&gt;Although &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage"&gt;window.localStorage&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage"&gt;window.sessionStorage&lt;/a&gt; are newer client-side storage APIs, they function like cookies without the HttpOnly flag. HttpOnly cookies are still the only standard mechanism for persisting session tokens that cannot be exfiltrated during an XSS attack.&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Server responses so slow the user abandoned! Trace using NEL and an example in Node.js</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Mon, 26 Oct 2020 08:55:34 +0000</pubDate>
      <link>https://dev.to/igneel64/server-responses-so-slow-the-user-abandoned-trace-using-nel-and-an-example-in-node-js-27n1</link>
      <guid>https://dev.to/igneel64/server-responses-so-slow-the-user-abandoned-trace-using-nel-and-an-example-in-node-js-27n1</guid>
      <description>&lt;p&gt;&lt;em&gt;How you can use another relatively new browser capability to retrieve reports on your own endpoints when user abandoned your service due to slow server response&lt;/em&gt; 🤦‍♂️&lt;/p&gt;

&lt;h3&gt;
  
  
  Network Error Logging ? 🤔
&lt;/h3&gt;

&lt;p&gt;If you have never heard about &lt;strong&gt;Network Error Logging&lt;/strong&gt; (&lt;em&gt;NEL&lt;/em&gt;) before and you are working on the web, you might get really excited, as I did when I read the &lt;a href="https://www.w3.org/TR/network-error-logging"&gt;specification&lt;/a&gt; about the capabilities it provides. &lt;/p&gt;

&lt;p&gt;We will not go into an introduction of this specification (&lt;em&gt;in this article&lt;/em&gt;) but just a few words to whet your appetite.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you are interested in the topic and want to follow along, take a look at the specification or introductory articles first.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NEL gives web service operators visibility into problems related to network reliability that the users of those services are experiencing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The most exciting part of NEL in my eyes is the “&lt;strong&gt;users&lt;/strong&gt;“ part. The place where the reports are stored and transmitted over to your NEL collector service is client browser.&lt;/p&gt;

&lt;p&gt;In a more &lt;em&gt;systemic language&lt;/em&gt;, the NEL agent is the user’s browser. That makes NEL reports, the &lt;strong&gt;ground truth about whether problems impact your users&lt;/strong&gt; at any point in the network interactions of an HTTP request.&lt;/p&gt;

&lt;p&gt;An amazing capability without requiring bespoke instrumentation, specialized infrastructure or new tools to get started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vEU-xKKW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6130/1%2A3u4FZUJ3EC77_n1XsYWxfA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vEU-xKKW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/6130/1%2A3u4FZUJ3EC77_n1XsYWxfA.png" alt="1000ft. view of NEL"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The power to monitor “abandonment”
&lt;/h2&gt;

&lt;p&gt;Except for all the TCP, DNS, TLS and HTTP specific errors being reported by NEL, for more than a year now the “&lt;strong&gt;abandonment&lt;/strong&gt;” error type has been available for Chromium based browsers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"abandonment"&lt;/em&gt;, as not yet well described in the official documentation, covers a significant blind spot which, if I can talk in marketing terms, is as low in the funnel as possible regarding a user reaching our product.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;“abandonment”&lt;/em&gt; error type is generated when the user closed the page and the requirements below are met:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Valid, not erroneous(400–500) or redirect, headers are received in the server response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The browser has not managed to fully read the response body from the server.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  In plain English:
&lt;/h3&gt;

&lt;p&gt;For a resource request, &lt;em&gt;let’s take the main document as an example which is probably the most important&lt;/em&gt;, this error report covers cases where all the network infrastructure has done its job but now due to the server or the CDN serving the request being slow &lt;strong&gt;the user has left&lt;/strong&gt;. 🙅‍♀️&lt;/p&gt;

&lt;p&gt;This slowness can most of the times be attributed to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Slow Time To First Byte (&lt;em&gt;ttfb&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Time to generate and transmit the full body response. Server-side rendering, slow database queries are a couple of things that come to mind.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s81D0bEl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4694/1%2ATXlgxhgrQwdrjaCEQtPwyg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s81D0bEl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4694/1%2ATXlgxhgrQwdrjaCEQtPwyg.png" alt="User leaves before response is complete"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Importance 🏆
&lt;/h3&gt;

&lt;p&gt;This spot is of extreme value to web engineers, user behaviour analysts and web performance engineers to say a few.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Client side analytics have no power there as the whole response body is not yet fully transmitted, let alone analytics scripts being run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server logs depending on your infrastructure at the worst case scenario have logged a &lt;em&gt;200&lt;/em&gt; status code or nothing at all*.* When a CDN is serving your pages, you usually don’t even have access to their logs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common ways of monitoring are leaving this range of behaviours not easily traceable but nevertheless crucial to profits.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Utilizing the NEL &lt;strong&gt;abandoned&lt;/strong&gt; error type you can now become aware of this issue and act upon it depending on your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Node.js implementation 👨‍💻
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--belukvE_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ukx3er2wc6p26k5colkx.png" class="article-body-image-wrapper"&gt;&lt;img height="200" width="200" alt="nodejs" src="https://res.cloudinary.com/practicaldev/image/fetch/s--belukvE_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ukx3er2wc6p26k5colkx.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To showcase how you could achieve the NEL reporting functionality and understand which failures are treated as *abandoned, w*e are gonna go about implementing an example Node.js web server using a couple of helper libraries and tools.&lt;/p&gt;

&lt;p&gt;In this example we are going to be using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://expressjs.com/"&gt;Express.js&lt;/a&gt; for the web server framework. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/nodemon"&gt;nodemon&lt;/a&gt; to avoid restarting the local server all the time.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ngrok.com/"&gt;ngrok&lt;/a&gt; for easy access to public secure URL. (&lt;em&gt;NEL does not work for insecure localhost endpoints&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A Simple Web Server
&lt;/h3&gt;

&lt;p&gt;Let’s start by creating our simple web server project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir nel-abandoned &amp;amp;&amp;amp; cd nel-abandoned
$ npm init -y
$ npm install express
$ touch app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Open up *app.js *with your favorite text editor and add the required setup to start an express web server:&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&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;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="cm"&gt;/*
  Allow express to parse the special content type 
  of the NEL report.
 */&lt;/span&gt;
 &lt;span class="nx"&gt;app&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="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&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;application/reports+json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

 &lt;span class="cm"&gt;/* Home route just sending nothing back*/&lt;/span&gt;
 &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&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="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;

 &lt;span class="cm"&gt;/* 
  NEL collector endpoint. 
  In a real environment, the reporting endpoint would be 
  in a completely different server IP, domain and CDN.
 */&lt;/span&gt;
 &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/report&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;// Log the reports received on the terminal &lt;/span&gt;
   &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;

 &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;Listening on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&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;p&gt;Now run your server and ngrok on different terminals.&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;$ &lt;/span&gt;npx nodemon app.js
 // On different terminal now
 &lt;span class="nv"&gt;$ &lt;/span&gt;ngrok http 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To let the clients know that you want to use NEL and report back to a specific endpoint, you want to send to the client the Reporting API/NEL required headers.&lt;/p&gt;

&lt;p&gt;To do that we will create a NelMiddleware that will send to every request the NEL headers we defined back to the client.&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;NelMiddleware&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Report-To&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
       &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;network-errors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="c1"&gt;// Expire in day&lt;/span&gt;
       &lt;span class="na"&gt;max_age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="c1"&gt;// Here use the secure URL you are gonna get from ngrok&lt;/span&gt;
       &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="p"&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="s2"&gt;NGROK_URL/report&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NEL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
       &lt;span class="na"&gt;report_to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;network-errors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="c1"&gt;// Cache the policy for a day&lt;/span&gt;
       &lt;span class="na"&gt;max_age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;86400&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;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="p"&gt;};&lt;/span&gt;

 &lt;span class="cm"&gt;/* Use the middleware before registering the routes */&lt;/span&gt;
 &lt;span class="nx"&gt;app&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="nx"&gt;NelMiddleware&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything went well, trying the &lt;em&gt;home&lt;/em&gt; route of your application and inspecting the DevTools network panel you would be able to see the NEL headers included in the document request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simulating &amp;amp; monitoring an “abandonment” error
&lt;/h3&gt;

&lt;p&gt;To help us in the analysis and simulation of our experiment, we can use the reporting dashboard of ngrok by opening &lt;a href="http://localhost:4040"&gt;http://localhost:4040&lt;/a&gt; where we get a free request traffic inspector. We would be able to inspect the reports posted to our service by the NEL agent later on.&lt;/p&gt;

&lt;p&gt;To simulate an &lt;em&gt;abandonment&lt;/em&gt; case as we described earlier you can try simply adding an artificial delay to the &lt;em&gt;home&lt;/em&gt; route. This would be the case of a slow Time to First Byte.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--65YGSvYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3790/1%2AKopxxef3WeaBnzEr3_QjKQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--65YGSvYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3790/1%2AKopxxef3WeaBnzEr3_QjKQ.png" alt=""&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&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="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the secure ngrok URL on the home route and before the 10 second timeout runs, close the current tab. You can do this a few times to populate more reports.&lt;/p&gt;

&lt;p&gt;In a few minutes you will be seeing either in the ngrok inspector or the console logger of the &lt;em&gt;/report *endpoint, some reports coming from the browser with the error type *&lt;/em&gt;&lt;em&gt;abandoned&lt;/em&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tRs6BX5E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2ABSm2IYarEMXmbw2V6_AOlg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tRs6BX5E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2ABSm2IYarEMXmbw2V6_AOlg.png" alt="Example “abandoned” error report"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Case of not fully delivered response body
&lt;/h3&gt;

&lt;p&gt;The second common case that would trigger the abandonment would be to have a part of the response body being triggered slowly and the user leaving the page before the full completion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HGRxbmDd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3790/1%2Aal8qHWn4ZA6VCMSukRpW0Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HGRxbmDd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3790/1%2Aal8qHWn4ZA6VCMSukRpW0Q.png" alt=""&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="cm"&gt;/* Helper for artificial delay */&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&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="nb"&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="nx"&gt;setTimeout&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="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

 &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&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="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="s2"&gt;text/html&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;
       &amp;lt;!DOCTYPE html&amp;gt;
       &amp;lt;html lang="en"&amp;gt;
         &amp;lt;head&amp;gt;
           &amp;lt;meta charset="UTF-8"&amp;gt;
           &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
         &amp;lt;/head&amp;gt;
       &amp;lt;body&amp;gt;
    &lt;/span&gt;&lt;span class="s2"&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;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;p&amp;gt;Hello World&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&lt;/span&gt;&lt;span class="s2"&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;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;/html&amp;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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&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;What this function does is it delivers parts of an HTML response on the response stream but without fully completing the body after the two delay calls of 5 seconds each.&lt;/p&gt;

&lt;p&gt;Trying out the same trick of opening the page for a couple of seconds and then closing it down would trigger more &lt;em&gt;abandonment&lt;/em&gt; type reports.&lt;/p&gt;

&lt;p&gt;That’s about it 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing notes
&lt;/h2&gt;

&lt;p&gt;Thanks for reaching this far into the article. At this point I want to again stress how important this capability of the NEL proposal is in my eyes. Congratulations on the team at Google that introduced initially this idea and also the folks on the &lt;a href="https://www.w3.org/webperf/"&gt;Web Performance Working Group&lt;/a&gt; for taking care of this specification.&lt;/p&gt;

&lt;p&gt;Just as with all things in life, there are some caveats for now that you might need to be aware of. Currently NEL is supported only in Chromium based browsers like Chrome, Edge &amp;amp; Opera but hopefully this is gonna increase as time passes.&lt;/p&gt;

&lt;p&gt;Hope you found something interesting and useful with this relatively new &lt;em&gt;abandonment&lt;/em&gt; reporting capability, and if you liked the article it would be nice to try and spread the word!&lt;/p&gt;

&lt;p&gt;Feel free to reach out to me on any of my social media for any questions 😊&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Picture by &lt;a href="https://www.pexels.com/el-gr/@song-kaiyue-1051966?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Song Kaiyue&lt;/a&gt; at &lt;a href="https://www.pexels.com/el-gr/photo/2029478/?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>node</category>
      <category>performance</category>
    </item>
    <item>
      <title>Google, You Might Be Wrong Now and the Testing Pavlova 🍰</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Sat, 04 Jul 2020 08:13:42 +0000</pubDate>
      <link>https://dev.to/igneel64/google-you-might-be-wrong-now-and-the-testing-pavlova-1cn1</link>
      <guid>https://dev.to/igneel64/google-you-might-be-wrong-now-and-the-testing-pavlova-1cn1</guid>
      <description>&lt;h2&gt;
  
  
  The Statement 📜
&lt;/h2&gt;

&lt;p&gt;One post on the Google Testing Blog in 2015 about End-to-End testing has influenced the way we approach software testing even today. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html"&gt;In the article&lt;/a&gt;, there are points that I wholeheartedly agree and points in which I disagree. This fact does not mean nothing more than that professionals with different experiences and context can provide their unique take on things. That is how we grow as a community and individuals.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Now that this is out of the way&lt;/em&gt;, let's go over some article points that in my opinion we need to re-evaluate based on the new normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Google Testing Blog
&lt;/h2&gt;

&lt;p&gt;For people that have not come across it yet, &lt;a href="https://testing.googleblog.com/"&gt;Google Testing Blog&lt;/a&gt; is a hub with amazing resources for any kind of topic around software and testing. Written by industry professionals and guest experts, the content there has benefitted our craft and made people rethink how to approach software testing in more than one aspects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Incident 👀
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;"Just Say No to More End-to-End Tests"&lt;/strong&gt;&lt;/em&gt; &lt;br&gt;
was the title of an article posted on the blog and really dealt a powerful blow to End-to-End testing enthusiasts back then. &lt;/p&gt;

&lt;p&gt;People from then on had an excuse to ditch honest efforts towards automation and discourage initiatives that were only aiming towards &lt;strong&gt;validating if the product works as a whole for the end user&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While this might not have been the original intent of the author, that is expected to happen when a so powerful title is posted on a blog that "comes from Google".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I seriously hope I am the only one that came across senior engineers or tech/team leads that stated a fact which for them seemed an undeniable truth and a correct approach:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Why do we need to do those 'End-to-End' tests when we don't have good unit test coverage first ? "&lt;/em&gt; 😶&lt;/p&gt;

&lt;p&gt;Certainly this can be the result of tons other factors, many of them reasonable, but a clear point is that we as a community of developers and testers can be considered poor in education on the topic of modern web automation testing.&lt;/p&gt;

&lt;p&gt;For a recent data source, take a look at the &lt;a href="https://www.tricentis.com/state-of-open-source-2020/"&gt;open source survey&lt;/a&gt; done by Tricentis. &lt;em&gt;We are missing education and training, that is why people cannot stand firm against absurd statements...&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Was Then, Is Not What Stands Now 👨‍🚀
&lt;/h2&gt;

&lt;p&gt;I will now be walking down into memory lane for most people. Right below we can see the staple &lt;em&gt;"Testing Pyramid"&lt;/em&gt; as can also be observed in the said article and a &lt;a href="https://docs.google.com/presentation/d/15gNk21rjer3xo-b1ZqyQVGebOp_aPvHU3YH7YnOMxtE/edit#slide=id.g437663ce1_53_98"&gt;presentation&lt;/a&gt; that went along with it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GNz3l_zx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lxq2e1i1et2n85jpjucv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GNz3l_zx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lxq2e1i1et2n85jpjucv.png" alt="Old testing pyramid"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was absolutely the truth and the way to go, but you don't see people interested in &lt;a href="https://trends.google.com/trends/explore?date=2015-06-28%202020-06-28&amp;amp;q=%2Fm%2F0h94450"&gt;Backbone.js&lt;/a&gt; either now, do you?&lt;/p&gt;

&lt;h3&gt;
  
  
  Slow to Orchestrate 🐢
&lt;/h3&gt;

&lt;p&gt;Spinning up a new environment was indeed painstaking and the capabilities of modern DevOps were a pipe dream. &lt;i&gt;Now we have on-demand environments in amazing speeds, Serverless, &lt;a href="https://www.heroku.com/flow"&gt;Review Apps&lt;/a&gt;, &lt;a href="https://vercel.com/features/deployment-previews"&gt;Deployment Previews&lt;/a&gt; and tons more great tools.&lt;/i&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Slow to Run 😴
&lt;/h3&gt;

&lt;p&gt;Spawning a full browser has its difficulties as it requires specialized preparation to provide GUI capabilities. Now with tools like &lt;a href="https://pptr.dev"&gt;Puppeteer&lt;/a&gt;, &lt;a href="https://playwright.dev"&gt;Playwright&lt;/a&gt; and &lt;a href="https://cypress.io"&gt;Cypress&lt;/a&gt; just to name a few, you can run End-to-End tests headless in any minimally equipped environment. Even Serverless!&lt;/p&gt;

&lt;h3&gt;
  
  
  Flaky 🎰
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://thehomeofwebautomation.com/how-selenium-works-architecture"&gt;Selenium architecture&lt;/a&gt; was not capable at the time to easily evaluate things like element ready state or network state. That ultimately resulted in &lt;b&gt;explicit wait()&lt;/b&gt; calls which even a millisecond delay would trigger an error for the whole test.&lt;/p&gt;

&lt;p&gt;This also made scaling and parallelization not as effective as it could be. The tools mentioned above are &lt;i&gt;closer&lt;/i&gt; to the browser platform and can automatically wait for the correct moment to trigger actions without hacks or explicit wait.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hard to Maintain* 🐛
&lt;/h3&gt;

&lt;p&gt;Without wanting to dive deep into this point, I will just add a statement that goes on the grapevine. At the time the people hired for the role were not as technically strong or had the training required to excel at the expectations for the position.&lt;br&gt;
&lt;em&gt;*This was and is true up to this day for situations that an application is in its infant stages and is constantly experiment with new experiences.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Let us jump to the present reality...&lt;/em&gt;🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Thinking Big is Required
&lt;/h2&gt;

&lt;p&gt;Quoting the article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think Smaller, Not Larger&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The thought of unit level tests providing actual confidence for a frontend application is moving away from the truth as time passes. Actual confidence is the thing that we are paid to create and that is &lt;br&gt;
&lt;b&gt;"Working software for the end user".&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Modern frontend applications have indisputably moved to being:&lt;/p&gt;

&lt;h3&gt;
  
  
  Heavy in business logic and state 🏋
&lt;/h3&gt;

&lt;p&gt;Your typical product these days has shifted business logic on the frontend side which is in one way or the other intermingled with routing tied in-memory browser state.To translate the above statement to layman's terms, when you want to test your e-commerce cart in React, you have to mock/stub the state manager and the router just to get things going.&lt;/p&gt;

&lt;p&gt;At least in my eyes, this is magnitudes faster than an end-to-end test for your cart but by stubbing a shared state manager and a global router, how confident are you that this unit test proves the cart is working ?&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-dependent on distributed services 🤹
&lt;/h3&gt;

&lt;p&gt;In one way or the other, the shift towards distributed computing, in all its shapes, is bound to permeate the ways we approach software. Same thing is starting to happen on the frontend applications. &lt;/p&gt;

&lt;p&gt;Their functionality, their validity and their usefulness depends on external services. Either that be another team's microservice, an authentication API or Google Places Autocomplete. That is in many ways reflected on the interfaces of your application.&lt;/p&gt;

&lt;h4&gt;
  
  
  Think larger and more distributed. 🌐
&lt;/h4&gt;




&lt;h2&gt;
  
  
  What Happens Now for Frontend Engineers ? 🤔
&lt;/h2&gt;

&lt;p&gt;The above points, when attempted to be translated into unit or integration tests, can quickly become tightly coupled to multiple mocking techniques and external APIs that are out of your control.&lt;/p&gt;

&lt;p&gt;To not get confused here, this is no pushing against them, but comparing them to a Puppeteer End-to-End test, they are not conceptually so far away in terms of setup and mocking required. There are even techniques to provide whole &lt;a href="https://thehomeofwebautomation.com/mock-your-environment-minute"&gt;environment mocks in minutes&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The suggestion here is to consider adding a couple of End-to-End tests to validate that a user flow works before throwing it out to QA. Just add &lt;a href="https://thehomeofwebautomation.com/getting-started-puppeteer"&gt;Puppeteer&lt;/a&gt; or &lt;a href="https://thehomeofwebautomation.com/getting-started-playwright"&gt;Playwright&lt;/a&gt;, throw in some lines of JavaScript that as a frontend engineer you are so familiar with, and you are good to go.&lt;/p&gt;

&lt;p&gt;As an ecosystem, we have reached the maturity levels that this is easily possible, so please do 🙏&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Furthermore something that I want to get out of my chest...&lt;/b&gt;    Unlike backend tests, frontend tests if not run against different environments, are not guaranteed to be working, even if they give you the green heavy checkmark. &lt;/p&gt;

&lt;p&gt;Never let someone with no frontend expertise dictate a testing strategy for the frontend. It is like getting a swimming coach to prep you for a boxing match.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Normal 🏄
&lt;/h2&gt;

&lt;p&gt;Great individuals as Kent C. Dodds and Guillermo Rauch have been introducing &lt;a href="https://twitter.com/rauchg/status/807626710350839808"&gt;unconventional thoughts&lt;/a&gt; about the &lt;a href="https://kentcdodds.com/blog/write-tests"&gt;"Testing Pyramid"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let me now give you my take on the new normal. &lt;strong&gt;Enjoy!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2kB0rpny--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dgqya95c2myjfe39qbzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2kB0rpny--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dgqya95c2myjfe39qbzb.png" alt="the testing pavlova"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can observe in what I like to call "Testing Pavlova", the layers and the ratio of expected tests are kept the same as with the "Testing pyramid", only a bit more stylish. &lt;/p&gt;

&lt;p&gt;Adding up the vertical axis that developers tend to confuse in many discussions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BJPZ1xHW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/r713hvhfq3i5kubjocea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BJPZ1xHW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/r713hvhfq3i5kubjocea.png" alt="going up and down the pavlova"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now for the revolutionary part, something that I have not seen being illustrated yet. You are receiving a shaped container, but you are not explicitly shown how to fill it. &lt;/p&gt;

&lt;p&gt;We will show how this &lt;em&gt;Pavlova&lt;/em&gt; should be filled, parallelizing that with the timeline of your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Top Down but Side to Side
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sky4JrRF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pcwknfen0mqygfygvvs7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sky4JrRF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pcwknfen0mqygfygvvs7.png" alt="how the pavlova is filled"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Useful Gists
&lt;/h2&gt;

&lt;h4&gt;
  
  
  &amp;gt; Consider the product "ok" to be launched when it also contains as few End-to-End tests as to cover your main revenue flows 💸
&lt;/h4&gt;

&lt;h4&gt;
  
  
  &amp;gt; Code coverage should be viewed as an organic side-effect of product maturity, not an explicit target 🌲
&lt;/h4&gt;

&lt;h4&gt;
  
  
  &amp;gt; The diminishing return occurs when you just add Unit level tests that will never confirm a product function, but probably just if a library works 🤦
&lt;/h4&gt;

&lt;h2&gt;
  
  
  🔥A Hot Take🔥
&lt;/h2&gt;

&lt;p&gt;The final points I wish to tackle in the article is one that I cannot find myself familiarizing with, no matter how many years I have been in the industry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the loop (&lt;em&gt;test feedback loop&lt;/em&gt;) is fast enough, developers may even run tests before checking in a change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the first point, I am actually confident that we as software engineering community, have invested in the tools and culture to have scheduled and automated test runs in many parts of our coding efforts assembly line. That is in our CI environment, pre-commit hooks and other examples depending on the project. If at your project/team, the mentioned techniques are a "backlog" item, please push it up, it is essential.&lt;/p&gt;

&lt;p&gt;and now...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Developers like it (&lt;em&gt;End-to-End testing&lt;/em&gt;) because it offloads most, if not all, of the testing to others.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the final point, we will be including only Frontend engineers into the mix because this shift in mindset is another part of the &lt;em&gt;new normal&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Taking into account the change in how applications are architected, API mocking capabilities and technotropies like &lt;a href="https://jamstack.org/"&gt;Jamstack&lt;/a&gt;, it is to our best interest to invest in including End-to-End testing as a "frontend-related" topic.&lt;/p&gt;

&lt;p&gt;Frontend engineers 👩‍💻&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are responsible for adding the new UI/UX interactions to be tested&lt;/li&gt;
&lt;li&gt;Are already working with JavaScript, so Node.js automation tools are easy to onboard&lt;/li&gt;
&lt;li&gt;Know how the browser/network operates and its quirks&lt;/li&gt;
&lt;li&gt;Are the ones who will fix most frontend issues in the first place&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all the great tools, services and community resources we have at our disposal it is not making much sense for frontend engineers to not take part in creating and maintaining End-to-End tests for their interfaces. Even the bare minimum.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing 📔
&lt;/h2&gt;

&lt;p&gt;Thank your for reaching the end of the article and I seriously hope you took your notes. Not to bash me online, but to just reconsider some of your past or present discussions about web automation testing, specifically the End-to-End topic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Good Idea That Often Fails In Practice&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was the characterization of End-to-End testing in the said blog post and I hope we will practically and conceptually get over this, with all the amazing people and tools around our ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onwards 🚀&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AMzdc451--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a4fb64cjx1tw555nqx6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AMzdc451--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a4fb64cjx1tw555nqx6u.png" alt="the home of web automation logo"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Cross-posted from &lt;a href="https://www.thehomeofwebautomation.com/google-might-be-wrong/"&gt;The Home of Web Automation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>webdev</category>
      <category>development</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Automation for Web Performance - All Flavours Lighthouse</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Fri, 26 Jun 2020 18:30:29 +0000</pubDate>
      <link>https://dev.to/igneel64/automation-for-web-performance-all-flavours-lighthouse-5fd0</link>
      <guid>https://dev.to/igneel64/automation-for-web-performance-all-flavours-lighthouse-5fd0</guid>
      <description>&lt;p&gt;Google Lighthouse is an amazing tool that has changed the way we view web performance and now directs our efforts to improve it. Let us show how easy it is to automate the audit and collect the data in many formats!&lt;/p&gt;

&lt;h2&gt;
  
  
  A Glimpse Into Web Performance 🚄
&lt;/h2&gt;

&lt;p&gt;During the last four to five years, &lt;strong&gt;web performance&lt;/strong&gt; has grown to be a huge topic of discussion and innovation across the internet-connected industry. It would be really convenient for anyone to be able to define briefly the components that gather under the umbrella of this term, but that is not the case.&lt;/p&gt;

&lt;p&gt;At a really high level we can list a few that are &lt;strong&gt;already specific&lt;/strong&gt; towards the web:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser internal workings as a host platform&lt;/li&gt;
&lt;li&gt;Data payloads&lt;/li&gt;
&lt;li&gt;User Interface/User Experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just the three items that popped up pretty easily in my head, are topics that deserve tomes and tomes of information, details and technotropies. The good thing though is that we do not need to digest all the information to be able to improve them on the context of our own projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lighthouse
&lt;/h2&gt;

&lt;p&gt;Just a pinch of understanding would be enough, and here is where &lt;a href="https://developers.google.com/web/tools/lighthouse"&gt;Lighthouse&lt;/a&gt; comes in the picture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mpcBmC99--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ibx848dg85t7p1fiy73f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mpcBmC99--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ibx848dg85t7p1fiy73f.png" alt="lighthouse logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lighthouse is tool that helps technologists audit, monitor and improve the overall quality of web pages, open-sourced under the Google Chrome umbrella. If you do not know it yet, it is already included in your Chrome and Edge browsers. Some details on how to run it if you haven't yet.&lt;br&gt;
&lt;em&gt;Lighthouse tab in &lt;a href="https://developers.google.com/web/tools/lighthouse#devtools"&gt;Chrome&lt;/a&gt;, Lighthouse tab in &lt;a href="https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/whats-new/2020/03/devtools#the-audits-panel-is-now-the-lighthouse-panel"&gt;Edge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Overall"&lt;/strong&gt; can sound cheeky and soft-handed to people that are software specialists. To their surprise Lighthouse is, at least in my opinion, one of the projects that has, is and will be improving the quality of the web as we experience it in many aspects. By default it includes suggestions for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Progressive Web App&lt;/li&gt;
&lt;li&gt;Best practices&lt;/li&gt;
&lt;li&gt;Accessibility&lt;/li&gt;
&lt;li&gt;SEO&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Great engineering effort from many slices of people across the tech community, has made this tool a powerhouse and indisputably the "go-to" for performance monitoring.&lt;br&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I were you, I would explore the categories and capabilities right away, the ROI is huge!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After you have gotten over the stroke that you got from the awesomeness of the tool...🤯&lt;/p&gt;

&lt;p&gt;Let me make you tremble once again by telling you that the whole report that Lighthouse has generated, together with granular performance metrics and more data, can be retrieved in an automated way using a single command or in more complicated scenarios just a few lines of JavaScript code. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In action now!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Lighthouse One-Line 🛤
&lt;/h2&gt;

&lt;p&gt;If the specific page or set of pages that you wish to run Lighthouse against, are valid for &lt;strong&gt;all&lt;/strong&gt; the criteria below:&lt;br&gt;
 - Publicly accessible&lt;br&gt;
 - Non differentiating between known and anonymous users&lt;br&gt;
 - Not requiring previous application state&lt;/p&gt;

&lt;p&gt;Then you can just use the &lt;a href="https://github.com/GoogleChrome/lighthouse#using-the-node-cli"&gt;lighthouse-cli&lt;/a&gt; or go with &lt;a href="https://developers.google.com/speed/pagespeed/insights/?hl=en"&gt;Google Pagespeed Insights&lt;/a&gt; (&lt;em&gt;that uses Lighthouse internally&lt;/em&gt;) and you do not need anything additional.&lt;/p&gt;

&lt;p&gt;You can follow along with a simple setup, that will provide a separate place to run and store your Lighthouse reports from using the command line interface, starting with a bare Node.js project.&lt;/p&gt;

&lt;p&gt;In your command line, let's build a new project for our task at hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;auto-lighthouse
&lt;span class="nb"&gt;cd &lt;/span&gt;auto-lighthouse
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Having scaffolded a project, installing the required library comes next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;lighthouse
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Only with the Lighthouse npm module installed, you can now run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lighthouse https://dev.to
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;What happens now is that you will see a Google Chrome instance launching in your machine, automatically navigating to the supplied URL, doing some magics 🧙‍♂️ and finally producing a single file inside the project folder. The file generated is an HTML file with a name structured like:&lt;br&gt;
&lt;strong&gt;{SUPPLIED_URL}{DATE}&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go and open this file in your browser of choice. There you have your amazing report to distribute in your team to reveal their &lt;em&gt;incompetencies&lt;/em&gt; 🤣&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v58H7RlA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fbzwn98fiaoszh07as33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v58H7RlA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fbzwn98fiaoszh07as33.png" alt="performance report"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Special Hint: If you want to save the file but also open the report automatically in your browser you can run the cli command with the &lt;code&gt;--view&lt;/code&gt; option.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;** If you wondering why your report is showing the device field as "Emulated Moto G4", you are not the only one confused. Monitor the rabbit holes &lt;a href="https://github.com/GoogleChrome/lighthouse/issues?q=is%3Aissue+mobile+emulation"&gt;here&lt;/a&gt;&lt;/em&gt; 🐰&lt;/p&gt;
&lt;h2&gt;
  
  
  One Step Further Tailored for You 👔
&lt;/h2&gt;

&lt;p&gt;Since you made it to this step you are either interested in the shiny content we have or the situation you want to automate your Lighthouse reporting is a bit more intricate for example...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user needs to be authenticated to see the target page&lt;/li&gt;
&lt;li&gt;There must be some browser/application state initialized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br&gt;
&lt;i&gt;Hmm...&lt;/i&gt;&lt;br&gt;
&lt;i&gt;We cannot pre-bake state in the browser using the lighthouse cli...&lt;/i&gt;&lt;br&gt;
&lt;i&gt;We cannot authenticate the user safely without going through a login action at least...&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How would we go about those scenarios then ? 🤔&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Of course we can use our favorite Node.js browser automation libraries, &lt;strong&gt;Puppeteer or Playwright&lt;/strong&gt;. Using these libraries, we would be able to orchestrate the steps needed to arrive at the correct application state, and then run Lighthouse.&lt;/p&gt;
&lt;h2&gt;
  
  
  Now The Code 👨‍💻
&lt;/h2&gt;

&lt;p&gt;*&lt;em&gt;I am sure you are eager to jump into the code and this is what we are gonna do. But as we progress through it, I will try my best to explain what seemed unclear (at least to me) when trying to get this process down.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;First up, you should go ahead and install the additional npm package we are gonna need to make this work. For demonstration purposes we will install &lt;a href="https://www.thehomeofwebautomation.com/getting-started-puppeteer/"&gt;Puppeteer&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;puppeteer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next create a new file called &lt;em&gt;index.js&lt;/em&gt; with the following context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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="nx"&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;puppeteer&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;lighthouse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;lighthouse&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="kd"&gt;function&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;url&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://dev.to&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* Random */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9222&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* Not arbitrary, the default Lighthouse would look for if not specified in options */&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="nx"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`--remote-debugging-port=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&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="cm"&gt;/* Expose this so we can use it below */&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;lighthouseOpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;disableStorageReset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="cm"&gt;/* For the custom steps we will show later */&lt;/span&gt;
    &lt;span class="na"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* To observe the good stuff */&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="cm"&gt;/* Run Lighthouse, using the options specified */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lighthouseResult&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;lighthouse&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;lighthouseOpts&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lighthouseResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lhr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cm"&gt;/* Inspect the "lhr" (lighthouse report) property in the console */&lt;/span&gt;

  &lt;span class="cm"&gt;/* Kill the browser 🔪 */&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="nx"&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;blockquote&gt;
&lt;p&gt;Probably the first question that comes into your mind is: &lt;strong&gt;Why are we using this &lt;em&gt;remote-debugging-port&lt;/em&gt; thing ?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of Lighthouse programmatic capabilities is that it can &lt;a href="https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-core/gather/connections/cri.js#L24"&gt;connect&lt;/a&gt; to an existing browser instance by using the provided port the browser is using.&lt;/p&gt;

&lt;p&gt;If you run the script now, it will have the exact same behavior with the CLI version but it will not result in a new HTML report in your directory, instead it will just log the &lt;em&gt;lhr&lt;/em&gt; property on the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding The Custom Steps 🔧
&lt;/h2&gt;

&lt;p&gt;As you might have suspected already, there is a place in which your additional logic can go in, in order to drive the app to the state you require.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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="nx"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="cm"&gt;/*...*/&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="cm"&gt;/*
 * Additional Web Automation logic
 * await authenticate(browser, ...args);
 * await fillEphemeralStorage(browser, ...args);
 * ...
 */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lighthouseOpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/*...*/&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;An example of a function that you would use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signinUrl&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="nx"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signinUrl&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;emailInput&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="nx"&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;input[type=email]&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;emailInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;admin@admin.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="nx"&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[id=go]&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;waitForNavigation&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;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;Functions like these will result in a state specific to your application needs but remember that it will probably have to do with something along the lines of:&lt;/p&gt;

&lt;p&gt;- Browser stored credentials (&lt;i&gt;cookies&lt;/i&gt;) 🍪&lt;br&gt;
- Local browser state (&lt;i&gt;IndexedDB, LocalStorage, etc&lt;/i&gt;) 🏦&lt;br&gt;
- Ephemeral app specific conditions 👻&lt;/p&gt;

&lt;p&gt;After the setup functions are completed, Lighthouse can be called to run at the target URL. &lt;/p&gt;
&lt;h3&gt;
  
  
  An Important Lighthouse Parameter 🚦
&lt;/h3&gt;

&lt;p&gt;Because of the Lighthouse inner workings, the metrics to be collected and heuristics of the collection, every time an audit is requested, a new page will open but with the &lt;strong&gt;browser storage cleaned up&lt;/strong&gt;, unless stated otherwise. That is understandable in order to start from a &lt;em&gt;clean" slate&lt;/em&gt;. You can see the code for this process &lt;a href="https://github.com/GoogleChrome/lighthouse/blob/91b4461c214c0e05d318ec96f6585dcca52a51cc/lighthouse-core/gather/driver.js#L1457"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To make sure our setup is not reset by Lighthouse, we pass the parameter &lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;disableStorageReset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now our setup is safe!&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling The Report 📈
&lt;/h3&gt;

&lt;p&gt;Currently the only processing we are doing in the report is logging it out in the console. &lt;strong&gt;Not so handy!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To our benefit, the Lighthouse API exposes a method in which we can generate a full report by providing the collected data and the format we want to report to be produced in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&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;generateReport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;lighthouse/lighthouse-core/report/report-generator&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cm"&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;lhr&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="nx"&gt;lighthouse&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;lighthouseOpts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./report.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generateReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lhr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&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;What we have done here is demonstrate how we can use the &lt;code&gt;generateReport&lt;/code&gt; function to create a new report in &lt;em&gt;JSON&lt;/em&gt; format and put it in a new file called &lt;em&gt;report.json&lt;/em&gt; in the current directory.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;*The generateReport function can be used to output &lt;b&gt;JSON&lt;/b&gt;, &lt;b&gt;CSV&lt;/b&gt; and &lt;b&gt;HTML&lt;/b&gt; formats for now.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;If you go and open this file, you can see the whole slew of web quality metrics. There is a really high chance that you were not aware how many things are collected and reported by Lighthouse 🎉&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schedule the script to run for all the websites you manage and you are good to go!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing 🎗
&lt;/h2&gt;

&lt;p&gt;Thank you for going through yet another recipe. I hope that you have a bit more ammo to convince you project leads that an automated Lighthouse audit can give you many bangs over zero buck. My suggestion is to take a day off from work and just see and study all the metrics reported. &lt;strong&gt;Time well spent!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spread the report and specialized insights inside your team/organization in order to drive change for the better that will benefit the end user. In the near future we will see some more stuff around web automation and performance monitoring. See you then!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://www.thehomeofwebautomation.com/foot-in-the-door/"&gt;The Home of Web Automation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZrZ7ZNa9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rlvwytoedzy7wmsr1h9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZrZ7ZNa9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rlvwytoedzy7wmsr1h9a.png" alt="the home of web automation logo"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>performance</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Full Guide: Introducing The Web Automation Initiative to Your Organization and Team</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Wed, 24 Jun 2020 07:51:04 +0000</pubDate>
      <link>https://dev.to/igneel64/full-guide-introducing-the-web-automation-initiative-to-your-organization-and-team-410c</link>
      <guid>https://dev.to/igneel64/full-guide-introducing-the-web-automation-initiative-to-your-organization-and-team-410c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is not a post to discuss and compare the low-level capabilities of each web automation library or framework. Here we plan to lay out some foundational concerns that you should keep in mind when choosing the right tool for the job.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Enjoy&lt;/strong&gt;!&lt;/p&gt;

&lt;h1&gt;
  
  
  Stepping with the "right" foot in the door for Web Automation
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Proposing a Web Automation Initiative 👔
&lt;/h2&gt;

&lt;p&gt;There comes the time that that you want to start your web automation initiative. That requires a quite &lt;em&gt;smart deal of preparation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;First you have got to convince your team that this is worthwhile.&lt;/p&gt;

&lt;p&gt;In the same manner you have to make the decision of which web automation strategy and which library or framework you should consider based on your application use cases. &lt;/p&gt;

&lt;p&gt;Let us go over some foundational concerns to help guide that decision and effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting In The Mood 🤖
&lt;/h2&gt;

&lt;p&gt;The first best thing is that you arrived at the point of discussing something related to web automation 👏&lt;/p&gt;

&lt;p&gt;In most discussions around web automation, especially when planning to introduce a new initiative or re-evaluate your technology stack, the topics that can quickly drive the discussions are &lt;strong&gt;cross-browser compatibility, programming language API and community backing&lt;/strong&gt; that available tools offer. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To not get confused here, by &lt;em&gt;browser compatibility&lt;/em&gt; we mean the range of available browsers a web automation task can run seamlessly on without any change required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Aim For Efficiency 🚀
&lt;/h3&gt;

&lt;p&gt;For modern software products and businesses, &lt;strong&gt;efficiency is a vital factor&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;That makes it all the more important for us to be &lt;em&gt;pragmatic&lt;/em&gt;. That means evaluate solutions that &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Minimize wasted effort &lt;/li&gt;
&lt;li&gt;Do just enough to complete the task at hand safely and with the expected quality levels&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;For our context we need to pick the right web automation tool serving our own needs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt; -&lt;em&gt;How many people in the team have experience with web automation testing ?&lt;/em&gt;&lt;br&gt;
 -&lt;em&gt;Do we have to run the suite in different browsers ?&lt;/em&gt;&lt;br&gt;
 -&lt;em&gt;Do we need to support Internet Explorer ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These are just some standard and common questions, but we will go over the process on how you can evaluate your requirements and tradeoffs even in more complex situations. &lt;br&gt;
&lt;strong&gt;Let's go!&lt;/strong&gt; 🏄&lt;/p&gt;
&lt;h2&gt;
  
  
  Evaluating Your Reality
&lt;/h2&gt;

&lt;p&gt;The first step for exploring the best possible solution is to &lt;strong&gt;rule out the cases&lt;/strong&gt; you do not even need to consider such decision. &lt;/p&gt;

&lt;p&gt;They are probably self explanatory but compiling down a table with rough estimations to get you out of this situation pretty quickly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type of web automation task&lt;/th&gt;
&lt;th&gt;What path to take&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task that aims to automate otherwise manual labour.&lt;/td&gt;
&lt;td&gt;Pick the tool that you feel most comfortable with except if execution speed is important. If it is, probably &lt;b&gt;Puppeteer&lt;/b&gt; or &lt;b&gt;Playwright&lt;/b&gt; would be just fine.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proof Of Concept for initiating an End To End testing initiative.&lt;/td&gt;
&lt;td&gt;Go with the tool that requires less setup and needs the minimum amount of code to achieve the required use cases. You could go with &lt;b&gt;Puppeteer&lt;/b&gt; or &lt;b&gt;Playwright&lt;/b&gt; if you are into vanilla but &lt;b&gt;Cypress&lt;/b&gt; would be my go-to here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Designing an End To End testing platform in an enterprise setting.&lt;/td&gt;
&lt;td&gt;
&lt;b&gt;Selenium&lt;/b&gt; and &lt;b&gt;Selenium-based&lt;/b&gt; frameworks would probably fit better as portability around teams and cross-browser compatibility is an absolute must for those businesses.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generate PDF from a remote context on a user action&lt;/td&gt;
&lt;td&gt;Speed is of the essence and you have all the control. In that case you can look into a solution like &lt;b&gt;serverless puppeteer/playwright&lt;/b&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;These situations as coarse grained as they might seem, can at least point you to a first solid direction. Efficiency metrics and team feedback will guide you for the rest of the decisions you have to make down the line depending on the path you chose initially.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Messy Middle of Web Automation 🌀
&lt;/h2&gt;

&lt;p&gt;Since you reached this part of the article, means we can borrow this amazing phrase &lt;a href="http://www.scottbelsky.com/"&gt;Scott Belsky&lt;/a&gt; to describe a common but certainly &lt;em&gt;messy&lt;/em&gt; situation you need to wrestle with. The &lt;em&gt;Messy Middle&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;This phrase will be used to describe the state that at some point in time your web automation endeavors are bound to reach.&lt;/p&gt;

&lt;p&gt;One of the characteristics of this state is &lt;a href="https://en.wikipedia.org/wiki/Overchoice"&gt;choice overload&lt;/a&gt; 🤯&lt;/p&gt;

&lt;p&gt;As browser technology is progressing rapidly, the ecosystem and available tools for web automation are growing along with it, especially in the &lt;em&gt;End to End&lt;/em&gt; testing league. The absolutely wrong path to take is start exploring the vast ecosystem without any guidance from you actual &lt;strong&gt;goals, data and expectations&lt;/strong&gt;. These main drivers as you can understand are highly dependent on the scenario that brought you to this stage from the start.&lt;/p&gt;

&lt;p&gt;This &lt;em&gt;Messy Middle&lt;/em&gt; most commonly can occur in one of the following scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trying to introduce the concept of End to End testing to a team (&lt;em&gt;or in a whole organization&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Re-evaluating the state of a legacy web automation codebase that bears the weight of time. &lt;em&gt;Flakiness, Configuration &amp;amp; Setup mayhem, takes hours to run etc...&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Above we list these two specific scenarios as they share the common requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Low resource cost&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;High impact&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;A &lt;em&gt;twist of wow factor&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In both cases you &lt;strong&gt;absolutely need to convince and inspire your team&lt;/strong&gt;, that is why we need to invest up front on the things that matter.&lt;/p&gt;
&lt;h3&gt;
  
  
  Look Further Into Requirements Not Options
&lt;/h3&gt;

&lt;p&gt;As technologists, and software enthusiasts at that, we love diving in and analyzing a plethora of options that we have to solve a particular problem. It feels like a playground with thousands of toys to pick up and play. &lt;/p&gt;

&lt;p&gt;We want to try the latest, touch the bleeding edge and feel that we are joining the innovation march, &lt;strong&gt;leaving expectations as an afterthought that we will tackle after we make our choices&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;-&lt;em&gt;Should we assemble our own minimal framework or start with something like Gauge or WebdriverIO ?&lt;/em&gt;&lt;br&gt;
-&lt;em&gt;Should we integrate tests using Cucumber or try it together with TestNG ?&lt;/em&gt;&lt;br&gt;
-&lt;em&gt;Might it be a good case to try the Screenplay pattern ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At that time, which is bound to happen, take a step back and bring again into your mind the word &lt;strong&gt;"Pragmatic"&lt;/strong&gt;. We are operating under the umbrella of a business and we have so many edges that our choices will have an effect on.&lt;/p&gt;

&lt;p&gt;From our peers and seniors but finally to the customer, we ought as professionals to make our decisions as informed as possible. Not that the technology questions are unimportant, on the contrary, since they are important they should not be taken lightly.&lt;/p&gt;
&lt;h3&gt;
  
  
  Gather Real User Data 👨‍👩‍👧‍👦
&lt;/h3&gt;

&lt;p&gt;There is no better indicator about your real requirements than from the people that actually use your product.&lt;/p&gt;

&lt;p&gt;For most use cases, the main difference you first need to consider is how &lt;em&gt;cross-browser compatible&lt;/em&gt; your solution needs to be.&lt;br&gt;
&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SeayRnpK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5c23mr8l15l0794l46sg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SeayRnpK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5c23mr8l15l0794l46sg.png" alt="browsers"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;You are trying to get a foot in the door but if the proposed path does not cover 30% of user technology specifications, it is not the "right" foot you are getting into with. It is totally reasonable to look for the option that would allow broader amount of coverage, but you have to act smart here, you need to base "broader" on something, which &lt;strong&gt;is not 100% of the browser market&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This step is skipped for most developers as they become allergic to step out of the comfort zone of &lt;em&gt;development tools&lt;/em&gt; 😥&lt;/p&gt;

&lt;p&gt;Data analytics tools such as &lt;em&gt;Google Analytics&lt;/em&gt;, can provide you with fine-grained information about which &lt;em&gt;browser/browser version/device&lt;/em&gt; combination your actual users are preferring to navigate your product with. &lt;br&gt;
&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XMg50-Fi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6f42vqi92raaaixpco1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XMg50-Fi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6f42vqi92raaaixpco1i.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now you can start working your case with reality as your guide.&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Special Hint: Look also for the conversions/combination metric! If Internet Explorer users are just 5% but bring 15% of the conversions, it matters a lot!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It will be clear as day that if your userbase is composed by a considerable amount of traffic from Internet Explorer, you should go for a solution that supports Selenium. On the other hand, if your application is mostly used on Safari, you should give Playwright its fair chance against Puppeteer.&lt;/p&gt;

&lt;p&gt;Thankfully though, there are free and open source options that want to lift the burden of this choice from our shoulders as much as possible. Frameworks like &lt;a href="https://webdriver.io/"&gt;WebdriverIO&lt;/a&gt; and &lt;a href="https://codecept.io/"&gt;CodeceptJS&lt;/a&gt; provide the capabilities to switch between Selenium, Puppeteer or even Playwright(&lt;em&gt;only for CodeceptJS&lt;/em&gt;) as your automation core library.&lt;/p&gt;
&lt;h3&gt;
  
  
  Evaluate Team Experience &amp;amp; Preferences 🤝
&lt;/h3&gt;

&lt;p&gt;Presenting your case in a team that already has experience with web automation is always a good thing. In the best case scenario you will be having more eyes on the target along with more technical knowledge from your peers. &lt;/p&gt;

&lt;p&gt;On the other side of the spectrum, a team that had a bad experience with similar efforts in the past, can be seen as a source of feedback for what went wrong and ways to avoid the same mistakes. &lt;/p&gt;

&lt;p&gt;⭐ &lt;em&gt;Do not forget that this situation can be fertile ground to change perspectives for the better (that is why you should continue reading)&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Bring The Excitement 🏂
&lt;/h4&gt;

&lt;p&gt;Talking about perspectives, it might be by far one of the strongest factors on the success of the initiative, you should strive to excite all the more people from your team. &lt;/p&gt;

&lt;p&gt;Try to create a group of "followers", people that will support your claim that this change will have positive impact for the team as a whole. In the same note, having your peers excited will have a high chance of them being willing to collaborate with you and contribute in the effort. This can only lead to much better results.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Keep in mind that more experienced managerial stuff, will not naturally prefer a "one-man" initiative as they will need to account for the bus factor of a new system.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On the other hand, as any &lt;em&gt;solid initiative&lt;/em&gt;, it needs to pull its own weight, that is why you must strive to require as few slices of people as possible in order to make this a reality.&lt;/p&gt;

&lt;p&gt;As an example, if you get the feeling that the CI pipeline is convoluted or it requires significant involvement of Operations people, try to choose a solution that makes the process as lightweight and minimal as possible.&lt;/p&gt;

&lt;p&gt;👉 &lt;em&gt;Minimizing the effort from other individuals and the changes required in other parts of the system, is sure to increase the chance the initiative is accepted and not thrown in a "low priority" bucket.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Maximum Output - Low Effort
&lt;/h2&gt;

&lt;p&gt;Independently of the results of your preparation, since hopefully you did not skip any corners, it is time to get the wheels spinning. Together with your team you have made the choices, keeping in mind all the needs of the application, and in order to actually start the initiative you need to present to management the &lt;strong&gt;incremental&lt;/strong&gt; steps towards your desired end goal.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Special Hint: Do not come empty handed on this aspect, expecting the question to be asked by someone else, be prepared with the correct amount of effort that can fit into the work schedule that your team is following.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Incremental"&lt;/strong&gt; can mean different things for different people, but we need to have this down for our initiative so that we can produce the maximum amount of output in &lt;em&gt;low effort&lt;/em&gt; iterations. What better way to illustrate such a case than with an example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strap in!&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Example: Monolith with Multiple Frontend Applications 🤹
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Goal
&lt;/h3&gt;

&lt;p&gt;Let's suppose your case is a monolithic application &lt;em&gt;(Django, Rails etc.)&lt;/em&gt; with multiple frontend apps for distinct parts of your product &lt;em&gt;(Data dashboard, Signup flow, Landing pages)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y9-2UeCI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a96h9yfuydv0m3fssmlk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y9-2UeCI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a96h9yfuydv0m3fssmlk.png" alt="Example app architecture"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;The application does not have any End to End tests as of yet, but has a simple test pipeline that spins up a local instance of the monolith, runs the backend tests and the different tests for the frontend that happen to be run using &lt;a href="https://jestjs.io/"&gt;jest&lt;/a&gt; for all applications.&lt;/p&gt;

&lt;p&gt;In this scenario your end goal is to create a containerized suite of End to End tests with Puppeteer that run in every PR merge of the application and tests all the parts at once. &lt;/p&gt;

&lt;p&gt;Some additional features of the suite that everybody agreed on include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screenshots on failure cases&lt;/li&gt;
&lt;li&gt;Statistics of past and current test runs&lt;/li&gt;
&lt;li&gt;Slack notifications when the suite fails to build a PR&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  First Iteration Proposal
&lt;/h3&gt;

&lt;p&gt;For the first iteration, you should aim for the least amount of setup required and &lt;a href="https://en.wikipedia.org/wiki/Happy_path"&gt;happy path&lt;/a&gt; coverage of couple of the main flows that are most valuable to your business.&lt;/p&gt;

&lt;p&gt;Our first targets can look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;landing page&lt;/em&gt; is loaded successfully and the CTA is visible within the viewport.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;dashboard application&lt;/em&gt; is loaded successfully and the main 3 routes can be clicked through and render the main graphs of the user expects to see.&lt;/li&gt;
&lt;/ul&gt;



&lt;h3&gt;
  
  
  Common Mistake: Setup For the First Tests ❌
&lt;/h3&gt;

&lt;p&gt;One of the main mistakes that individuals make when introducing a web automation initiative is to try to setup the "final" view of the pipeline integration right at the start of the initiative. &lt;/p&gt;

&lt;p&gt;This is where many trip up and the whole initiative &lt;strong&gt;gets discarded&lt;/strong&gt; when you depend on other people to kickstart it. &lt;em&gt;Some examples where you will need to take time from your DevOps team...&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nobody in the team knows how to setup Jenkins for the Selenium Server to run in parallel&lt;/li&gt;
&lt;li&gt;Your Docker setup is failing for "reasons" 🐳&lt;/li&gt;
&lt;li&gt;You are not feeling comfortable modifying the Jenkinsfile on your own&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without going deeper here into the reasons and remediation of this, you have to go with the &lt;strong&gt;most unobtrusive setup as possible&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Unobtrusive Setup 👻
&lt;/h3&gt;

&lt;p&gt;In our case this means integrating the first scenarios inside the existing frontend unit testing suite. Just the plain &lt;code&gt;npm test&lt;/code&gt; that your frontend apps are already using in one of the build hooks (&lt;em&gt;hopefully&lt;/em&gt; 😅) is more than enough. &lt;/p&gt;

&lt;p&gt;The application is already running the tests in the pipeline, so you can easily go with the solution of adding &lt;a href="https://github.com/smooth-code/jest-puppeteer"&gt;jest-puppeteer&lt;/a&gt; to your project and add your two scenarios then and there on their respective frontend repositories.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No further containerization is required&lt;br&gt;
No additional commands on the build pipeline&lt;br&gt;
&lt;strong&gt;No further effort, just your two test scenarios&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  First Iteration Complete 🎖
&lt;/h3&gt;

&lt;p&gt;With the scenario described above that would take:&lt;br&gt;
- One additional NPM package&lt;br&gt;
- 100 or so lines of code &lt;br&gt;
- Some seconds added on the pipeline total build time&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With that we have completed the first iteration of our initiative.&lt;/strong&gt; Every time our application goes through the simple test pipeline, we make sure that two of the main parts of our product are not broken. This is no small guarantee for the business and you manage to deliver it in a really short timespan. &lt;/p&gt;

&lt;p&gt;Hopefully you have surprised a couple of people that did not know the power you can have with End to End tests giving a little effort. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full steam ahead for the next iteration! 🚄&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Iteration!
&lt;/h3&gt;

&lt;p&gt;Actually, for the next iteration, &lt;em&gt;you are on your own&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You have already something going on and that is enough to take some time from each iteration cycle and enhance it. Keep showing the value and advocate the confidence that it gives while shipping code into production.&lt;/p&gt;

&lt;p&gt;Do not forget that value is expressed differently in the ears of different people.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remind developers that they can be confident that they did not break something important while trying new features refactorings 🛠&lt;/li&gt;
&lt;li&gt;Show the product owners that &lt;em&gt;time-to-release&lt;/em&gt; will keep decreasing as QA has less manual work to do for each feature ⏲&lt;/li&gt;
&lt;li&gt;Indicate to managers and stakeholders that the main revenue flows are &lt;strong&gt;safe&lt;/strong&gt; while the product is becoming better and more feature-rich 💰&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You got this! 🔥&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Thank you for taking the time over yet another &lt;em&gt;not so short&lt;/em&gt; writeup, which I hope you enjoyed and found useful ideas about how to tackle a web automation initiative, both in the technical and in the non-technical aspects 🙇‍♂️&lt;/p&gt;

&lt;p&gt;From personal experience, committing and going through the process of gathering data, evaluate current requirements and analyze past experiences from your team is probably the most important decision you could have made.&lt;/p&gt;

&lt;p&gt;Not because it will make the effort more likely to succeed, &lt;em&gt;which can never be guaranteed&lt;/em&gt;, but for the sole reason that it will arm you with the &lt;strong&gt;feeling of innate confidence&lt;/strong&gt; and the &lt;strong&gt;joy of solving a real problem for your business&lt;/strong&gt;, not just a tech test-drive that might have a chance to fit the requirements.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://www.thehomeofwebautomation.com/foot-in-the-door/"&gt;The Home of Web Automation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZrZ7ZNa9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rlvwytoedzy7wmsr1h9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZrZ7ZNa9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rlvwytoedzy7wmsr1h9a.png" alt="the home of web automation log"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Resources to explore:&lt;/em&gt;&lt;br&gt;
- &lt;a href="https://www.thehomeofwebautomation.com/getting-started-puppeteer/"&gt;Getting started with Puppeteer&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://www.thehomeofwebautomation.com/getting-started-playwright/"&gt;Getting started with Playwright&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://support.google.com/analytics/answer/1012034?hl=en#Technology"&gt;Google Analytics technology report&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image by &lt;a href="https://www.pexels.com/@mateusz-dach-99805"&gt;Mateusz Dach&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>tutorial</category>
      <category>motivation</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Automate SSL Certificate renewal and issue monitoring using Puppeteer 📜</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Sat, 20 Jun 2020 18:19:46 +0000</pubDate>
      <link>https://dev.to/igneel64/automate-ssl-certificate-renewal-and-issue-monitoring-using-puppeteer-4b08</link>
      <guid>https://dev.to/igneel64/automate-ssl-certificate-renewal-and-issue-monitoring-using-puppeteer-4b08</guid>
      <description>&lt;h2&gt;
  
  
  Your SSL Backstory 🕵
&lt;/h2&gt;

&lt;p&gt;To end up in this post, you probably are in some way involved with an entity that has a web presence, either that be a website, a service or a product. This &lt;em&gt;entity&lt;/em&gt; of yours needs to be discovered by your clients/users through the World Wide Web and most of the times using a web browser directly (&lt;em&gt;that is also true for mobile devices&lt;/em&gt;). To make this happen you took your path together with your team and designed and developed a kind of web program that aims to make the world a better place.&lt;/p&gt;

&lt;p&gt;At some point depending on your role, you came across the &lt;em&gt;SSL&lt;/em&gt; or &lt;em&gt;HTTPS&lt;/em&gt; thing that hopefully people said &lt;a href="https://www.forbes.com/sites/forbestechcouncil/2018/05/18/why-an-ssl-certificate-is-important-for-your-company-website/"&gt;is a must&lt;/a&gt; for all websites independently of purpose and content. They are right in what they said and you should believe them.&lt;/p&gt;

&lt;p&gt;What happened after that is, either with an automatic commercial setup or a hosting service cross-sell, you purchased the magic certificate that guarantees safety, freedom from Google's restless eye and a, &lt;em&gt;not so stylish now&lt;/em&gt;, lock badge on the visitor's address bar 🔒&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can Go Wrong ? 🤷‍♂️
&lt;/h2&gt;

&lt;p&gt;When you bought your certificate and the process has gone smoothly, your server gained the capability to encrypt between your site and your visitors. This certificate is also used to authenticate that you are capable and authorized to claim this security standard.&lt;/p&gt;

&lt;p&gt;This &lt;em&gt;certification&lt;/em&gt; is not printed once and valid forever though. The authority organization behind these certificates have decided an expiration date of &lt;br&gt;
"&lt;a href="https://www.trustzone.com/ssl-certificate-validity-is-now-capped-at-a-maximum-of-2-years/"&gt;2 years&lt;/a&gt; &lt;strong&gt;maximum&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;As browser vendors really care about serving secure information around the web in the case that your certificate is expired, or has any kind of "issue", your web visitors will be prompted by the &lt;em&gt;dreaded screen of a faulty SSL certificate&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BJC9bkqW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ph5osd4dg2v5q9l8qzol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BJC9bkqW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ph5osd4dg2v5q9l8qzol.png" alt="ssl expired screen prompted by browser"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, this is the screen layout that the browser will prompt to your users 🙊&lt;/p&gt;

&lt;p&gt;In some cases they will need to explicitly state they want to enter this web space or in some cases they are not allowed at all. &lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;em&gt;What could that translate to ?&lt;/em&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tremendous reduction in authority and credibility by your users 🙅‍♂️&lt;/li&gt;
&lt;li&gt;Lost revenue &amp;amp; leads 💸&lt;/li&gt;
&lt;li&gt;SEO penalties 👮‍♀️&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By now you are probably are shaken enough about not forgetting to renew your SSL certificate but also to detect issues with it as fast as possible.&lt;/p&gt;

&lt;p&gt;One thing you can do now is send a message to your webmaster to check on the certificate expiration date. &lt;strong&gt;The next best thing to do&lt;/strong&gt; is to schedule a regular check about when is the required time to renew the certificate and also monitor incidents similar in nature.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Can we automate that ?&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;We sure can!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Here Comes Web Automation 🤖
&lt;/h2&gt;

&lt;p&gt;You decided to automate this process of checking your certificate. Good for you and web automation tools have you covered. To showcase how we can do that we will be using &lt;a href="https://pptr.dev"&gt;Puppeteer&lt;/a&gt; as it has the utility of providing that kind information out of the box, but probably any &lt;a href="https://chromedevtools.github.io/devtools-protocol/"&gt;CDP&lt;/a&gt; capable solution can do.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you are not sure how to start of with Puppeteer, you can have a look at &lt;a href="https://www.thehomeofwebautomation.com/getting-started-puppeteer/"&gt;Recipe #1&lt;/a&gt; and come back.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Looking through the documentation, we can see that Puppeteer's API exposes directly information about the security details of a specific network response through the &lt;a href="https://pptr.dev/#?product=Puppeteer&amp;amp;version=v4.0.0&amp;amp;show=api-class-securitydetails"&gt;SecurityDetails interface&lt;/a&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This interface exposes really neat information, like certificate issuer and the ending date of the certificate validity, that we will be using for our demonstration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Let's jump in!&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Show Me the Code 👨‍💻
&lt;/h2&gt;

&lt;p&gt;After you have done your casual Puppeteer setup, the first thing to do is enable request interception for the newly created Page object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With that, you have now gained access to some page events that will allow you to listen to the request/response cycle of every request on the pages you navigate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// We do not need to do anything on the Request event, just let it move forward&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;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;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="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="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;response&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;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="c1"&gt;// Here the magic will occur&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;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://www.thehomeofwebautomation.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The request that we need to cater for in our case is the initial DOC type request that is done to fetch the first HTML content of our page. That request carries all the information about the certificate we own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Inside the response handler */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&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="s2"&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="c1"&gt;// Get the content-type of the response&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;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&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;// Check for an HTML specific response&lt;/span&gt;
  &lt;span class="cm"&gt;/* Retrieve the security details */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;securityDetails&lt;/span&gt; &lt;span class="o"&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;securityDetails&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="cm"&gt;/* Authority that issued the certificate */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;certificateIssuer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;securityDetails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="cm"&gt;/* SecurityDetails.validTo() returns a Unix Timestamp so we need to convert it */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validToDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;securityDetails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validTo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Run this code and log some things on the console to get a fill for the data you can retrieve! For the next part we would aim to notify the responsible individuals on an upcoming certificate expiration date, so that they can take action accordingly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Continue inside the conditional */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diffInDays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;computeDateDiffInDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;validToDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Calculate the difference in days&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;diffInDays&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="c1"&gt;// If the expiry is in less than 90 days&lt;/span&gt;
  &lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diffInDays&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;certificateIssuer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Send a notification&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* The notify function can be something like */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;daysRemaining&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;whoToContact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;some channel or some email address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="c1"&gt;// sendSlackNotification(...) https://blog.nodeswat.com/simple-node-js-and-slack-webhook-integration-d87c95aa9600&lt;/span&gt;
  &lt;span class="c1"&gt;// sendMailToServiceManagement(...) https://blog.mailtrap.io/sending-emails-with-nodemailer/&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With that, we are &lt;strong&gt;mostly done&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;What I would advise you to do is schedule this as a job to run every day and leave it running for one or for all the domains you own and take care of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Additional Checks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  👉 More than one text/html responses
&lt;/h3&gt;

&lt;p&gt;In many scenarios, there more than one requests that responds with HTML content (&lt;i&gt;fitting the content-type conditional&lt;/i&gt;), but for our needs, the first one will suffice. What you can do is introduce a simple boolean flag.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Higher scope from the response handler */&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;initialHtmlFound&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="cm"&gt;/* Inside the response handler */&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;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;initialHtmlFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nx"&gt;initialHtmlFound&lt;/span&gt; &lt;span class="o"&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;h3&gt;
  
  
  👉 Redirects
&lt;/h3&gt;

&lt;p&gt;There is probably no web engineer that has not been bitten by a case of redirects. What you can do here is check for the response status and continue to the next one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;initialHtmlFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="cm"&gt;/* You should also check for possible redirects using response.status() &amp;gt;= 300 &amp;amp;&amp;amp; response.status() &amp;lt; 400 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  👉 Different kinds of SSL errors
&lt;/h3&gt;

&lt;p&gt;There are different kinds of SSL errors that might come up and they would throw an error right at the navigation step. To be on the safe side you can also take care of those.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Add this simple check for the rest of the certificate error cases&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;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://expired.badssl.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;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;net::ERR_CERT_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;
    &lt;span class="nx"&gt;notify&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Now we need to worry&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;// Do whatever you want here&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  👉 SecurityDetails is &lt;code&gt;null&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The final special case that I would mention here is the possibility of the SecurityDetails being &lt;em&gt;null&lt;/em&gt;. When does this happen ? Most often when we intercept a response that does not have any security details to expose. That can happen if you decide to navigate to a page that has no SSL certificate at all e.g. &lt;a href="http://example.com"&gt;http://example.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now you are really ready&lt;/strong&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Side Note 🖋
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;If For Some Reason&lt;/strong&gt; you have lost the plot at some point and a charlatan convinced you that you do not need an SSL certificate for your &lt;em&gt;so special&lt;/em&gt; case, please let the next thing you do today is go to any vendor (&lt;em&gt;&lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt; is free&lt;/em&gt;) and &lt;strong&gt;just set up the goddamn thing&lt;/strong&gt;. If you do not believe us here, I will leave the experts &lt;a href="https://www.troyhunt.com/heres-why-your-static-website-needs-https/"&gt;do the talking&lt;/a&gt; and Mr. Troy Hunt is the real deal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Thanks for reading this recipe and I hope your learned one or two useful things for your web automation efforts. Stay safe on the web and do not allow your users to see one of these embarrassing screens again.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cross posted from &lt;a href="https://www.thehomeofwebautomation.com/automate-certificate-renewal/"&gt;The Home of Web Automation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Picture from user &lt;a href="https://www.pexels.com/el-gr/@danny-meneses-340146?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Danny Meneses&lt;/a&gt; at &lt;a href="https://www.pexels.com/el-gr/photo/css-html-internet-laptop-943096/?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Pexels&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Getting started with Puppeteer 🤖 </title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Sat, 20 Jun 2020 08:19:14 +0000</pubDate>
      <link>https://dev.to/igneel64/getting-started-with-puppeteer-ing</link>
      <guid>https://dev.to/igneel64/getting-started-with-puppeteer-ing</guid>
      <description>&lt;p&gt;Puppeteer may currently be the most known headless browser automation library out there. It provides a high-level Node.js API which allows you to spin up and send commands to a Chromium or Chrome browser instance. It has proven itself to be easy to install, simple to use and performant by nature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DUP7aJUB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gpme2h882i6xab1cp3vt.png" class="article-body-image-wrapper"&gt;&lt;img alt="Puppeteer logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--DUP7aJUB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gpme2h882i6xab1cp3vt.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Backstory 📖
&lt;/h2&gt;

&lt;p&gt;The way that Puppeteer works is that it provides a thin layer above the &lt;a href="https://chromedevtools.github.io/devtools-protocol/"&gt;DevTools Protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The DevTools Protocol is what gives you the power to do all the cool stuff in the actual "Inspect Element" toolbar in your browser. Actually this protocol is the same that powers up most Blink-based browsers &lt;em&gt;(Chrome, Chromium etc.)&lt;/em&gt; providing the tools for DOM inspection, network profiling, debugging and all the other cool capabilities we have access to. &lt;br&gt;
&lt;em&gt;In Puppeteer you can do almost anything you can do in the actual browser without hacks included.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Puppeteer belongs under the Google Chrome umbrella and specifically is maintained by the Chrome DevTools team. That fact alone should give you some confidence about the long-term sustainability of the project. Additionally it is guaranteed to be up to date with the latest features that are shipped in the Chromium/Chrome browsers. You will not usually have to wait about a feature being &lt;em&gt;ported&lt;/em&gt; to the library.&lt;/p&gt;

&lt;p&gt;So let's get to it!👷&lt;/p&gt;
&lt;h2&gt;
  
  
  Get The Library
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Initially make sure you are in a machine with Node.js &amp;gt;=v10.18.1 installed so we can go with the latest Puppeteer version.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Make a new project folder called &lt;strong&gt;puppeteer-example&lt;/strong&gt; so we can start going through the process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;mkdir &lt;/span&gt;puppeteer-example
  &lt;span class="nb"&gt;cd &lt;/span&gt;puppeteer-example
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we can go ahead and bootstrap the required Node.js setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;  npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With this you are ready to install your favorite libraries like &lt;a href="https://www.npmjs.com/package/left-pad"&gt;left-pad&lt;/a&gt; or &lt;a href="https://www.npmjs.com/package/browser-redirect"&gt;browser-redirect&lt;/a&gt; but you can skip it for now 😂. Back to our target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;  npm &lt;span class="nb"&gt;install &lt;/span&gt;puppeteer@4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;While installing the library, you probably came across a message on your console stating &lt;em&gt;Downloading Chromium xxx&lt;/em&gt;. That message is there to let you know that with the Puppeteer library, a specific version of Chromium for your operating system is also downloaded (&lt;em&gt;inside node_modules&lt;/em&gt;) to be used by your installation of Puppeteer. The reason for that is every Puppeteer version is &lt;strong&gt;only guaranteed&lt;/strong&gt; to work with a specific Chromium version it comes bundled with.&lt;br&gt;
&lt;em&gt;Special Hint: If you are a bit disk-space constrained, delete your node_modules directory from your test or unnused Puppeteer projects after you are done.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  First Encounter🤞
&lt;/h2&gt;

&lt;p&gt;We got through the installation and now we can start writting some code. You will probably be surprised with how much you can do with a few lines of code.&lt;/p&gt;

&lt;p&gt;For our first task, we will try to explore the official Puppeteer website &lt;a href="https://pptr.dev/"&gt;https://pptr.dev/&lt;/a&gt;.&lt;br&gt;
Create a test file &lt;code&gt;index.js&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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="nx"&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;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="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="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="nx"&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="c1"&gt;// We use this option to go into non-headless mode&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="nx"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Create a new page instance&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;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://pptr.dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Navigate to the pptr.dev website&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;waitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Wait for 5 seconds to see the beautiful site&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="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Close the browser&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now by running this code using &lt;code&gt;node test.js&lt;/code&gt; you will witness a Chromium instance launching and navigating to the pptr.dev website for 5 seconds before closing down.&lt;/p&gt;

&lt;p&gt;I am sure that this now feels a comfortable place for web automation enthusiasts. The only component missing is the scenarios you need to run and getting the feel for the intuitive and simple API that Puppeteer advertises. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why not take a look ?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring a Simple Scenario 🕵
&lt;/h2&gt;

&lt;p&gt;Skipping the pleasantries, our aim will be to explore the autocomplete search functionality that pptr.dev website has for our convenience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RQVBPQnd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9n6zfpmyhgzrl5p2u7kv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RQVBPQnd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9n6zfpmyhgzrl5p2u7kv.png" alt="Search autocomplete"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Thinking Out Loud
&lt;/h3&gt;

&lt;p&gt;So let us go about describing what does an &lt;em&gt;actual user&lt;/em&gt; needs to do to get this autocomplete feature to achieve its purpose.&lt;/p&gt;

&lt;p&gt;We expect the user to:&lt;br&gt;
 &lt;span&gt;&lt;b&gt;1.&lt;/b&gt;&lt;/span&gt; Open the page&lt;br&gt;
 &lt;span&gt;&lt;b&gt;2.&lt;/b&gt;&lt;/span&gt; Try to find the autocomplete search&lt;br&gt;
 &lt;span&gt;&lt;b&gt;3.&lt;/b&gt;&lt;/span&gt; Type his query for the API method he is looking for&lt;br&gt;
 &lt;span&gt;4.&lt;/span&gt; Click the most relevant result on the list&lt;br&gt;
 &lt;span&gt;&lt;b&gt;5.&lt;/b&gt;&lt;/span&gt; Expect to see the section with the item he selected&lt;/p&gt;

&lt;p&gt;To test out if the Puppeteer API is as intuitive as it claims to be, we can go ahead and translate this thinking to Puppeteer commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Somewhere else... */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Homepage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;autocompleteSearchInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input[type='search']&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;apiSearchTerm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;metrics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// The API method we are looking for&lt;/span&gt;
&lt;span class="cm"&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;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://pptr.dev&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;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Homepage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;autocompleteSearchInput&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;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Homepage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;autocompleteSearchInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiSearchTerm&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;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;search-item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Find the API name using XPath&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$apiMethod&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="nx"&gt;$x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;//api-method-name[text()='&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;apiSearchTerm&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="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;// Check if this method name section is actually visible on the viewport&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isApiMethodVisible&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;$apiMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersectingViewport&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="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isApiMethodVisible&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Well that was it!&lt;/strong&gt; 🎉&lt;br&gt;
The code above, containing also some housekeeping, in my eyes seems pretty straightforward based on the thinking process we laid out, I do not think I even need to explain what most of the commands contribute to. The API successfully translates to clear language without relying on other external abstractions.&lt;/p&gt;

&lt;p&gt;A point that we can stand on a bit is the combination of commands that are used to detect if the API method that we were looking for is actually inside the browser viewport. People with experience in the field know that to assert this fact you would either create your own custom command (&lt;em&gt;doing viewport dimension calculations&lt;/em&gt;) or rely on a framework command that has already been implemented for us. &lt;/p&gt;

&lt;p&gt;The differentiating factor here is that the command we get directly from Puppeteer could be considered the most reliable, just from the fact that it is provided by the platform itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  One or Two Things Missing 🙈
&lt;/h2&gt;

&lt;p&gt;After we all agree that the API is rather intuitive and simple to use, we can go over and mention a couple of things that might seem to be "missing" in making our development experience a tad much better.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;em&gt;1)&lt;/em&gt; Filling your code with the &lt;code&gt;async&lt;/code&gt; keyword
&lt;/h4&gt;

&lt;p&gt;As you have definitely observed, there is this &lt;em&gt;async&lt;/em&gt; keyword you have to sprinkle all around your code, and it feels a bit noisy for me at least. This keyword is required because of the event-driven nature of the browser APIs. The way to code around asynchronous and event-driven platforms in JavaScript is by using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"&gt;Promises&lt;/a&gt; to model your operations, and Puppeteer has done just that.&lt;/p&gt;

&lt;p&gt;To make handling of those asynchronous operations a bit less painful, JavaScript has added some new keywords to the language syntax. These keywords are the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function"&gt;async &amp;amp; await&lt;/a&gt; that you see on our code. Because Puppeteer's API needs to use Promises, the best way we can write our code is to use this &lt;em&gt;async/await&lt;/em&gt; syntax for most commands.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;em&gt;2)&lt;/em&gt; No chaining available yet
&lt;/h4&gt;

&lt;p&gt;Due to some design decisions and the nature of the library, as we have mentioned in the point above, there is currently no support for what we can call &lt;em&gt;method chainning&lt;/em&gt;. With this capability our code could become so much more fluent to read and follow through. Picture something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input[type='search']&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&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;metrics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I cannot vouch for but I think there are some third-party library solutions you can try. If you want to go a bit over the state and the possible external solutions, you start by taking a look at one relevant GitHub &lt;a href="https://github.com/puppeteer/puppeteer/issues/928"&gt;issue&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;You just got through a super fast introduction on how to setup Puppeteer and code a simple scenario for an autocomplete search. From here on out you are on your own, except for all the recipes that will come on &lt;a href="https://www.thehomeofwebautomation.com/"&gt;The Home of Web Automation&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;My suggestion would be to start experimenting on your own use case and as a bedtime story, go over the &lt;a href="https://github.com/puppeteer/puppeteer/blob/master/docs/api.md"&gt;detailed API documentation&lt;/a&gt; on GitHub. It is almost certain you will find a couple of surprising things you did not expect to do using the native commands.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cross posted from &lt;a href="https://www.thehomeofwebautomation.com/getting-started-puppeteer/"&gt;The Home of Web Automation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Photo from &lt;a href="https://www.pexels.com/el-gr/@kevin-ku-92347?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Kevin Ku&lt;/a&gt; at &lt;a href="https://www.pexels.com/el-gr/photo/ai-macro-577585/?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Pexels&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>javascript</category>
      <category>testing</category>
    </item>
    <item>
      <title>Introducing Playwright 🎭</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Thu, 18 Jun 2020 08:52:14 +0000</pubDate>
      <link>https://dev.to/igneel64/introducing-playwright-4die</link>
      <guid>https://dev.to/igneel64/introducing-playwright-4die</guid>
      <description>&lt;p&gt;Introducing &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;, one of the newest and most rapidly growing headless automation libraries out there. Released in January 2020 by Microsoft, Playwright is a Node.js library that advertises performant, reliable and hustle-free browser automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Charming Browser Qualities 🐈
&lt;/h2&gt;

&lt;p&gt;One of the main advantages that you will find on Playwright versus other similar solutions is the &lt;strong&gt;range of browsers it can orchestrate&lt;/strong&gt;. It supports Chromium, Firefox and WebKit based browsers on Linux, Windows and Mac operating systems. Yes you heard that right, you can run a "Safari-like" browser on Linux or Windows; nothing new, just &lt;a href="https://webkit.org/" rel="noopener noreferrer"&gt;WebKit&lt;/a&gt;. An amazing advantage with the speed and reliability of protocol-driven browser libraries paired with a really broad range of browser coverage.&lt;br&gt;
&lt;em&gt;How does Playwright achieve that out of the box&lt;/em&gt; ?&lt;/p&gt;

&lt;p&gt;As mentioned above for Chromium based browsers, in a similar way as Puppeteer does, downloads a version of Chromium/Chrome and uses the &lt;a href="https://chromedevtools.github.io/devtools-protocol/" rel="noopener noreferrer"&gt;Chrome DevTools Protocol&lt;/a&gt; to orchestrate the browser instance. For Firefox and WebKit engines, what Playwright does is again downloading the actual browser but &lt;strong&gt;extends their debugging protocol capabilities&lt;/strong&gt; to provide a unified API and features. There is no modification of the actual browsers, so that it is expected to work exactly the same in the testing and the real user's browser. &lt;br&gt;
&lt;em&gt;To get a fill for the "patches" you can probably take a look at the repository under the &lt;a href="https://github.com/microsoft/playwright" rel="noopener noreferrer"&gt;browser_patches folder&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Moving on From Puppeteer 💼
&lt;/h2&gt;

&lt;p&gt;If you have used &lt;a href="https://pptr.dev" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; in the past and you were excited about its good parts, Playwright promises that with even more power. Just by taking a glimpse of the API on the Playwright &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;official website&lt;/a&gt; you will quickly notice that it kinda looks the same with the Puppeteer API. You are certainly right &lt;em&gt;and it is not by accident&lt;/em&gt;.&lt;br&gt;
The truth is that &lt;strong&gt;the same team that built Puppeteer, has now moved on to Microsoft and continued Playwright from a Puppeteer fork&lt;/strong&gt; 👀&lt;/p&gt;

&lt;p&gt;Without feeling the need to get into &lt;em&gt;company politics&lt;/em&gt; or &lt;em&gt;open source dynamics&lt;/em&gt;, the Playwright team promises an even better and more testing-friendly API along with significant improvements that target multi-page scenario performance, cloud-native operations and other goodies. All that while keeping the migration scenario from a Puppeteer codebase, an almost "mechanical" and straightforward task.&lt;/p&gt;

&lt;p&gt;Let's jump in then!&lt;/p&gt;
&lt;h2&gt;
  
  
  The Installation Step
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Initially make sure you are in a machine with Node.js &amp;gt;=v10.15.0 installed so we can go with the current Playwright version.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Make a new project folder called &lt;strong&gt;playwright-example&lt;/strong&gt; so we can start cooking 🍳&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="nb"&gt;mkdir &lt;/span&gt;playwright-example
  &lt;span class="nb"&gt;cd &lt;/span&gt;playwright-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now for the setup of our Node.js project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pre-setup (&lt;em&gt;funny that we have these stuff&lt;/em&gt; 😅) is ready, now for the actual setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npm &lt;span class="nb"&gt;install &lt;/span&gt;playwright
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Installing Playwright as you can see in your console pulls specific versions of Chromium, Firefox and WebKit. With the additional ~250mb of downloads in a special place in your machine cache, you get the browser support the library is rightfully advertising.&lt;/p&gt;

&lt;h4&gt;
  
  
  Small Detour 🏝
&lt;/h4&gt;

&lt;p&gt;To ease up the tension that you might have been building with this thought running through your mind, we will take a small detour:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is this WebKit thing and how can I be confident it behaves like Safari on Linux/Windows ? &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First off some foundational knowledge. The 'commercial' browsers as you know them like Google Chrome, Mozilla Firefox, Apple Safari and others, are built on top of rendering/browser engines and each vendor adds on top of it a few goodies for its userbase. The most well known engines are Blink, Gecko and WebKit that are used by Chrome/Chromium/Microsoft Edge/Opera, Firefox and Safari respectively. In other words, it is the base for a browser's main functionalities.&lt;/p&gt;

&lt;p&gt;You can run WebKit with Playwright on Windows/Linux and expect similar results with the real Safari browser, as the layouting on the page and the JavaScript execution (&lt;em&gt;handled by &lt;a href="https://developer.apple.com/documentation/javascriptcore" rel="noopener noreferrer"&gt;JavaScriptCore&lt;/a&gt;&lt;/em&gt;) are mostly the same. There might be differences in more specialized fields as how rendering works, performance, audio, video and images, but probably will fit your use case.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you want to know more or keep up with the latest news on Playwright, go ahead and follow &lt;a href="https://twitter.com/arjunattam" rel="noopener noreferrer"&gt;Arjun Attam&lt;/a&gt;, you won't be dissappointed.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Launchpad 🚀
&lt;/h2&gt;

&lt;p&gt;Let's create our launchpad with the bare essential commands to get up and running with Playwright. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Touch&lt;/em&gt; an &lt;code&gt;index.js&lt;/code&gt; file with the following contents:&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;playwright&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="kd"&gt;function&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;playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webkit&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="c1"&gt;// Non-headless mode to feel comfy&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="c1"&gt;// So much to say, but another time&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Create a new Page instance which handles most of your needs&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://playwright.dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Navigate to the Playwright webpage&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;waitForTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Rest your eyes for five seconds&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="c1"&gt;// Close the browser&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Starting with Something Simple
&lt;/h2&gt;

&lt;p&gt;To get our feet wet, we aim to test the autocomplete search functionality on the official Playwright &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;website&lt;/a&gt;. The search component is there for users to search through the topics, documentation and API methods or Playwright. Our goal is to simulate the scenario where a user browses the page and searches for a specific method using this component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4zn8ub82tmrh7bwbdyhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4zn8ub82tmrh7bwbdyhu.png" alt="Autocomplete search on Playwright website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well structured, dynamically updated component inside a &lt;em&gt;Single Page App&lt;/em&gt; type website, it seems like a really good deal for a test drive. Our first objective is to build out the steps that a user needs to take to reach the goal of finding the API method she is looking for.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Expectations 🥂
&lt;/h3&gt;

&lt;p&gt; &lt;span&gt;&lt;b&gt;1.&lt;/b&gt;&lt;/span&gt; Open the page&lt;br&gt;
 &lt;span&gt;&lt;b&gt;2.&lt;/b&gt;&lt;/span&gt; Try to find the autocomplete search&lt;br&gt;
 &lt;span&gt;&lt;b&gt;3.&lt;/b&gt;&lt;/span&gt; Type his query for the API method he is looking for&lt;br&gt;
 &lt;span&gt;&lt;b&gt;4.&lt;/b&gt;&lt;/span&gt; Click the most relevant result on the list&lt;br&gt;
 &lt;span&gt;&lt;b&gt;5.&lt;/b&gt;&lt;/span&gt; Expect to see the section with the item he selected&lt;/p&gt;

&lt;p&gt;Let's now see how the &lt;em&gt;steps&lt;/em&gt;, that we expect the user to take, can be translated to Playwright commands.&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;/* Somewhere else... */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Homepage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;autocompleteSearchInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search-view input&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;apiSearchTerm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// The API method we are looking for&lt;/span&gt;
&lt;span class="cm"&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;https://playwright.dev&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;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;networkidle&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="nx"&gt;Homepage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;autocompleteSearchInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiSearchTerm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Check for 'methods' that have the specific search term &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;`//search-suggestions/a[contains(@href, 'api.md')]//mark[.='&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiSearchTerm&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="c1"&gt;// Find the method name title using XPath&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$apiMethod&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;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xpath=//header-with-link//h4[contains(.,'context')]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Check if this method name section is actually visible on the viewport&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isApiMethodVisible&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;$apiMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boundingBox&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;notEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isApiMethodVisible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see right above, the API that is expressing the user interactions down into code is at least in my view pretty intuitive. Similar to Puppeteer, you can expect that most actions the user can take are translated into direct Page instance methods (&lt;em&gt;type, click, dblclick, etc...&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;A point that we can stand on a bit is the combination of commands that are used to detect if the API method that we were looking for is actually inside the browser viewport. People with experience in the field know that to assert this fact you would either create your own custom command (&lt;em&gt;doing viewport dimension calculations&lt;/em&gt;) or rely on a framework command that has already been implemented for us. &lt;/p&gt;

&lt;p&gt;The differentiating factor here is that the command we get directly from Playwright could be considered the most reliable, just from the fact that it is provided by the platform itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  One or Two Things Missing 🙈
&lt;/h2&gt;

&lt;p&gt;After we all agree that the API is rather intuitive and simple to use, we can go over and mention a couple of things that might seem to be "missing" in making our development experience a tad much better.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;1)&lt;/em&gt; Filling your code with the &lt;code&gt;async&lt;/code&gt; keyword
&lt;/h4&gt;

&lt;p&gt;As you have definitely observed, there is this &lt;em&gt;async&lt;/em&gt; keyword you have to sprinkle all around your code, and it feels a bit noisy for me at least. This keyword is required because of the event-driven nature of the browser APIs. The way to code around asynchronous and event-driven platforms in JavaScript is by using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="noopener noreferrer"&gt;Promises&lt;/a&gt; to model your operations, and Playwright has done just that.&lt;/p&gt;

&lt;p&gt;To make handling of those asynchronous operations a bit less painful, JavaScript has added some new keywords to the language syntax. These keywords are the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function" rel="noopener noreferrer"&gt;async &amp;amp; await&lt;/a&gt; that you see on our code. Because Playwright's API needs to use Promises, the best way we can write our code is to use this &lt;em&gt;async/await&lt;/em&gt; syntax for most commands.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;2)&lt;/em&gt; No chaining available yet
&lt;/h4&gt;

&lt;p&gt;Due to some design decisions and the nature of the library, as we have mentioned in the point above, there is currently no support for what we can call &lt;em&gt;method chainning&lt;/em&gt;. With this capability our code could become so much more fluent to read and follow through. Picture something like:&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;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search-view input&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;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;context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But at some point, we might get there!&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing 🧘‍♂️
&lt;/h2&gt;

&lt;p&gt;So this was a glimpse to get you started with your first Playwright script to assert an actual user scenario. Loads of things to mention for each command and the capabilities but we are gonna see them closer in the recipes that will come on &lt;a href="https://www.thehomeofwebautomation.com/" rel="noopener noreferrer"&gt;The Home of Web Automation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Especially the Playwright's &lt;em&gt;BrowserContext&lt;/em&gt; as advertised is an abstraction that can unlock much more power and performance using parallelization locally or from even the cloud. Pretty excited to try that!&lt;/p&gt;

&lt;p&gt;Playwright might seem &lt;em&gt;new&lt;/em&gt; to the scene but on the contrary it has some long history as we mentioned earlier. If you want to pit it against another tool or introduce it as &lt;em&gt;'X killer'&lt;/em&gt;, sorry but we are not doing that here. The least I can say is that if your application has a considerable slice of WebKit-based browser users, then give Playwright a try, your users will thank you for that 💪&lt;/p&gt;

&lt;p&gt;As with every tool though, start with anything that catches your attention, feels comfortable and addresses your actual needs in a simpler way.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cross posted from &lt;a href="https://www.thehomeofwebautomation.com/getting-started-playwright/" rel="noopener noreferrer"&gt;The Home of Web Automation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Image by &lt;a href="https://pixabay.com/users/Devanath-1785462/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=1248088" rel="noopener noreferrer"&gt;Devanath&lt;/a&gt; from &lt;a href="https://pixabay.com/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=1248088" rel="noopener noreferrer"&gt;Pixabay&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Selenium Works - High Level Architecture</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Tue, 16 Jun 2020 06:44:05 +0000</pubDate>
      <link>https://dev.to/igneel64/how-selenium-works-high-level-architecture-g2c</link>
      <guid>https://dev.to/igneel64/how-selenium-works-high-level-architecture-g2c</guid>
      <description>&lt;p&gt;&lt;a href="https://www.selenium.dev/" rel="noopener noreferrer"&gt;Selenium WebDriver&lt;/a&gt; undoubtedly remains the king/queen of web automation and testing. Tooling, frameworks, community, job postings, millions of people interacting with it and the rest of the ecosystem is resting on the shoulders of a project that started back at 2004. That proves that powered by the effort of all those people maintaining it and contributing to it, it is that good on what aims to do.&lt;/p&gt;

&lt;p&gt;Even with how lean the introduction is, independently if you are working currently with Selenium or not, it stands to reason that taking a peek on what the architecture of this tool formed out to be will only be to your benefit. Enjoy the view.&lt;/p&gt;

&lt;h2&gt;
  
  
  The High Level
&lt;/h2&gt;

&lt;p&gt;First off, most people that are using Selenium directly, &lt;em&gt;by directly meaning a &lt;a href="https://www.selenium.dev/documentation/en/selenium_installation/installing_selenium_libraries/" rel="noopener noreferrer"&gt;client library&lt;/a&gt;&lt;/em&gt;, are writting scripts, using their favorite language, that describe specific steps and interactions they want to simulate on a real browser instance. That could be from navigating to pages, clicking elements to submitting forms and &lt;em&gt;scrapping content without permission&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebDriver&lt;/span&gt; &lt;span class="n"&gt;driver&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;ChromeDriver&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;                  

&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://www.selenium.dev/projects/"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Open this URL in the browser instance      &lt;/span&gt;

&lt;span class="nc"&gt;WebElement&lt;/span&gt; &lt;span class="n"&gt;searchElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findElement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;By&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Find the hipster search of this website            &lt;/span&gt;
&lt;span class="n"&gt;searchElement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendKeys&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"firefox"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Fill the input with the search term&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This above is just a simple example of code in Java that QA engineers are used to writting for their day to day responsibilities. You run this script (&lt;em&gt;with the rest of the boilerplate&lt;/em&gt;) and you get a Chrome window on the Selenium website, with the "firefox" term on the search field, straightforward. The exact same API we used is exposed in different language bindings like JavaScript, Python and Ruby. &lt;/p&gt;

&lt;p&gt;For some individuals though, the process and the parts that these commands have to go through to arrive at the browser and how they operate together is kind of a blurry picture.&lt;/p&gt;

&lt;p&gt;We can start clearing the blur with the illustration below:&lt;/p&gt;

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

&lt;p&gt;Some of the components might seem familiar, others not so much. We are gonna break them down one by one so as to connect the dots and fill any potential bumps in your understanding of Selenium WebDriver.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Components
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Client Libraries
&lt;/h3&gt;

&lt;p&gt;Client libraries or otherwise known as &lt;em&gt;language bindings&lt;/em&gt;, are libraries that allow developers to use the Selenium high level API in their language of choice. Without knowing all the details, usually what "client libraries" provide, is wrappers over some transport mechanism to communicate with a fixed set of endpoints. &lt;em&gt;Think of them as just a thin layer that most of the times translates _HTTP&lt;/em&gt; handling from one language to another._&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SeleniumHQ/selenium/blob/master/javascript/webdriver/http/http.js" rel="noopener noreferrer"&gt;This seems to be the case&lt;/a&gt; at least with the JavaScript wrapper of WebDriver.&lt;/p&gt;

&lt;h3&gt;
  
  
  JsonWire Protocol
&lt;/h3&gt;

&lt;p&gt;As mentioned in &lt;em&gt;(1)&lt;/em&gt; client libraries use a specific HTTP API, kinda rest-&lt;em&gt;ish&lt;/em&gt; as &lt;a href="https://www.theautomatedtester.co.uk/blog/how-selenium-works-transport/" rel="noopener noreferrer"&gt;David Burns say&lt;/a&gt;, to communicate their desired commands to the &lt;em&gt;browser driver&lt;/em&gt; that we will speak about in a moment.&lt;/p&gt;

&lt;p&gt;In order to prevent chaos, pains and expected deviances from client library authors and maintainers, a system needs to define a standarized way to communicate with the outside world. The decision for WebDriver, to communicate as uniformly as possible with &lt;em&gt;browser drivers&lt;/em&gt;, was the implementation of the &lt;a href="https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol" rel="noopener noreferrer"&gt;JsonWire protocol&lt;/a&gt; and as you can imagine by now, it is JSON over HTTP. The specification for this API endpoints can be found &lt;a href="https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#command-reference" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To see how straightforward it can be, here is a small 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="cm"&gt;/* 
 * Send the command to the session with id=sessionId 
 * to initiate a navigation to the "url" body parameter 
 */&lt;/span&gt;

&lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&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;https://example.com&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;Going to the JsonWire specification page from GitHub, you must have noticed the OBSOLETE warning, but do not falter. It does not mean that it is an abandoned project or that the information there is irrelevant, on the contrary it has taken a big step forward. As widely used as this protocol came to be, with all the huge ecosystem of tech and people behind it, managed to become an actual &lt;em&gt;Working Draft&lt;/em&gt; of the W3C standard.&lt;/p&gt;

&lt;p&gt;That fact makes it an &lt;a href="https://www.w3.org/TR/webdriver/" rel="noopener noreferrer"&gt;official protocol&lt;/a&gt; that user agents aim to implement and conform, in order for programs to remotely instruct the behaviour of web browsers. A huge success and recognition for the WebDriver project in my opinion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser Driver
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;browser driver&lt;/em&gt; can be considered the "backend" of the WebDriver architecture. It acts as the end host of the JsonWire protocol accepting commands in the form we shown earlier and responding in kind with values, errors, stats or anything that the protocol defines. On one side it actually works exactly like a regular standalone HTTP server for the client libraries. On the other end, it is using its own HTTP API to communicate, send commands and receive responses from the actual browser instance as it instruments its behaviour.&lt;/p&gt;

&lt;p&gt;As you might expect or have noticed while trying to use Selenium, there are specific drivers for different browsers that implement the meaty part of actually sending the commands that retrieved from the client libraries to the browser instance. For example there is the GeckoDriver that supports Firefox, the ChromeDriver that supports Chrome/Chromium, the OperaDriver for Opera &lt;a href="https://www.selenium.dev/downloads/#browsersCollapse" rel="noopener noreferrer"&gt;and more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each of these &lt;em&gt;browser drivers&lt;/em&gt; uses its own way and implementation to communicate with the actual browser instance. Some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ChromeDriver uses the &lt;a href="https://chromedevtools.github.io/devtools-protocol/" rel="noopener noreferrer"&gt;DevTools Protocol&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The GeckoDriver uses the &lt;a href="https://firefox-source-docs.mozilla.org/testing/marionette/" rel="noopener noreferrer"&gt;Marionette&lt;/a&gt; remote protocol.&lt;/li&gt;
&lt;li&gt;The OperaDriver (for Opera&amp;gt;=26) is based on ChromeDriver with &lt;a href="https://github.com/operasoftware/operachromiumdriver" rel="noopener noreferrer"&gt;some adaptations&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can understand some of the drivers may not be open sourced by the maintaining companies but I am confident you can find more information if you look through the pages and documentation for each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser
&lt;/h3&gt;

&lt;p&gt;The actual browser instance that will receive the commands for its respective browser driver, and finally simulate the web automation task we planned all along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;That was all about the close look we attempted at the high level architectural components that make up the 'Selenium Architecture' as I see it. Hope you enjoyed it as much as I did while I was looking into how all the actors come together and how much work and dilligence is put in the development and maintenance of each project.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cross posted from &lt;a href="https://www.thehomeofwebautomation.com/how-selenium-works-architecture/" rel="noopener noreferrer"&gt;The Home of Web Automation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by &lt;a href="https://pixabay.com/users/Tama66-1032521/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=5146458" rel="noopener noreferrer"&gt;Peter H&lt;/a&gt; from &lt;a href="https://pixabay.com/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=5146458" rel="noopener noreferrer"&gt;Pixabay&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>selenium</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Home of Web Automation - A different take on a software blog</title>
      <dc:creator>Peter Perlepes</dc:creator>
      <pubDate>Mon, 15 Jun 2020 10:43:23 +0000</pubDate>
      <link>https://dev.to/igneel64/the-home-of-web-automation-a-different-take-on-a-software-blog-1kgp</link>
      <guid>https://dev.to/igneel64/the-home-of-web-automation-a-different-take-on-a-software-blog-1kgp</guid>
      <description>&lt;p&gt;&lt;em&gt;About a month ago, I decided to take my passion and knowledge on web automation and testing and turn them into a blog. That came to be &lt;a href="https://www.thehomeofwebautomation.com/"&gt;The Home of Web Automation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Need
&lt;/h2&gt;

&lt;p&gt;In the last few years the scene of &lt;em&gt;Web Automation&lt;/em&gt; has received a surge of attention from the software industry, and that is not without reason.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliability is a must for both new-found companies and large enterprises alike.&lt;/li&gt;
&lt;li&gt;Applications are more interactive and a fair amount of times &lt;em&gt;frontend heavy&lt;/em&gt;, meaning that they are responsible for orchestrating lots of different actors.&lt;/li&gt;
&lt;li&gt;Manual repetitive tasks and testing is still proving to be a real pain when trying to move fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the above (&lt;em&gt;and tons more&lt;/em&gt;) appear to be fair reasons to invest in the skills, technologies and methodologies that revolve around web automation. &lt;/p&gt;

&lt;p&gt;Additionally there seems to be a considerable gap in the technical skills that individuals require to succeed in these efforts, I aim to help as much as possible with that. For people that want more information on the topic, there is a &lt;a href="https://www.tricentis.com/state-of-open-source-2020/"&gt;2020 survey around open-source web automation testing tools&lt;/a&gt; that is quite revealing.&lt;/p&gt;

&lt;p&gt;Being in the shoes that most people reading this post have been, I can sympathize with the fact that this can be &lt;em&gt;just another topic to learn&lt;/em&gt;. Understanding the information fatigue that the software industry has been bearing on its denizens, I will try my best to make the experience as &lt;strong&gt;joyful as possible&lt;/strong&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Different Take
&lt;/h2&gt;

&lt;p&gt;From my experiences in the world of software and how rapidly it is progressing, all the frameworks, the libraries, the techniques, it can make one feel really &lt;strong&gt;anxious&lt;/strong&gt; and &lt;strong&gt;fatigued&lt;/strong&gt; about being required to consume and understand all of these intricacies. More articles to read, more podcasts to listen to, more conference talks that you would put in your &lt;em&gt;"todo"&lt;/em&gt; list.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is it important ?&lt;/em&gt; Heck yes it is. Your career, financials and dreams might depend on it. But in this race, can there be a relaxing spot where you can keep getting the relevant technical stuff ?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why not give it a try?&lt;/em&gt; That was what I felt like at least while building this website.&lt;/p&gt;

&lt;p&gt;During the process of putting together the visual aspect, pallete, fonts, image sources and the like, I decided to take a different route than what would be expected on a software related site.&lt;/p&gt;

&lt;h3&gt;
  
  
  A warm kind of pallete
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vW_qxxuJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wgumu0xzpjb67m2etboi.png" class="article-body-image-wrapper"&gt;&lt;img alt="jet orange beige" src="https://res.cloudinary.com/practicaldev/image/fetch/s--vW_qxxuJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wgumu0xzpjb67m2etboi.png"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Playful, almost meaningless drawings
&lt;/h3&gt;

&lt;p&gt;I cannot stress enough how much I love &lt;a href="https://excalidraw.com/"&gt;Excalidraw&lt;/a&gt;💙 and how extensively I will be using it to create some meaningless drawings (&lt;em&gt;But also for the diagrams required&lt;/em&gt;) for each post. As it sparks some joy for me, I hope it does the same to you!&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VR8os0ev--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ijq2ve119uz9uxfivwno.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VR8os0ev--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ijq2ve119uz9uxfivwno.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Weird cursive fonts
&lt;/h3&gt;

&lt;p&gt;Using &lt;a href="https://fonts.google.com/specimen/Caveat"&gt;Caveat&lt;/a&gt; from Google Fonts seemed to fit my target amazingly well.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jo6XbW_---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8sk5kghuz2l542iyx63x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jo6XbW_---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8sk5kghuz2l542iyx63x.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A try on software jokes
&lt;/h3&gt;

&lt;p&gt;If at some point I prompt you to install &lt;a href="https://www.npmjs.com/package/left-pad"&gt;left-pad&lt;/a&gt; or throw a joke at some website's UX decision, please understand it is benevolent 😁&lt;/p&gt;

&lt;p&gt;The interface is composed of elements that their purpose is to make you more energetic and spark the feeling of happiness when you open a page, &lt;em&gt;even while not being the most readable&lt;/em&gt;. Actually this became one of the top goals to be reflected on the user interface. &lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;If you are into web automation, testing and all the ecosystem around those, you can follow for new content &lt;a href="https://twitter.com/HomeOfWebAuto"&gt;@HomeOfWebAuto&lt;/a&gt;. &lt;br&gt;
&lt;em&gt;I would be writting about how I built the blog in a later post&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Any feedback is appreciated, if you have any questions/request reach out to me and I hope you enjoy it!&lt;/p&gt;

</description>
      <category>testing</category>
      <category>design</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
