<?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: Scott White</title>
    <description>The latest articles on DEV Community by Scott White (@scahhht).</description>
    <link>https://dev.to/scahhht</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%2F232202%2F1f0f2ffc-d3aa-4b00-8bee-701371dd1960.jpg</url>
      <title>DEV Community: Scott White</title>
      <link>https://dev.to/scahhht</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scahhht"/>
    <language>en</language>
    <item>
      <title>Using Google's OCR API with Puppeteer for Visual Testing</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Mon, 08 Feb 2021 23:11:53 +0000</pubDate>
      <link>https://dev.to/walrusai/using-google-s-ocr-api-with-puppeteer-for-visual-testing-42m6</link>
      <guid>https://dev.to/walrusai/using-google-s-ocr-api-with-puppeteer-for-visual-testing-42m6</guid>
      <description>&lt;p&gt;The &lt;a href="https://developers.google.com/web/tools/puppeteer" rel="noopener noreferrer"&gt;Puppeteer framework&lt;/a&gt; is a Node.js library that can be used for automated browser testing. This library provides high-level access to chromium-based browsers through the dev tools protocol. In automated testing, you can use assertion libraries like chai.js or should.js to make assertions about elements or objects that should appear in an application. &lt;/p&gt;

&lt;p&gt;One of the most common scenarios in automated testing is to assert if a word or phrase is displayed in a web application. There are few methods available for a developer to accomplish this task.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One of them is to iterate through all the elements in a web page to look for the matching word. However, this method is inefficient and error-prone. Besides, querying every element is time-consuming and will result in incorrect assertions as you have not scoped any part of the page, meaning the word could appear in unexpected locations.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// Iterate through all elements&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contents&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="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;getProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;innerText&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;jsonValue&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;text_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Hello/g&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text_value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;A better method is selecting elements using the XPath to find the element which contains the specified word. This is far simpler and more efficient than iterating through all the elements. However, without a defined scope, this method will also provide results from the whole web page.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;          &lt;span class="c1"&gt;// Using XPath&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&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;$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;//h1[contains(text(), 'Domain')]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The most effective method would be to use optical character recognition (OCR)  to specify a part of the page and carry out searching within that scope to find the location of the word. Using OCR allows you to utilize a powerful character recognition algorithm without having to go through HTML elements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What is OCR?
&lt;/h1&gt;

&lt;p&gt;Optical Character Recognition, or OCR, is a technology that enables you to convert images of text content into machine-readable, digital data. OCR can convert any type of text content, whether it’s handwritten or printed, into editable and searchable data using an image of that text content. This is done by processing the image through a machine-learning algorithm to clean and recognize each character of a specific image.&lt;/p&gt;

&lt;p&gt;There are multiple open-source OCR tools like &lt;a href="https://github.com/madmaze/pytesseract" rel="noopener noreferrer"&gt;pytesseract &lt;/a&gt;or &lt;a href="https://github.com/JaidedAI/EasyOCR" rel="noopener noreferrer"&gt;EasyOCR&lt;/a&gt;, which can be used to integrate OCR functionality into a program. However, these tools require significant configurations to get up and running to provide results with an acceptable accuracy level.&lt;/p&gt;

&lt;p&gt;Google provides a ready-made solution to integrate OCR functionality to an application using its &lt;a href="https://cloud.google.com/vision/docs/reference/rest/?apix=true" rel="noopener noreferrer"&gt;Vision API&lt;/a&gt;. It will be used in this tutorial since it abstracts most of the model fine-tuning. The Vision API provides two distinct detection types to extract text from images, as mentioned below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TEXT_DETECTION&lt;/strong&gt; - This will detect and extract text from any provided image. The resulting JSON will include the extracted string and individual words with their bounding boxes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DOCUMENT_TEXT_DETECTION&lt;/strong&gt; - This will also detect and extract text from any image but is optimized to be used for dense text and documents. Its JSON output will feature all the details, including extracted strings plus page, block, paragraph, word, and break information.&lt;/p&gt;

&lt;p&gt;Let’s take a look at the following image, which contains a dense text block.&lt;br&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%2Fg006nx3c42f7419auzbl.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%2Fg006nx3c42f7419auzbl.png" alt="Text overlaid on an image of a computer with app icons"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following code can be used to implement both detection types to extract the text in that image but there will be some differences in the output. If you want to use “DOCUMENT_TEXT_DETECION”, uncomment it and comment the line type: 'TEXT_DETECTION'.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [result] = await client.annotateImage({
        image: {
            content: imageContent,
        },
        features: [{
            type: 'TEXT_DETECTION',
            // type: 'DOCUMENT_TEXT_DETECTION',
        }]
    });

    const textAnno = result.textAnnotations;
    textAnno.forEach(text =&amp;gt; console.log(text));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;TEXT_DETECTION Output&lt;/strong&gt;&lt;br&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%2Fn6o4ekultz3btlb1gfw1.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%2Fn6o4ekultz3btlb1gfw1.png" alt="text detection from a document"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DOCUMENT_TEXT_DETECTION Output&lt;/strong&gt;&lt;br&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%2Fb1u1a54k3hpx422vkoqv.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%2Fb1u1a54k3hpx422vkoqv.png" alt="text detection from a document"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, while &lt;strong&gt;TEXT_DETECTION&lt;/strong&gt; was able to simply extract the text from the image, the &lt;strong&gt;DOCUMENT_TEXT_DETECTION&lt;/strong&gt; was also able to identify the period (.) punctuation mark as the separating character. This is due to the &lt;strong&gt;DOCUMENT_TEXT_DETECTION&lt;/strong&gt; being geared towards denser text blocks like documents and can extract more intricate details, while &lt;strong&gt;TEXT_DETECTION&lt;/strong&gt; is more suited for large text objects like street signs, billboards, etc.&lt;/p&gt;
&lt;h1&gt;
  
  
  Google Vision API with Puppeteer
&lt;/h1&gt;

&lt;p&gt;In this section, you will be using DOCUMENT_TEXT_DETECTION as you are working with web content. Now, let’s start incorporating Vision API (OCR) within a Puppeteer program.&lt;/p&gt;

&lt;p&gt;First, capture a screenshot of the text area you need to extract text from the Puppeteer's &lt;strong&gt;page.screenshot()&lt;/strong&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const browser = await puppeteer.launch({headless:true});
        const page = await browser.newPage();
        await page.goto('https://www.example.com');  

        const imageFilePath = 'ocr-text-block.png'

        // Take screenshot
        await page.screenshot({
            path: imageFilePath,
            clip: { x: 0, y: 0, width: 800, height: 400 },
            omitBackground: true,
        });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you have to encode the image to &lt;strong&gt;base64&lt;/strong&gt; format in order to pass the resulting data to the Vision API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        // Read the image contents
        const imageContent = Buffer.from(fs.readFileSync(imageFilePath)).toString('base64');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, pass the data to the Vision API and you will get the following result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        // Pass the content
        const client = new vision.ImageAnnotatorClient();
        const [documentAnnotateResult] = await client.annotateImage({
            image: {
                content: imageContent,
            },
            features: [{
                type: 'DOCUMENT_TEXT_DETECTION',

            }]
        });

        // Get the annotated result
        console.log(documentAnnotateResult.fullTextAnnotation)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you have obtained the OCR result without any errors, compare the image you have captured with the corresponding output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Captured image (ocr-text-block.png)&lt;/strong&gt;&lt;br&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%2F0u0jq90i8o4gm2ntejt8.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%2F0u0jq90i8o4gm2ntejt8.png" alt="example domain written on a white background"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OCR Result&lt;/strong&gt;&lt;br&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%2Fhzxrkcj3avjwuwrjq7yk.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%2Fhzxrkcj3avjwuwrjq7yk.png" alt="terminal output using google ocr api"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it! Congratulations, now you have successfully integrated the OCR functionality. The next step would be to iterate over the text annotations and find a match against the string you are searching for. In this instance, you will be searching for a string called “permission”.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        var point_x = 0
        var point_y = 0
        //Iterate the elements
        const textAnno = documentAnnotateResult.textAnnotations;
        textAnno.forEach(text =&amp;gt; {
            if(text.description === 'permission') {

                point_x = text.boundingPoly.vertices[0].x;
                point_y = text.boundingPoly.vertices[0].y;            
            }
        }
        );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Iterate through the results using a &lt;strong&gt;forEach&lt;/strong&gt; loop and if a matching text is found, obtain its coordinates from &lt;strong&gt;boundingPoly.vertices&lt;/strong&gt; and store them in &lt;strong&gt;point_x&lt;/strong&gt; and &lt;strong&gt;point_y&lt;/strong&gt; variables.&lt;/p&gt;

&lt;p&gt;Then, you have to invoke the &lt;strong&gt;document.getElementFromPoint&lt;/strong&gt; function to get the DOM element that contains the matched text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        // Find the element using elementFromPoint
        const foundElement = await page.evaluateHandle((x,y) =&amp;gt; document.elementFromPoint(x,y), point_x,point_y);
        console.log(foundElement._remoteObject.description)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the word “permissions” is inside a paragraph tag (&amp;lt;p&amp;gt;), the console output will show a DOM object description attribute like the following.&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%2Fpzcuduzn2ixmwun5g8me.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%2Fpzcuduzn2ixmwun5g8me.png" alt="ocr javascript file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Limitations of OCR
&lt;/h1&gt;

&lt;p&gt;OCR is a powerful tool when it comes to identifying web content. However, it is not foolproof, and its major issue will be the accuracy of character recognition. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing some characters entirely.&lt;/li&gt;
&lt;li&gt;Mixing up similar characters (6 to b, o to 0, I to l, etc.)&lt;/li&gt;
&lt;li&gt;Messing up spaces in documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The points mentioned above are some facts that lead to the inaccuracy of the result. Refer to the following image and its OCR result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image&lt;/strong&gt;&lt;br&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%2Fxv7ahyfhaeva1hbuqpw2.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%2Fxv7ahyfhaeva1hbuqpw2.png" alt="The words Los Altos on a white background"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OCR Result&lt;/strong&gt;&lt;br&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%2F12qn7bslit0kc45g9gs8.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%2F12qn7bslit0kc45g9gs8.png" alt="OCR result of predicting Los Altos image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can assert that the word in the image is “Los Altos”, but the OCR function mistakenly detects the letter “t” as the plus (+) sign. This will cause incorrect character recognition and even breaks the word Altos into three separate segments. (AL, + and OS). These kinds of issues are very common when dealing with handwritten text in images or documents.&lt;/p&gt;

&lt;p&gt;To mitigate these issues and keep the Puppeteer test resilient, you need to have some room for variance in OCR results. The simplest method would be to handle the commonly mistaken characters. For example, suppose you encounter a &lt;code&gt;6&lt;/code&gt; that doesn't match your string, substitute &lt;code&gt;6&lt;/code&gt; with &lt;code&gt;b&lt;/code&gt;, and check again. However, This method will quickly become cumbersome and not a very flexible solution to handle multiple inconsistencies. \&lt;/p&gt;

&lt;p&gt;Another option would be to implement a word distance algorithm based on a string metric like &lt;a href="https://en.wikipedia.org/wiki/Levenshtein_distance" rel="noopener noreferrer"&gt;Levenshtein distance&lt;/a&gt;, which measures the difference between two sequences. You could specify a threshold that allows for some variance in the results while still getting the confidence that the desired text was found in the specified area.&lt;/p&gt;

&lt;h1&gt;
  
  
  Going beyond visual testing
&lt;/h1&gt;

&lt;p&gt;Visual testing is an essential piece of the testing toolkit. It's a great way to ensure you aren't introducing visual anomalies as you make changes to your app, and it can be helpful in making simple assertions like the presence of text or images. &lt;/p&gt;

&lt;p&gt;However, image-based-testing is not one-size-fits-all. To successfully end-to-end test your product, you'll likely need to support a much wider range of assertions and actions (including uploading files, verifying text messages or emails are received, etc.). &lt;/p&gt;

&lt;p&gt;To support the automation of testing these harder assertions and experiences, you'll need to expand your toolkit to go beyond visual testing. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looking to go beyond visual testing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://walrus.ai" rel="noopener noreferrer"&gt;walrus.ai&lt;/a&gt; provides visual assertions and handles everything mentioned in this article out of the box. Beyond that, &lt;a href="https://walrus.ai" rel="noopener noreferrer"&gt;walrus.ai&lt;/a&gt; can be used to test your hardest user experiences end-to-end. Drop us a line if you'd like to see it in action!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>testdev</category>
    </item>
    <item>
      <title>5 Tips for Writing Better Unit Tests</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Thu, 04 Feb 2021 17:07:00 +0000</pubDate>
      <link>https://dev.to/walrusai/5-tips-for-writing-better-unit-tests-5d17</link>
      <guid>https://dev.to/walrusai/5-tips-for-writing-better-unit-tests-5d17</guid>
      <description>&lt;h1&gt;
  
  
  What are Unit Tests?
&lt;/h1&gt;

&lt;p&gt;Unit testing is the testing of individual parts or components of a program. The purpose of unit testing is to test the functionality of a single component and verify its behavior independently from other components. A unit in unit testing includes the smallest piece of functionality that can be tested. The main objective of a unit test is to isolate the smallest parts of a program and test the functionality of each part. The basic structure of a unit test is designed to check if the output of a defined code block matches the given condition. For example, when testing a function, we specify a set of input arguments and test if the function returns the expected output, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;func.py&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ==== Function ==== #
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Calculations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

  &lt;span class="c1"&gt;# Function to Test
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;value2&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;test.py&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ==== Unit Test ==== #
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;func&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Calculations&lt;/span&gt;

&lt;span class="c1"&gt;# Test Class
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestingFunctions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# Test Case
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_calculation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;input_one&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
        &lt;span class="n"&gt;input_two&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Calculations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_two&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Identify if output is equal to zero
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Avoid Complex Setups
&lt;/h1&gt;

&lt;p&gt;Since we are testing the functionality of the simplest testable unit during the unit testing, it is better to keep the tests also as simple as possible. One of the major difficulties developers face during unit testing is the test cases becoming hard to understand and change due to making them unnecessarily complex.&lt;/p&gt;

&lt;p&gt;The AAA Rule provides a good guideline to keep a unit test simple as much as possible. In unit testing, AAA stands for Arrange, Act, and Assert. The first step is to arrange the test case with the input variables, properties, and conditions to get the expected output. The next step is to act upon your arrangements by calling the methods or functions. In the final step, which is the assertion, you can assert to verify the expected output using a test framework. This method makes the codebase of test cases much easier to read and understand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt;

&lt;span class="c1"&gt;# AAA Methodology
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestingStringFunctions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_string_manipulation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Arrange
&lt;/span&gt;        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello World"&lt;/span&gt;

        &lt;span class="c1"&gt;# Act
&lt;/span&gt;        &lt;span class="n"&gt;new_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="c1"&gt;# Assert
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'dlroW olleH'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another important thing to consider when performing unit tests is the test dependencies. If a test case depends on another test case, changing a single test case could affect other test cases, which will, in turn, defeat the purpose of individual unit tests.&lt;/p&gt;

&lt;h1&gt;
  
  
  Exhaustiveness is not the Goal
&lt;/h1&gt;

&lt;p&gt;It is always a good idea to perform comprehensive unit testing for your program. However, it does not mean that you should write test cases for each and every scenario. Considering every edge case beforehand is a waste of valuable development time.&lt;/p&gt;

&lt;p&gt;Tests should be straight forward covering the common flow of your code block. Once simple tests are passing, you can increase the coverage of the test to include edge cases and cover multiple boundaries. When a bug is discovered in the development cycle, you can expand the test coverage to include that bug. This will keep your test cases up to date without having to create separate test cases to tackle the bugs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Write Tests First When Fixing Bugs
&lt;/h1&gt;

&lt;p&gt;The first intention of a developer when discovering a bug is to try and fix it somehow. Although she would be able to fix the bug at that time, her understanding of the bug may be limited and it might cause new issues down the pipeline in the future.&lt;/p&gt;

&lt;p&gt;Therefore, it is always a good practice to create a test for the discovered bug encompassing the components that cause the bug. This way, you can verify if the bug relates to the indicated components or is caused by an external factor. Having a simple and straightforward test will lead to an easier bug fix by quickly exposing the problems.&lt;/p&gt;

&lt;p&gt;When you write your test, make sure to run it before fixing the bug and verifying that the test fails. Otherwise, you may write a test that doesn’t actually encapsulate the bug, therefore defeating the entire purpose.&lt;/p&gt;

&lt;p&gt;Once a bug is fixed, you should not discard that unit test. These tests can then also be used in regression testing to test the overall functionality of the program. It will be an extra benefit as we know both the failing and passing conditions of that test in a future event when a similar bug is discovered. That will also make it easy to know where to apply the fix.&lt;/p&gt;

&lt;h1&gt;
  
  
  Mock with Caution
&lt;/h1&gt;

&lt;p&gt;Mocking is a way of simulating the behavior of a real object in a controlled manner by creating an artificial object (mock object) similar to it. Mock objects enable developers to mock the behavior of complex functions without having to invoke the function. This is useful when dealing with external services where you require those services only to execute the underlying code block but they are not needed for the test case.&lt;/p&gt;

&lt;p&gt;Mocks are also useful in increasing the speed and efficiency of a test case. Without mocks, if a test calls for an external library or a function, you must wait for that function to be completed to continue the test. This will lead to slow test execution times and can even introduce unnecessary errors or behavior inside the test case. For example, when dealing with the file systems and network functions, you could use a mock object to simulate those behaviors, effectively speeding up the test while focusing on the core functionality of the test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;func&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;delete_file&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestFunctions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# Mock the os module
&lt;/span&gt;    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'func.os'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_file_delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mockobj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test.txt"&lt;/span&gt;
        &lt;span class="n"&gt;delete_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;# Verify the parameters were passed correctly to the function
&lt;/span&gt;        &lt;span class="n"&gt;mockobj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_called_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important thing to remember when using mocks is not to mock the functionality of the test case. Mocks should not be used if you are testing for database queries, API calls, or any functionality that has a direct impact on your overall test.&lt;/p&gt;

&lt;h1&gt;
  
  
  Keep Tests Independent
&lt;/h1&gt;

&lt;p&gt;The tests should be orthogonal to each other and be used to test a single functionality at a time. The test classes should be tested independently and should not depend on anything other than the testing framework.&lt;/p&gt;

&lt;p&gt;When an order is needed to carry out unit testing, you can use the inbuilt setup and teardown methods of the test framework to isolate the test cases. A good test case should not be affected by any implementation changes. When dealing with API changes, a program with a solid code base with correctly implemented individual unit tests is vital for identifying errors and making the program up and running again.&lt;/p&gt;

&lt;p&gt;Besides, creating independent tests enables us to easily modify each test case while increasing the test spectrum without causing any side effects to other tests. In test automation environments, independent tests are the preferred way to integrate with CI/CD pipelines.&lt;/p&gt;

&lt;h1&gt;
  
  
  The next step
&lt;/h1&gt;

&lt;p&gt;Unit tests are a great way to discover and resolve bugs in units, ensuring that every single part of the program works well. However, if you want to test different parts of the program working together, in their actual environment, you need to go for integration testing or end-to-end. You can learn more about the testing hierarchy &lt;a href="https://walrus.ai/blog/2019/11/introduction-end-to-end-testing/"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Integration tests abstracted up a level from unit tests, executing and asserting on code paths that traverse between multiple units or modules. Now we're starting to ensure the different pieces of our application interact with each other as expected.&lt;/p&gt;

&lt;p&gt;Even farther are end-to-end (E2E) tests. These are tests aimed to cover the entire surface area of your application from top to bottom. These should generally follow the code paths expected from your end-users to ensure they're as close to reality as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looking for an easy way to take the next step beyond unit testing?&lt;/strong&gt; Check out &lt;a href="https://walrus.ai"&gt;walrus.ai&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests can be written in plain english — no difficult setup required&lt;/li&gt;
&lt;li&gt;Test your most challenging user experiences – emails, multi-user flows, third-party integrations&lt;/li&gt;
&lt;li&gt;Zero maintenance — walrus.ai handles test maintenance for you, so you never experience flakes&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>testdev</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Simple Steps for Testing a Chrome Extension in Puppeteer</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Wed, 03 Feb 2021 22:03:28 +0000</pubDate>
      <link>https://dev.to/scahhht/simple-steps-for-testing-a-chrome-extension-in-puppeteer-2pm3</link>
      <guid>https://dev.to/scahhht/simple-steps-for-testing-a-chrome-extension-in-puppeteer-2pm3</guid>
      <description>&lt;h1&gt;
  
  
  What is Puppeteer?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/web/tools/puppeteer" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; is a Node library that can be used for the automated testing of chrome extensions. It provides a high-level API control over Chrome or Chromium via the DevTools protocol. One great advantage of this library is that it can be used to do most of the things you manually do in the browser. &lt;/p&gt;

&lt;p&gt;When testing chrome extensions, there are two major test scenarios, which are testing the compatibility of the extension and testing functionality of the extension. You can test the compatibility of a chromium-based browser extension by loading that extension on each browser variant. When it comes to testing the functionality, the puppeteer library provides various methods to imitate user interactions. This allows developers to create automated test cases to inspect and analyze the behavior of an extension.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing Extension Compatibility
&lt;/h1&gt;

&lt;p&gt;Chrome extensions are inherently cross-browser compatible. Most chrome extensions can also be used in other chromium-based browsers such as MS Edge (Chromium), Vivaldi, Brave, etc. Puppeteer provides users the ability to specify the browser in the puppeteer class. In the following section, you will see how to load a browser extension in different browsers.&lt;/p&gt;

&lt;p&gt;When using the normal puppeteer library, it will install the latest Chromium build, and  therefore it is not required to provide the location of the browser and get called directly.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;// Calling the default chromium installation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, if you are using the lightweight puppeteer-core or defining a different browser, you need to provide the executablePath option in the puppeteer.launch method. The below code demonstrates how to test the installation of  an extension on MS Edge (Chromium) &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;browser_test.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Path to extension folder&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;F:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;puppeteer&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;ext&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;cplklnmnlbnpmjogncfgfijoopmnlemp&lt;/span&gt;&lt;span class="se"&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Open Browser&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;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="c1"&gt;// Define the browser location&lt;/span&gt;
            &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Program Files (x86)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Microsoft&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Edge Beta&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Application&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;msedge.exe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// Disable headless mode&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;// Pass the options to install the extension&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;`--disable-extensions-except=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;paths&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="s2"&gt;`--load-extension=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;paths&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="s2"&gt;`--window-size=800,600`&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Navigate to Extension&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Navigate to extension page&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome-extension://fgbekoibaojaiamoheiklljjiihibdcb/panel.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Take a screenshot of the extension page&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Take Screenshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msedge-extension.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Close Browser&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;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="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The following code-block will open a new MS Edge (Chromium) browser and install the chrome extension using the provided optional arguments.&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="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;disable&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;except&lt;/span&gt;&lt;span class="o"&gt;=&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Extension&lt;/span&gt; &lt;span class="nx"&gt;Folder&lt;/span&gt; &lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="o"&gt;=&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Extention&lt;/span&gt; &lt;span class="nx"&gt;Folder&lt;/span&gt; &lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The load-extension option will load the plugin while the --disable-extensions-except option will disable other extensions that might interfere with the testing.&lt;/p&gt;

&lt;p&gt;A new browser page (tab) will then be created, and you will navigate to the extension URL and take a screenshot. The extension URL can be defined in the below format.&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Extension&lt;/span&gt; &lt;span class="nx"&gt;Protocol&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;//&amp;lt;Extension ID&amp;gt;/&amp;lt;Extension Page&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;\&lt;br&gt;
Additionally, the code is encapsulated within an async function to enforce an asynchronous operation in each stage of the puppeteer process. &lt;/p&gt;

&lt;p&gt;You can make sure that the test is successful by checking the terminal output and the captured screenshot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terminal&lt;/strong&gt;&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%2F0qojft7ujhxzd9qdlzst.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%2F0qojft7ujhxzd9qdlzst.png" alt="the result of a chrome extension test being run in the terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Captured Screenshot (msedge-extension.png)&lt;/strong&gt;&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%2Fzjnoebux22a5bhm1hv0w.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%2Fzjnoebux22a5bhm1hv0w.png" alt="Screenshot generated by puppeteer script"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can define the location for any other chromium-based browser in the same way that is used to define the location for MS Edge (Chromium).&lt;/p&gt;
&lt;h1&gt;
  
  
  Testing the Extension Functionality
&lt;/h1&gt;

&lt;p&gt;Now you know how to load an extension to a browser, and next comes the functionality testing of the extension. But before that, you have to tackle a major obstacle in defining the extension URL. Whenever a new browser instance is loaded, it will create a new extension ID, making it difficult to define a static path for the extension. &lt;/p&gt;

&lt;p&gt;However, Puppeteer provides a way to query for the extension ID by utilizing a background script.  There, the extension ID can be extracted using the targets method in the Browser class, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;find-extensionid.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Define the extension path&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;F:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;puppeteer&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;ext&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;cplklnmnlbnpmjogncfgfijoopmnlemp&lt;/span&gt;&lt;span class="se"&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Open Browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Configure the browser (Default Chromium Installation)&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// Chrome options&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;`--disable-extensions-except=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;paths&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="s2"&gt;`--load-extension=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;paths&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="s2"&gt;`--window-size=800,600`&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Name of the extension&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iMacros for Chrome&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 extension&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targets&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;targets&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;extensionTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;_targetInfo&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;_targetInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;extensionName&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;_targetInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background_page&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;// Extract the URL&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;extensionTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_targetInfo&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Extracted URL ==&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;extensionURL&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;urlSplit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;extensionURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Split URL ==&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlSplit&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;extensionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlSplit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Extension ID ==&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;extensionID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Define the extension page&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionEndURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;panel.html&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 page&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Navigate to Extension&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`chrome-extension://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionEndURL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Take Screenshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chromium-extension.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Close Browser&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;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="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In the above code block, you are querying for the chrome extension using the targets method. The targets method can be used to access the target of any page managed by the browser. In this instance, the target is specified using the extension name (This refers to the extension title defined in the manifest.json file of the extension).&lt;/p&gt;

&lt;p&gt;Once the extension is located, you can extract the complete URL and then split it to retrieve the extension ID. You can observe this process using the console.log() outputs as shown in the following screenshot.&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%2Fld48whe3x26p0w9uqa8c.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%2Fld48whe3x26p0w9uqa8c.png" alt="The console log output of extracting a chrome extension ID in puppeteer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step is to create a variable to pass the necessary page with the extension. You have created a new variable called extensionEndURL to indicate the page and finally pass both the extension ID and the necessary page to the page.goto() method to direct the browser to the extension location.&lt;/p&gt;

&lt;h1&gt;
  
  
  User Interactions
&lt;/h1&gt;

&lt;p&gt;Now, you have launched the browser and obtained the extension programmatically. The only remaining task is to interact with the extension. Puppeteer provides multiple ways to find elements within a webpage and interact with them. Let us go through the following code sample to see them in action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;google-search.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Calling the default chromium installation&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;defaultViewport&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.google.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Setup Viewport&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;setViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="c1"&gt;// Interact with webpage&lt;/span&gt;
        &lt;span class="c1"&gt;// Search Google&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#tsf &amp;gt; div:nth-child(2) &amp;gt; div.A8SBwf &amp;gt; div.RNNXgb &amp;gt; div &amp;gt; div.a4bIc &amp;gt; input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Puppeteer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name=btnK]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&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="c1"&gt;// Wait for results&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;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Take screenshot&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google-search.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;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="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Above, you have defined a simple script to navigate to Google and perform a search. You are providing input to the search field using the page.type() method and CSS selector to indicate the element and enter the search term. Then, using the page.$eval() method, you are querying for the search button and clicking on that button. This method will use the document.querySelector function to search for the specific element. Then you have to wait for the page to load and take the screenshot.&lt;/p&gt;

&lt;p&gt;All these interactions are based on DOM object querying. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction" rel="noopener noreferrer"&gt;Document Object Model&lt;/a&gt; is queried by using the  $ (querySelector) and $$ (querySelectorAll) APIs provided by Puppeteer.&lt;/p&gt;

&lt;p&gt;When it comes to testing a Chrome extension, you can use the same methods to interact with the extension. The following code-block illustrates how to interact with the “iMacros for Chrome” extension using the methods mentioned above. When the extension loads, you will click on the RECORD tab and navigate to the macro recording section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Navigate to the page&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Navigate to Extension&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`chrome-extension://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionEndURL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Click on RECORD tab&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;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#record-tab-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&amp;gt;Take Screenshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chromium-extension.png&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;&lt;strong&gt;RESULT&lt;/strong&gt;&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%2F0co7g7e06f775xk0lxx1.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%2F0co7g7e06f775xk0lxx1.png" alt="IMacros for chrome extension being used to generate a screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Bonus Script
&lt;/h1&gt;

&lt;p&gt;The following script includes the things you have learned up to now and creates a script that will execute in multiple browsers like Chromium, MS Edge (Chromium), Vivaldi, and Google Chrome. It will also test the extension loading and functionality and then end the program by taking a screenshot. This script will run in all the browsers simultaneously while executing each action within the browsers in an asynchronous manner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ext_test.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;// Required global variables&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ext_install_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;ext&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;cplklnmnlbnpmjogncfgfijoopmnlemp&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;img&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionEndURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;panel.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Function to obtain extension ID&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;browserObj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Name of the extension&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iMacros for Chrome&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 extension&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targets&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;browserObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;targets&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;extensionTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;_targetInfo&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;_targetInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;extensionName&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;_targetInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background_page&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;// Extract the URL&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;extensionTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_targetInfo&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlSplit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;extensionURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlSplit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;extID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Testing Built-in Chromium&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;===&amp;gt; Testing Chromium&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;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// Chrome options&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;`--disable-extensions-except=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext_install_path&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="s2"&gt;`--load-extension=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext_install_path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getID&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`chrome-extension://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionEndURL&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#record-tab-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default-chromium.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;})();&lt;/span&gt;

&lt;span class="c1"&gt;// Testing MS Edge (Chromium)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;===&amp;gt; Testing MS Edge (Chromium)&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;browser_location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Program Files (x86)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Microsoft&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Edge Beta&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Application&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;msedge.exe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;browser_location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;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;`--disable-extensions-except=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext_install_path&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="s2"&gt;`--load-extension=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext_install_path&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="s2"&gt;`--window-size=800,600`&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;extensionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getID&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`chrome-extension://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionEndURL&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#record-tab-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msedge-extension.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;

&lt;span class="c1"&gt;// Testing Vivaldi&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;===&amp;gt; Testing Vivaldi&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;browser_location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Program Files&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Vivaldi&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Application&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;vivaldi.exe&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;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;browser_location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headless&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;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;`--disable-extensions-except=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext_install_path&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="s2"&gt;`--load-extension=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext_install_path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getID&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`chrome-extension://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionEndURL&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#record-tab-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vivaldi.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;

&lt;span class="c1"&gt;// Testing Google Chrome&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;===&amp;gt; Testing Google Chrome&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;browser_location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Program Files&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Google&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Chrome&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Application&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;chrome.exe&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;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;browser_location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headless&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;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;`--disable-extensions-except=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext_install_path&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="s2"&gt;`--load-extension=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext_install_path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getID&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`chrome-extension://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionEndURL&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#record-tab-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google-chrome.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In this article, we’ve learned about Puppeteer and how it can be used to test Chrome extensions. Puppeteer is a powerful library that can be customized according to the needs of the user. Puppeteer can also be integrated with other Javascript testing frameworks like Jest, Mocha, or Jasmine in order to create a complete test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing a test for your Chrome extension? There's an easier way.
&lt;/h2&gt;

&lt;p&gt;While the above is a good starting point for building Chrome Extension testing in-house, it's still quite a challenging process. If you're looking for an easier way to test your Chrome extension, check out &lt;a href="https://walrus.ai" rel="noopener noreferrer"&gt;walrus.ai&lt;/a&gt;!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests can be written in plain english — no difficult setup required&lt;/li&gt;
&lt;li&gt;Chrome extensions can be tested in an actual browser, not just in a new window/tab&lt;/li&gt;
&lt;li&gt;Zero maintenance — walrus.ai handles test maintenance for you, so you never experience flakes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Can I get an example?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine you wanted to test Zoom's Google calendar Chrome extension. Writing a test for that is as easy as writing 4 lines of code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ walrus -u https://zoom.us -i \
  'Click on the Zoom Chrome Extension' \
  'In the extension popup, click "Schedule a meeting"' \
  'In the browser, click Save' \
  'In the browser, verify a calendar event loads'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Want to learn more?&lt;/strong&gt; &lt;a href="https://walrus.ai" rel="noopener noreferrer"&gt;Let us know&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>devops</category>
      <category>testdev</category>
    </item>
    <item>
      <title>Why end-to-end tests are flaky, and how to fix them</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Mon, 27 Apr 2020 15:04:01 +0000</pubDate>
      <link>https://dev.to/scahhht/why-end-to-end-tests-are-flaky-and-how-to-fix-them-2mio</link>
      <guid>https://dev.to/scahhht/why-end-to-end-tests-are-flaky-and-how-to-fix-them-2mio</guid>
      <description>&lt;p&gt;End-to-end tests are an &lt;a href="https://walrus.ai/blog/2019/11/introduction-end-to-end-testing/"&gt;essential tool&lt;/a&gt; to verify that the true customer experience is working every time you make changes to your application.&lt;/p&gt;

&lt;p&gt;They're also not without their problems.&lt;/p&gt;

&lt;p&gt;End-to-end tests are notoriously difficult to maintain, for a couple of reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When making changes to your app, you need to refactor your old tests to be able to handle the new changes.&lt;/li&gt;
&lt;li&gt;Even when nothing changes about your app, sometimes your tests fail anyway, forcing you to investigate the failure. These are called &lt;strong&gt;test flakes.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What is a flaky test?
&lt;/h3&gt;

&lt;p&gt;A test that is flaky will pass sometimes and fail other times, even when nothing changes about the test case itself. In other words, flaky tests are non-deterministic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do flaky tests matter?
&lt;/h3&gt;

&lt;p&gt;Flaky tests hurt reliability, and carry large explicit and implicit costs:&lt;/p&gt;

&lt;h4&gt;
  
  
  ❌ Flaky tests hinder productivity
&lt;/h4&gt;

&lt;p&gt;Every time a test flakes, a developer needs to investigate whether the failure is true or false. If the "flake rate" of your tests is constant, every new test you add represents more time the engineering team will have to spend in the future to maintain that test.&lt;/p&gt;

&lt;p&gt;Furthermore, test failures have a cascading effect on overall productivity. In a CI/CD environment, test failures lead to failed pipelines, which lead to delayed deploys.&lt;/p&gt;

&lt;p&gt;When the whole point of CI/CD is to speed up development cycles, flaky tests can defeat the whole purpose.&lt;/p&gt;

&lt;h4&gt;
  
  
  💸 Organizational costs
&lt;/h4&gt;

&lt;p&gt;The hidden, and largest cost of test flakiness is the damage it has on the overall trust of the testing process.&lt;/p&gt;

&lt;p&gt;If engineers cannot trust the results of their tests (because they don't know if the failures are true or false), they will not want to write tests to begin with. Test flakiness is the catalyst in a negative feedback loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the tests are flaky, engineers won't trust the tests&lt;/li&gt;
&lt;li&gt;If engineers don't trust the tests, they won't write them in the first place&lt;/li&gt;
&lt;li&gt;If the codebase doesn't have tests, no one trusts the codebase&lt;/li&gt;
&lt;li&gt;If no one trusts the codebase, then they need to write tests (which starts the cycle over again)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  😡 Emotional costs
&lt;/h4&gt;

&lt;p&gt;The least obvious cost to the organization, but the most obvious to the engineer who writes tests is the emotional toll they take. No one likes to write flaky tests knowing that they'll be stuck maintaining them in perpetuity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common causes of test flakiness, and how to deal with them
&lt;/h3&gt;

&lt;p&gt;While end-to-end tests are notoriously flaky, they're at least predictably flaky. Being cognizant of these root causes goes a long way to authoring that are resilient to test flakes.&lt;/p&gt;

&lt;h4&gt;
  
  
  ⏰Asynchronous waiting
&lt;/h4&gt;

&lt;p&gt;Waiting for a fixed amount of time between steps in a test drastically increases the chances that your test will falsely fail between any given steps.&lt;/p&gt;

&lt;p&gt;To avoid "waiting" between steps for fixed periods of time, instead write your tests to poll your application continuously to ensure it is in the appropriate state before asserting on the step criteria.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡With walrus.ai&lt;/strong&gt;, no wait steps are necessary between instructions. Simply relay the user story in plain English, and all transitions are handled automatically.&lt;/p&gt;

&lt;h4&gt;
  
  
  🎬Inconsistent starting state
&lt;/h4&gt;

&lt;p&gt;If the initial conditions for your testing environment differ between runs, your tests will likely fail to even kick off because the starting assertions won't pass.&lt;/p&gt;

&lt;p&gt;To avoid this, make sure that your application is in a consistent state every time before the test starts. To ensure the environment is the same every time, you should tear down the environment after every test run, whether it's a pass or a fail. That way if a flake occurs, it won't affect future test runs as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡With walrus.ai&lt;/strong&gt;, you can specify setup or teardown instructions directly in the body of the test, so you never have to worry about an inconsistent testing environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  ↩️Dependencies
&lt;/h4&gt;

&lt;p&gt;If your tests depend on each other, or even worse, if they depend on themselves, they won't be able to run concurrently (which you will want to do if you are running tests in a CI/CD environment).&lt;/p&gt;

&lt;p&gt;Concurrency and dependency doesn't have to be explicit either. If you use the same account or environment to run two separate tests that require different initial conditions (some user permission flipped on vs. off, for example), those tests are implicitly dependent.&lt;/p&gt;

&lt;p&gt;To avoid this, consider splitting up the accounts you run tests with to be distinct. That way, you can mitigate implicit dependencies, and more easily execute setup and teardown within the test.&lt;/p&gt;

&lt;p&gt;If tests are truly dependent, then they shouldn't run concurrently. Instead, trigger a dependent test based on the pass of the upstream test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡With walrus.ai&lt;/strong&gt;, you can specify separate account credentials for different tests by &lt;a href="https://docs.walrus.ai/requests"&gt;using environment variables&lt;/a&gt;, thereby eliminating implicit dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a one-size-fits-all solution to test flakiness?
&lt;/h3&gt;

&lt;p&gt;Walrus.ai lets engineers ship flake-free end-to-end tests in minutes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;😎Easy –&lt;/strong&gt; tests can be written in plain English&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🛠No maintenance –&lt;/strong&gt; flakes and refactors are handled entirely by walrus.ai&lt;/p&gt;

&lt;p&gt;💯&lt;strong&gt;Coverage for your whole app –&lt;/strong&gt; test your hardest user experiences with ease, including APIs, third-party integrations, emails, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_QUbOEQK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xq2bn85cnhvk09xpku1n.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_QUbOEQK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xq2bn85cnhvk09xpku1n.gif" alt="end-to-end tests with walrus.ai"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Testing Database Interactions with Jest</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Fri, 24 Apr 2020 18:15:07 +0000</pubDate>
      <link>https://dev.to/walrusai/testing-database-interactions-with-jest-519n</link>
      <guid>https://dev.to/walrusai/testing-database-interactions-with-jest-519n</guid>
      <description>&lt;p&gt;Testing Database Interactions with Jest&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/facebook/jest"&gt;Jest&lt;/a&gt; has quickly become one of the most popular Javascript testing libraries. While Jest may be mostly used in the context of frontend applications, at &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; we use Jest for testing our backend Node.js services as well.&lt;/p&gt;

&lt;p&gt;Jest aims to make testing 'delightful', and a large component of that delight comes from speed. Jest by default runs concurrently with worker processes, a pattern that encourages and even requires test isolation. While this is relatively simple to accomplish for frontend code, there's a shared mutable state elephant in the room for backend code: the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why test database interactions?
&lt;/h2&gt;

&lt;p&gt;For unit tests, we generally follow best practice of mocking any interactions that are outside the unit. Consider the following function:&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;changeUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&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="nx"&gt;username&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;userRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;User&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;updated&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;userRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateUserName&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="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;updated&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;This function takes a handle to database connection, the userId, and a new username, and updates the username in the database. We abstract away the underlying SQL necessary to make the database update with the &lt;a href="https://deviq.com/repository-pattern/"&gt;Repository pattern&lt;/a&gt;. This allows us to test this function rather easily.&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;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changeUserName&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should update username in db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;getRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&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;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated&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;result&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;changeUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;However, what if we want to test our actual Repository? The code for the repository probably looks something like this:&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;class&lt;/span&gt; &lt;span class="nx"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;update&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="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
        UPDATE users SET username = :username WHERE id = :id
    `&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we wanted to test method, we could obviously mock the db connection and assert that &lt;code&gt;.sql&lt;/code&gt; is called with the expected parameters. But what if this SQL is invalid, or more probably, what if the SQL is valid but is doing the wrong thing?&lt;/p&gt;

&lt;p&gt;Presumably, at some point, we'll want to test the actual interaction with the database. We won't get into what we actually call these tests (there's probably 1000s of internet discussions on whether we've crossed the line from unit tests to integration tests by involving a real database), we'll simply cover how to do it safely and concurrently with Jest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the database for Jest
&lt;/h2&gt;

&lt;p&gt;As we've discussed, Jest by default runs tests concurrently — this makes sharing a database problematic. Tests that are running in parallel will clobber each other's database state, causing spurious failures and flakes.&lt;/p&gt;

&lt;p&gt;The simplest option to overcome this limitation is to run Jest with the &lt;code&gt;--runInBand&lt;/code&gt; option. This forces Jest to only use one process to run all your tests. However, this probably will make your test suite far slower. At &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; this took our test suite from 10s of seconds to a few minutes, and simply wasn't tenable for our CI/CD processes of constant deployments.&lt;/p&gt;

&lt;p&gt;Luckily, parallel testing with a database is a pretty solved problem. We can simply spin up a database for each worker process we're using. If all tests running against a particular database are run serially, then we don't have to worry about parallel processes mutating database state.&lt;/p&gt;

&lt;p&gt;The easiest solution for this would be something like the following. We could spin up a database before each Jest worker, and shut it down after. Since all tests within a worker run serially, each worker could operate on its individual database safely.&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;beforeWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&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;createDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`db_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JEST_WORKER_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// All tests run serially here.&lt;/span&gt;

&lt;span class="nx"&gt;afterWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;destroyDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&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;Unfortunately, while Jest exposes the &lt;code&gt;JEST_WORKER_ID&lt;/code&gt; environment variable to distinguish between workers, &lt;a href="https://github.com/facebook/jest/issues/8708"&gt;it doesn't expose any simple way of hooking in per-worker setup and teardown methods&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This means that we can't dynamically spin up and tear down databases for each worker. We can, however, do the next best thing, using a static number of Jest workers.&lt;/p&gt;

&lt;p&gt;First, we'll need a test setup script that prepares our multiple databases.&lt;/p&gt;

&lt;p&gt;Note: The following code examples use &lt;a href="https://github.com/typeorm/typeorm"&gt;Typeorm&lt;/a&gt;, but the code could easily be extended for any other database interaction library such as Sequelize, Massive.js, Knex etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connection&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;createConnection&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="s1"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_MASTER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,
    host: process.env.DATABASE_HOST,
    port: 5432,
  });
  const databaseName = `walrus_test_template`;
  const workers = parseInt(process.env.JEST_WORKERS || &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;);

  await connection.query(`DROP DATABASE IF EXISTS ${databaseName}`);
  await connection.query(`CREATE DATABASE ${databaseName}`);

  const templateDBConnection = await createConnection({
    name: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;templateConnection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,
    type: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,
    username: process.env.DATABASE_USER,
    password: process.env.DATABASE_PASSWORD,
    database: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;walrus_test_template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,
    host: process.env.DATABASE_HOST,
    migrations: [&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;migrations&lt;/span&gt;&lt;span class="cm"&gt;/*.ts'],
    port: 5432,
  });

  await templateDBConnection.runMigrations();
  await templateDBConnection.close();

  for (let i = 1; i &amp;lt;= workers; i++) {
    const workerDatabaseName = `walrus_test_${i}`;

    await connection.query(`DROP DATABASE IF EXISTS ${workerDatabaseName};`);
    await connection.query(`CREATE DATABASE ${workerDatabaseName} TEMPLATE ${databaseName};`);
  }

  await connection.close();
})();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With this script, we create a connection to our database, Postgres in this instance. Then, for each worker that we are using (set statically in the &lt;code&gt;JEST_WORKERS&lt;/code&gt; environment variable, we initialize a new database.&lt;/p&gt;

&lt;p&gt;Because we're using Postgres, we can use a handy feature called Template Databases. This makes new database creation cheap, and allows us to only run our migrations once. At a high level, we create one template database, run our migrations once against the template database, and then quickly copy over the database for each Jest worker.&lt;/p&gt;

&lt;p&gt;Now, we simply have to connect to the correct database in all of our tests. With the &lt;code&gt;JEST_WORKER_ID&lt;/code&gt; environment variable, this is trivial:&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;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;connection&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;createConnection&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="s1"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_HOST&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="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`walrus_test_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JEST_WORKER_ID&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="na"&gt;logging&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="nx"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;namingStrategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SnakeNamingStrategy&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;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;connection&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;Now all of our workers will use individual databases, allowing us to run tests in parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up between tests
&lt;/h2&gt;

&lt;p&gt;While parallel tests are no longer problematic, we still have a problem with serial tests. Consider the following example, again of our contrived &lt;code&gt;UserRepository&lt;/code&gt;:&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;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserRepository&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should create user&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countUsers&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
  &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should delete user&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&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;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&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;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteUser&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="nx"&gt;expect&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;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countUsers&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Notice anything wrong here? The second test will fail. While we're setting up different databases for each of our parallel workers, tests within the same file are run serially. This means the user created in the first test is still present in the second test, causing the test to fail.&lt;/p&gt;

&lt;p&gt;We considered two approaches to solve this problem. The first approach is to wrap each test in a database transaction:&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;beforeEach&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;afterEach&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rollbackTransaction&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;With this approach, any database updates made within the test are wrapped into the initialized transaction. When the test is finished, we simply rollback the transaction, discarding any of those updates. While this approach is fast and generally supported by all databases, it's not always the best for certain classes of integration-tests.&lt;/p&gt;

&lt;p&gt;Sometimes, the behavior under test may actually be the transaction itself. For example, we may want to test that when an update fails, certain components of the update are preserved (committed), and others are rolled back. This logic would require us to manually start and stop transactions within our code, and wrapping the code in a parent transaction with this method would keep us from effectively testing rollbacks.&lt;/p&gt;

&lt;p&gt;Another, simpler but slower approach, is to just clear out the database after before every test. While this may be slower, it's less likely to bite us later. We can do this in a simple beforeEach block.&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;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;createQueryRunner&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;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
      DO
      $func$
      BEGIN
        EXECUTE (
          SELECT 'TRUNCATE TABLE ' || string_agg(oid::regclass::text, ', ') || ' CASCADE'
            FROM pg_class
            WHERE relkind = 'r'
            AND relnamespace = 'public'::regnamespace
        );
      END
      $func$;
    `&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;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The above code iterates through all our tables and clears them using the SQL &lt;code&gt;TRUNCATE&lt;/code&gt; command. In the &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; test suite, this occurs in the order of milliseconds, and is a worthwhile performance tradeoff for keeping our tests simple.&lt;/p&gt;

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

&lt;p&gt;By clearing the database between tests, and using one test per worker, we can continue getting the delightful Jest experience for testing database connected backend applications.&lt;/p&gt;

&lt;p&gt;While testing database interactions with Jest helps increase unit and integration test coverage without sacrificing test stability, running true end-to-end tests with browser automation tools like Selenium or Cypress can still be flaky and unstable. &lt;/p&gt;

&lt;p&gt;Jest's goal is to make unit and integration testing 'delightful' — our goal at &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; is to do the same for end-to-end testing. Engineering teams can write tests in plain english, and we take care of automating the tests, resolving flakes, and maintaining tests as their applications change. Our contrived example above showed how to test the database end of a username update, here's how simple the corresponding end-to-end test with walrus.ai could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;walrus &lt;span class="nt"&gt;-u&lt;/span&gt; your-application.com &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'Login'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'Change your username'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'Verify you receive a confirmation email at the new email address'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'Verify your username is changed'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</description>
      <category>database</category>
      <category>devops</category>
      <category>javascript</category>
      <category>jest</category>
    </item>
    <item>
      <title>Why engineers suck at writing integration tests</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Tue, 21 Apr 2020 18:57:58 +0000</pubDate>
      <link>https://dev.to/walrusai/why-engineers-suck-at-writing-integration-tests-4d7k</link>
      <guid>https://dev.to/walrusai/why-engineers-suck-at-writing-integration-tests-4d7k</guid>
      <description>&lt;p&gt;In a perfect world, we can all agree that having automated tests are a good thing. They improve reliability, prevent bugs from making it to production, and reduce the load on manual QA, in addition to a whole host of other &lt;a href="https://walrus.ai/blog/2019/11/unexpected-benefits-writing-tests/" rel="noopener noreferrer"&gt;less obvious, but crucial benefits&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;But we don't live in a perfect world. &lt;/p&gt;

&lt;p&gt;The real downside to automated tests is writing them in the first place. While important for any CI/CD development environment, writing and maintaining tests is the least favorite part of a developer's job. As we all know, if we don't like to do something, we probably won't do it well. &lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  In short, engineers suck at writing integration tests because writing integration tests sucks
&lt;/h4&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What's so bad about writing integration tests?&lt;/strong&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  ✏  Writing integration tests is too hard
&lt;/h3&gt;

&lt;p&gt;Writing a test is fundamentally expressing a user story. You want to make sure that your customer can take a set of actions in a particular order, and your application responds the way you would expect based on those actions. For example, if you're Amazon, you want to make sure that your customer can add an item to the cart, and the item actually loads in the cart when it's opened. &lt;/p&gt;

&lt;p&gt;While easy to express in English, writing a test for that story in something like Selenium requires making complicated assertions on every step of the story (make sure a button loads containing some text, make sure it's clickable, make sure a page loads, that an image of the item loads, etc.). &lt;/p&gt;

&lt;p&gt;Furthermore, whatever framework you use to test is probably not the framework you use to do most of your job, which means every time you go to write tests, you have to brush off the test-writing skills before you can really get productive. &lt;/p&gt;

&lt;h3&gt;
  
  
  🛠  Maintaining them is too costly
&lt;/h3&gt;

&lt;p&gt;Test maintenance comes in many forms: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost of updating tests every time you make changes to your application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Writing tests is important when you're making changes to your application. At the same time, when you make changes to your application, you likely need to update your tests if any of the changes conflict with the assertions you're making inside of those tests. This circularity means that every time you're making big changes to your site, not only do you need to write new tests for that functionality, you probably also need to update all of the tests you've &lt;strong&gt;&lt;em&gt;already&lt;/em&gt;&lt;/strong&gt; written so they don't break. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost of investigating and fixing flaky tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A flaky test fails sometimes and passes other times, despite no changes to the user story it is testing. Every time one of these flakes occurs, you need to investigate whether it is a true failure of the user story, or a false failure of the user story. The more tests you add, the more investigation you need to do when your test suite flakes. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost of held-up deploys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When tests fail, even falsely in the event of flakes, CI/CD pipelines will fail, which means your important changes won't make it to production. The whole purpose of CI/CD is to speed up the process of making continuous improvements, but flaky or brittle tests defeat that purpose entirely. &lt;/p&gt;

&lt;p&gt;Every test that you write doesn't only carry the cost of authoring it; it also carries the cost of all future maintenance you'll be responsible for related to that test. As such, developers tend to view tests as a burden, and who wants to voluntarily take on a burden? &lt;/p&gt;

&lt;h3&gt;
  
  
  ❓ Results aren't trustworthy
&lt;/h3&gt;

&lt;p&gt;The whole point of writing tests is to verify that any changes you make to your application don't break the user experience. If you have to investigate every failure due to the flaky nature of most testing frameworks, it defeats the whole purpose of testing (to improve reliability, prevent bugs, and move faster). This untrustworthiness makes writing tests feel pointless, and nobody wants to do pointless work. &lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Full coverage is difficult to achieve
&lt;/h3&gt;

&lt;p&gt;Browser testing technologies have not kept up with the increasing complexity of web applications. Tools like Selenium or Cypress don't have easy ways to handle user experiences like third-party integrations, file uploads or downloads, interacting with maps, or sending/receiving emails. &lt;/p&gt;

&lt;p&gt;If those types of flows are important to your product, then writing tests for the functionality around them will feel pointless, because it only represents a partial solution, and won't be testing the true user experience. &lt;/p&gt;

&lt;p&gt;Nobody wants to build a partial solution. When faced with a partial solution, it's easier to build nothing at all. &lt;/p&gt;

&lt;h2&gt;
  
  
  What needs to change?
&lt;/h2&gt;

&lt;p&gt;Getting engineers to write better tests means solving the problems that make them bad at writing tests in the first place. So to get engineers to write better tests: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We need to make tests easier to write&lt;/li&gt;
&lt;li&gt;We need to make tests easier to maintain by eliminating flakiness&lt;/li&gt;
&lt;li&gt;We need to eliminate false negatives and false positives from our test results&lt;/li&gt;
&lt;li&gt;We need to make it possible to test the full functionality of our applications&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Luckily, &lt;a href="//walrus.ai"&gt;walrus.ai&lt;/a&gt; is here to help!
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;End-to-end tests can be written in plain English in minutes, without handling any mapping on your end&lt;/li&gt;
&lt;li&gt;Tests are maintained entirely by walrus.ai, so you don't need to update tests when you change your application&lt;/li&gt;
&lt;li&gt;Results are verified by walrus.ai, so you only get true failures or true passes&lt;/li&gt;
&lt;li&gt;Walrus.ai can test the most complicated flows, including third-party integrations, file upload or download, 2fa, or email-based flows. &lt;/li&gt;
&lt;/ol&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%2Fpijoeofji50zrv12zjby.gif" 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%2Fpijoeofji50zrv12zjby.gif" alt="integration test written in plain English"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Tips for working remotely as a developer? </title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Thu, 12 Mar 2020 22:48:22 +0000</pubDate>
      <link>https://dev.to/scahhht/tips-for-working-remotely-as-a-developer-op5</link>
      <guid>https://dev.to/scahhht/tips-for-working-remotely-as-a-developer-op5</guid>
      <description>&lt;p&gt;In light of recent events, lots of folks are probably working remotely who typically wouldn't. &lt;/p&gt;

&lt;p&gt;I'd love to hear from this community what works help you be productive at home, and also what helps you stay sane when you're in the house all day! &lt;/p&gt;

</description>
      <category>discuss</category>
      <category>mentalhealth</category>
    </item>
    <item>
      <title>Three Common Pitfalls of CI/CD, and How to Avoid Them</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Fri, 22 Nov 2019 22:52:30 +0000</pubDate>
      <link>https://dev.to/walrusai/three-common-pitfalls-of-ci-cd-and-how-to-avoid-them-2c9l</link>
      <guid>https://dev.to/walrusai/three-common-pitfalls-of-ci-cd-and-how-to-avoid-them-2c9l</guid>
      <description>&lt;p&gt;CI/CD is ultimately about getting your ideas to market faster by continuously shipping code in smaller, more testable increments. However, to successfully adopt this process, it requires the coordinated buy-in of the entire engineering and testing teams, the adoption of tools to facilitate the process, and major process changes. &lt;/p&gt;

&lt;p&gt;Moving to CI/CD is risky if you don't take the time to think through how to handle the transition. &lt;/p&gt;

&lt;p&gt;So what are the common mistake teams make when switching to CI/CD?&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it too easy to bypass pipelines
&lt;/h2&gt;

&lt;p&gt;CI/CD is kind of like working out — it requires a specific set of habits, and a lot of discipline to stick to them. If you skip too many days at the gym, you fall out of the habit and stop going entirely. &lt;/p&gt;

&lt;p&gt;In a similar vein, if you give your team a way to bypass part of your CI/CD pipeline, the whole process won't stick. When your (hopefully automated) testing catches bugs, it'll be too tempting to bypass your checks, convincing yourself you'll fix the issues later. &lt;/p&gt;

&lt;p&gt;Only in the most extraordinary circumstances should you give anyone a way to bypass pipelines. &lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the wrong metrics to track, or not tracking metrics at all
&lt;/h2&gt;

&lt;p&gt;Ideally, CI/CD should help you get your ideas to market faster, reduce the risk of any given deployment, ship fewer bugs to production, and ultimately make your team happier because they're shipping more code. &lt;/p&gt;

&lt;p&gt;If those are your goals, the metrics you track should align with those objectives. Aggregate lines of code doesn't tell you anything about bugs, the customer experience, or velocity. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your&lt;/strong&gt; &lt;strong&gt;metrics should measure velocity and highlight chokepoints in your processes&lt;/strong&gt;&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe8bwyg69obdhnlpldufc.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe8bwyg69obdhnlpldufc.png" alt="pipelines of continuous delivery"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Velocity requires every step of the continuous delivery process to happen quickly, with minimal transition friction. Track how long the transition takes between each step in the process, and the main causes for slow transitions. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How long does it take for your team to discover and review pull requests&lt;/li&gt;
&lt;li&gt;Is your unit and integration test coverage high?&lt;/li&gt;
&lt;li&gt;How often are your tests failing?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Your metrics should track the customer experience&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CI/CD requires moving quickly and confidently. Automated tests help in terms of providing a layer of protection against a broken customer experience, but rarely can they catch everything. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identify key business metrics that reflect the true state of the customer experience.&lt;/li&gt;
&lt;li&gt;Make dashboards of key metrics publicly viewable, so if a deploy goes out that breaks the customer experience, you can catch it quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building CD on top of unstable CI
&lt;/h2&gt;

&lt;p&gt;Automated tests are essential to any successful CI/CD process. To continuously deploy, requiring human intervention to test for bugs is untenable. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lack of test coverage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Moving to CI/CD without test coverage defeats the entire purpose of the process. It doesn't matter how often you push to production — if your product has bugs, you are delivering a poor customer experience. &lt;/p&gt;

&lt;p&gt;Tests help you spot bugs before your customers do, and culturally prevent you from shipping buggy code. &lt;/p&gt;

&lt;p&gt;By no means should you always have 100% test coverage, but you should always ensure that the most important flows for the customer experience, and any experiences governed by business logic are thoroughly tested. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flaky tests based on poor testing frameworks or standards&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you write tests that need to be changed any time you refactor code, you may end up worse-off than if you never wrote them in the first place. Tests should be abstract enough to track user stories (how will these user progress through the onboarding experience), and not so specific that something as small as change in button text would cause a failure. &lt;/p&gt;

&lt;p&gt;Furthermore, if you do not have a strong process for training engineers to implement tests, the process of writing them will be untenably slow, and defeat the purpose of CI/CD. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lack of alignment between development, testing, and other stakeholders in the customer experience&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;In a strong culture of CI/CD, the engineers who write the tests, the testers who bug-bash flows, and the product teams who manage the customer experience should all have a say in which tests are written, and their ongoing maintenance. &lt;/p&gt;

&lt;p&gt;Up front, product managers should enumerate the user stories for testing, engineers should implement tests per those stories, and testers should ensure the customer experience behaves as expected, as well as monitor production metrics to ensure no business logic fundamentally fails. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to build a better culture of testing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;An effective implementation of CI/CD requires organizational buy-in, access to right tools, and continuous measurement. &lt;/p&gt;

&lt;p&gt;A strong culture should care about implementing tests, and monitoring that the customer experience behaves as expected after every deploy. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests should be easy to write, and flexible enough to not break when code is refactored&lt;/li&gt;
&lt;li&gt;Tests should be flexible enough to not break when code is refactored&lt;/li&gt;
&lt;li&gt;Product teams should be invested in the testing process — enumerating user stories that are important to validate during CI pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And your tools should make all of that easy. At &lt;a href="http://walrus.ai" rel="noopener noreferrer"&gt;walrus.ai&lt;/a&gt;, we're trying to do just that.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxjd6hzuy8owayh35ijjo.gif" 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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxjd6hzuy8owayh35ijjo.gif" alt="walrus website"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Five Ways to Shorten your Continuous Delivery Cycle</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Fri, 08 Nov 2019 15:25:50 +0000</pubDate>
      <link>https://dev.to/walrusai/five-ways-to-shorten-your-continuous-delivery-cycle-e27</link>
      <guid>https://dev.to/walrusai/five-ways-to-shorten-your-continuous-delivery-cycle-e27</guid>
      <description>&lt;p&gt;The age of multi-month release cycles is coming to a close, and the movement of Continuous Integration (CI) and Continuous Delivery (CD) is taking its place. &lt;/p&gt;

&lt;p&gt;CI/CD is ultimately about adopting a set of practices and tools to accelerate the deployment of software, from the moment its written, to the time it is live on a site. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The benefits of CI/CD are numerous:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can get to market with your initial idea or new feature faster&lt;/li&gt;
&lt;li&gt;The risk of any given deployment is smaller&lt;/li&gt;
&lt;li&gt;Because your team will be able to reliably ship code without manual intervention, they will feel more productive, professionally fulfilled, and ultimately happier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The touch-points in the CI/CD release cycle are numerous, and should be optimized individually to cut down cycle time as much as possible, so your team can deliver software faster, more reliably, and with fewer headaches.&lt;/p&gt;

&lt;h1&gt;
  
  
  Understanding continuous deployment
&lt;/h1&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fneyffv35bgoqswi1wk72.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fneyffv35bgoqswi1wk72.png" alt="Flowchart of the whole ci cd lifecycle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At its core, CI/CD is about increasing the speed in which software can be delivered to customers by automating the deployment of code while maintaining confidence in its stability. &lt;/p&gt;

&lt;p&gt;Continuous Integration (CI) is the practice of continually integrating code into one shared repository, and ensuring that the code delivers the desired customer experience. Continuous Delivery (CD) governs how that continually integrated code is actually deployed in an automated fashion. &lt;/p&gt;

&lt;p&gt;There are four essential ingredients for any successful CI/CD process: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code is checked in as frequently as possible&lt;/li&gt;
&lt;li&gt;Test coverage stays high&lt;/li&gt;
&lt;li&gt;Tests are fully automated &lt;/li&gt;
&lt;li&gt;Deployment happens without the need for human intervention&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Maintaining a culture of CI/CD requires significant investment in process, an organizational commitment to velocity, and tools to facilitate each step in the code review review, CI, and CD stages of delivery. &lt;/p&gt;

&lt;h1&gt;
  
  
  The five ways to speed up your continuous deployment cycles
&lt;/h1&gt;

&lt;p&gt;To increase the velocity at which you launch software, you should optimize every stage of the delivery cycle, from code review, to building and testing your code, to actually deploying the changeset. &lt;/p&gt;

&lt;p&gt;Here are our five tips to shorten your cycles, starting with how you write code, and ending with how you deploy it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Identify the minimum viable changeset
&lt;/h2&gt;

&lt;p&gt;Keeping pull requests small both increases the speed at which they are reviewed, and reduces the risk associated with downstream failures. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your pull requests are massive, no one will want to review them. As such, writing smaller PRs decrease the "time-to-first-review" for each PR.&lt;/li&gt;
&lt;li&gt;Smaller PRs are easier to review, and so the "time-stuck-in-review" goes down the smaller the changeset.&lt;/li&gt;
&lt;li&gt;Large PRs that touch many parts of the codebase carry a larger risk of subsequent merge conflicts, as well as potential test failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reduce time-to-discovery of pull requests
&lt;/h2&gt;

&lt;p&gt;Code sitting in limbo carries a great cost. It slows down velocity, and discourages the writer of the code. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To ensure PRs are reviewed promptly, adopt tools to surface accountability to the team. &lt;a href="https://pullreminders.com/" rel="noopener noreferrer"&gt;Pull reminders&lt;/a&gt; is a great way to surface PRs that need review, and notify team members in a public channel.&lt;/li&gt;
&lt;li&gt;Eliminate the bystander effect by adding fewer reviewers to each PR, so each reviewer feels more accountability for that review.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Automate your testing
&lt;/h2&gt;

&lt;p&gt;Test automation is an essential ingredient to a well-running CI/CD process. Dependencies on manual QA or any sort of human intervention hold up deploys and defeat the purpose of continuous delivery. &lt;/p&gt;

&lt;p&gt;However, if not implemented correctly, tests can hold up your deploys just as much as humans. Flakey tests caused by frequent changes to the site can lead to failures. &lt;/p&gt;

&lt;p&gt;Ultimately, to maintain continuous deployment, you need high coverage, with flexible automated tests. You can't just have one. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want better test coverage with one line of code?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Try &lt;a href="http://walrus.ai" rel="noopener noreferrer"&gt;walrus.ai&lt;/a&gt; – integration tests in one line of code, in plain English. We handle the rest. &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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgu38dtozxl6k5zxmw62x.gif" 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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgu38dtozxl6k5zxmw62x.gif" alt="Console running an integration test"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adopt a feature flag system
&lt;/h2&gt;

&lt;p&gt;Feature flags are a great mechanism to test changes in production without rolling them out to all of your users at once.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create dynamic flags that deliver the new experience to people in your specific domain.&lt;/li&gt;
&lt;li&gt;Ramp up exposure of your updated experience over time in order to reduce the risk of your changeset even further.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Focus on production monitoring
&lt;/h2&gt;

&lt;p&gt;CI/CD requires moving quickly and confidently. Automated tests help in terms of providing a layer of protection against a broken customer experience, but rarely can they catch everything. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identify key business metrics that reflect the true state of the customer experience.&lt;/li&gt;
&lt;li&gt;Make dashboards of key metrics publicly viewable, so if a deploy goes out that breaks the customer experience, you can catch it quickly.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>How to Work with Designers as an Engineer</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Tue, 05 Nov 2019 17:33:37 +0000</pubDate>
      <link>https://dev.to/scahhht/how-to-work-with-designers-as-an-engineer-22bi</link>
      <guid>https://dev.to/scahhht/how-to-work-with-designers-as-an-engineer-22bi</guid>
      <description>&lt;p&gt;A product's design isn't just how it looks. It's how it works, and how it makes you feel. &lt;/p&gt;

&lt;p&gt;As the point of discovery for new products transitions from real-world sales to web and mobile interfaces, design is increasingly a critical differentiator for companies trying to acquire and retain customers. &lt;/p&gt;

&lt;p&gt;At the same time, advancements in frontend architecture, and the movement to more frequent site updates through continuous deployment, means that design and engineering teams have to work more closely than ever before. &lt;/p&gt;

&lt;p&gt;The relationship between design and engineering rests on a few core principles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineers need to empathize with designers&lt;/strong&gt; — what they care about, what they look for in a design, what their ideal process is. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design needs to understand engineering constraints&lt;/strong&gt; — what can be built quickly, what's possible given team staffing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both need to develop an intuition for the user&lt;/strong&gt; — at the end of the day, both want to deliver something that solves an important customer need.&lt;/p&gt;

&lt;h1&gt;
  
  
  Developing design intuition
&lt;/h1&gt;

&lt;p&gt;As with most collaborative relationships, the key to developing a strong partnership relies on understanding your colleague's core objectives and their process for accomplishing those. &lt;/p&gt;

&lt;p&gt;At first, understanding a design is not as intuitive as reading a product spec or reviewing code, because the intention behind the design is not explicitly spelled out. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Seek to understand intention&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Simply put, design is the process of identifying and solving problems. When reviewing designs, ask the designer &lt;strong&gt;why&lt;/strong&gt; they made the choices they did. What problem are they trying to solve? What are the principles that govern this design? Why is this solution ideal? &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Ask about process&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The fidelity of a design is a function of where it currently lives in the design process. Some designers are comfortable showing designs before they've been critiqued. Some are comfortable sharing only after they've been tested with users. When you receive designs, ask where they are in the designer's process. Are they ready for implementation? Waiting on user feedback? &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Understand design systems&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Most companies have established design patterns that are used to make iterating on new work faster. Like an alphabet for design at the company. When you understand both the intention behind a given design, as well as the patterns or tools that are already available in bringing that intention to fruition, you're in a better position to critique a design. Is this custom element worth building when we already have a component that sufficiently solves the problem we are trying to address, and doesn't fly in the face of the intention behind the overall design? &lt;/p&gt;

&lt;h1&gt;
  
  
  Providing engineering context
&lt;/h1&gt;

&lt;p&gt;There are many avenues to solve a particular problem. There are fewer avenues to solve that problem quickly. If you want to deliver software that solves a customer problem &lt;strong&gt;&lt;em&gt;quickly,&lt;/em&gt;&lt;/strong&gt; it's critical to identify the lowest-scope solution that is still viable. To identify the "minimum" in "minimum viable product", you need to understand what constraints you're operating under. &lt;/p&gt;

&lt;h3&gt;
  
  
  Communicate constraints early
&lt;/h3&gt;

&lt;p&gt;In the early stages of a design, the designer is typically thinking more about the problem they are trying to solve, the customer profile of the person who suffers that problem, and what the ideal solution looks like. &lt;/p&gt;

&lt;p&gt;As an engineer, providing context about the constraints of the system are helpful in shaping the studio-space for the designer, and preventing him/her from exploring paths that would ultimately be infeasible. &lt;/p&gt;

&lt;p&gt;Give your designer guard-rails — what data is available in this part of the user experience, what kinds of elements take a lot longer to build than others, how much more time building a custom component would take than using an existing one from the design system. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Make yourself available&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Go to design reviews. Make yourself a resource during the exploration phase of a designer's process. Sit with your designer. Teach your designer &lt;strong&gt;why&lt;/strong&gt; certain approaches are more feasible than others. Help your designer develop her/his own intuition about the constraints of your system.&lt;/p&gt;

&lt;h1&gt;
  
  
  Developing empathy for the user
&lt;/h1&gt;

&lt;p&gt;Ultimately, the goals of the engineer and the designer are the same — to solve a problem for the customer. The customer and his/her problem have to be the north star, both for the design and the implementation. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Find a common vocabulary&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When discussing a design or the final implementation of that design, it helps to have a well-understood vocabulary with which to provide feedback. Write down the problem you're trying to solve, and use it as a reference when providing feedback. Come up with the principles that govern the solution, and validate that what you're implementing fulfills those principles. &lt;/p&gt;

&lt;p&gt;Many disagreements stem from simple miscommunication. Coming up with a common vocabulary with which to discuss designs ensures that disagreements are about what is actually important — the customer experience, and not the words we use to describe it. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Fill in the gaps&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Even the most thorough designs rarely cover all of the possible edge-cases that one could experience in the real customer experience. As the engineer implementing the design, you'll probably encounter these edge-cases before anyone else does. &lt;/p&gt;

&lt;p&gt;Understanding the problem you're trying to solve, and the intention behind the overall design  will enable you to make important judgement calls when fixing these edge-cases. You can always work out the kinks later, but coming up with a strong first-pass based on the principles governing the overall solution will always be much faster than going back to design for further iteration on edge-cases. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Meet your customers&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;At the end of the day, there's no better way to understand how your customer users your product than to actually watch them. Developing an intuition for your customer's pain points, what they care about, and how they actually use your product will sharpen the judgment you use to make any change to the customer experience.&lt;/p&gt;

&lt;h1&gt;
  
  
  Are you an engineer who cares about the user?
&lt;/h1&gt;

&lt;p&gt;The first step to writing any piece of software should be to identify the &lt;strong&gt;user story&lt;/strong&gt; for what you're building — what problem is it solving, and exactly how will the user interact with it? &lt;/p&gt;

&lt;p&gt;Then you have to write tests to ensure your user can actually complete the story you want them to. Wouldn't it be great if you could just write the user story, and the tests would write themselves? &lt;/p&gt;

&lt;p&gt;With &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt;, you can write integration tests in one line of code, in plain english.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LFcz8Uml--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/9gtknx9ulajc9s7detfx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LFcz8Uml--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/9gtknx9ulajc9s7detfx.gif" alt="Integration test being run in a console"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>design</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Four Unexpected Benefits of Writing Tests</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Mon, 04 Nov 2019 17:07:36 +0000</pubDate>
      <link>https://dev.to/scahhht/four-unexpected-benefits-of-writing-tests-2ofm</link>
      <guid>https://dev.to/scahhht/four-unexpected-benefits-of-writing-tests-2ofm</guid>
      <description>&lt;p&gt;No one likes writing tests. &lt;/p&gt;

&lt;p&gt;If you're setting them up for the first time, it's a pain (getting your servers going, training your team on a framework, etc.). Even if you're used to writing them, it can feel like overkill when you're just trying to develop new features that customers will get value from. &lt;/p&gt;

&lt;p&gt;Despite being a pain to write, integration tests are a great example of delayed gratification — there's an up-front cost, but they pay for themselves in the long run. &lt;/p&gt;

&lt;p&gt;In fact, a test's role in preventing a bug makes it some of the highest-value code an engineer can write. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tests save time — and not just for the developer&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LG-FWcWA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/y0mds8geb6czn9dwuhf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LG-FWcWA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/y0mds8geb6czn9dwuhf7.png" alt="image showing the lifecycle of a bug"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bugs do not exist in a vacuum. &lt;/p&gt;

&lt;p&gt;Most bugs are discovered by the users themselves, where they then make the long-and-windy journey back to the developer with the right context. Along the way, a bug might be handled by a support person, who then passes it along to their boss, who might get in touch with a product manager, who will know an engineer who is familiar with that part of the codebase.&lt;/p&gt;

&lt;p&gt;At each step in this journey, the bug gets more expensive as time is invested from each stakeholder in its investigation and resolution. &lt;/p&gt;

&lt;p&gt;All the while, the customer is waiting for a resolution, every minute more sure that they want to cancel. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration tests eliminate that whole journey.&lt;/strong&gt; The moment a bug is deployed related to someone's code, that person gets notified. No more telephone, no more lengthy resolution process. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tests improve decision-making&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Many companies use A/B testing to measure the impact of new features, or user-facing changes of any kind to their site. &lt;/p&gt;

&lt;p&gt;When you introduce more variants (test groups) to your site, you increase the probability of bugs, as you are supporting multiple different versions of the site which in turn increases the overall surface area you need to support. &lt;/p&gt;

&lt;p&gt;Bugs in any of these variants can have the effect of skewing the results of your experiment. Furthermore, without test coverage, you may not even be aware that these bugs exist and are influencing the outcome of your experiment.&lt;/p&gt;

&lt;p&gt;As such, implementing tests in your experimental code ensures that you don't make important decisions based on flawed data. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tests help your brand&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Social media, customer reviews, and word-of mouth are increasingly the primary ways that millennials make purchasing decisions. &lt;/p&gt;

&lt;p&gt;Nothing kills a potential customer's intent to purchase like seeing an angry tweet about a bad bug. &lt;/p&gt;

&lt;p&gt;A bug-free experience is a prerequisite for customer acquisition and retention, and integration tests are the best lever you have to ensure that your customer experience is bug-free.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tests print money&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Tests have high ROI on two dimensions: they prevent money-losing bugs, and they help developers spend more of their time building features, not fixing bugs. &lt;/p&gt;

&lt;p&gt;For any critical business process (such as customer checkout, coupon code redemption, etc.), tests can make all the difference. Without tests, you increase the probability that a fraudulent actor can exploit your coupon system, or a legitimate customer can't make it through checkout. &lt;/p&gt;

&lt;p&gt;Furthermore, while tests have an up-front cost (you have to write them), they save time in the long-run, particularly when you take into account the many stakeholders that are involved in solving bugs. &lt;/p&gt;

&lt;h1&gt;
  
  
  But writing tests is hard...right?
&lt;/h1&gt;

&lt;p&gt;Historically, writing tests has been a pain. You have to get servers running, adopt a framework, and write them for every new feature. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Writing tests does not have to be hard&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt;, you can write integration tests in one line of code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚡️ Fast:&lt;/strong&gt; Get results in 5 minutes&lt;/li&gt;
&lt;li&gt;🏝 &lt;strong&gt;Easy:&lt;/strong&gt; Write tests in plain English, and we'll handle the rest&lt;/li&gt;
&lt;li&gt;👩🏻‍💻 &lt;strong&gt;Developer-first:&lt;/strong&gt; One API call, no sales calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="http://walrus.ai"&gt;Try if free!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jGfjjab8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/f5q4sc7umczdye9chgjh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jGfjjab8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/f5q4sc7umczdye9chgjh.gif" alt="An integration test being run in a console"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Work Remotely Without Going Insane</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Fri, 01 Nov 2019 17:28:15 +0000</pubDate>
      <link>https://dev.to/walrusai/how-to-work-remotely-without-going-insane-28l6</link>
      <guid>https://dev.to/walrusai/how-to-work-remotely-without-going-insane-28l6</guid>
      <description>&lt;p&gt;The future of work is remote. &lt;/p&gt;

&lt;p&gt;In recent years, fully-remote companies like GitLab, Zapier, and InVision have proven that a remote culture is not just a perk, but a clear business advantage. Technology is accelerating this transition. We can chat instantaneously in Slack, coordinate a global videoconference through Zoom, and track our entire roadmap in Asana. &lt;/p&gt;

&lt;p&gt;For employees, remote work provides more flexibility (hours, family time, no need to move when switching jobs), fewer distractions (no sales calls happening next to your desk), and shorter commutes (or no commute). For companies, a remote culture makes it easier to hire, reduces office costs, and naturally attracts self-motivated workers. &lt;/p&gt;

&lt;p&gt;Despite its advantages, remote work can have drawbacks. Between loneliness and isolation, organizational ambiguity, unclear work/life boundaries, and complex coordination, remote work offers several challenges. &lt;/p&gt;

&lt;p&gt;None of these challenges are insurmountable. At &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt;, we've thought a lot about how we can improve our remote-first culture. By introspecting on the disadvantages of remote work, we can structure our days thoughtfully to mitigate or eliminate these disadvantages at the root-cause. &lt;/p&gt;

&lt;h1&gt;
  
  
  Remote work is not without challenges
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Working remotely can be lonely&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Humans are social animals. &lt;a href="https://psycnet.apa.org/record/2009-12071-015"&gt;Research suggests&lt;/a&gt; that when unplanned, spontaneous social interactions prevent loneliness. When you work remotely, you miss such interactions that are endemic to the office — chatting at the desks, passing people in the hallways, sitting down for lunch. If you're not careful, it's easy to feel isolated, particularly if you're transitioning into a remote job from a bustling office. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;It's&lt;/strong&gt; &lt;strong&gt;easy to feel out of the loop&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If documentation at your particular company is poor, it's easy to feel lost as a remote worker. If your goals are ambiguous, remote policies ill-defined, and spontaneous meetings occurring behind closed doors, it's easy to feel like you're left out as a remote employee. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Coordination is challenging&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When a team works across multiple time-zones, coordinating real-time meetings is challenging. A bright-and-early meeting in San Francisco might be squarely when you want to tuck in your child in Amsterdam. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Work/Life boundaries can become blurred&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If you live, work, and sleep in the same physical space, the lines between these your work time and your personal time can easily fade. Furthermore, the flexibility of remote work can cause your work hours to be different than your social community, increasing feelings of isolation. &lt;/p&gt;

&lt;h1&gt;
  
  
  How to structure your days to live your best remote life
&lt;/h1&gt;

&lt;h3&gt;
  
  
  ✅  Define Done
&lt;/h3&gt;

&lt;p&gt;When you are in a traditional office, it's clear that the day is ending when people start to filter out. When you work remotely, you don't have such obvious cues, and it's easy to lose track of when you should stop working. &lt;/p&gt;

&lt;p&gt;To counteract this, give yourself a target. When you start working for the day, choose a rough timeframe when you want to finish your work for that day. If something about your day changes significantly that causes you to step away from working (exercise, last-minute appointment, etc.), re-evaluate and adjust your target end-time, and try to stick to that time. &lt;/p&gt;

&lt;h3&gt;
  
  
  ▶️  Identify triggers
&lt;/h3&gt;

&lt;p&gt;When you sit down at your desk in an office, your body knows it's time to work. When you wake up in the same place you'll work for the day, there's no clear start to the work day. &lt;/p&gt;

&lt;p&gt;Teach your brain to start working by associating certain actions (or "triggers") with the commencement of work. For example, walk around the block, and when you return, start your work. Or simply stay in your PJs up until you're ready to start working, then change your clothes and immediately kick off your work day. &lt;/p&gt;

&lt;p&gt;Choosing a routine or process to start the day will help sharpen the lines between life and work, and will get you into deep focus faster. &lt;/p&gt;

&lt;h3&gt;
  
  
  📄  Document everything
&lt;/h3&gt;

&lt;p&gt;Successful remote cultures rely on strong documentation. When in doubt, you should document what you are working on, even when it feels like too much. Create publicly-trackable tasks for everything you plan on working on. Block out time on your public calendar so everyone knows what you're up to during the day. Write down your approach for whatever task you're working on in a publicly-viewable document. &lt;/p&gt;

&lt;p&gt;Thoughtful and publicly available documentation helps others gain visibility on what you're working on, reduces the need for meetings, and gives you more structure in what you're going to work on in a given day. Furthermore, the more available documentation, the less likely anyone will feel out of the loop with respect to decision-making thought processes or what's being worked on. &lt;/p&gt;

&lt;h3&gt;
  
  
  👩🏻‍💻  Choose a work area
&lt;/h3&gt;

&lt;p&gt;If behavioral psychology has taught us anything, it is that animals &lt;a href="https://en.wikipedia.org/wiki/Classical_conditioning"&gt;behave predictably to repeated stimuli&lt;/a&gt;. Just like a dog might start salivating when you walk to the cabinet in which its food is held, you'll want to take a nap if you climb into bed. &lt;/p&gt;

&lt;p&gt;To get into the work mindset, choose an area of the house (or a different place, like a co-working space or a coffee shop) to start your day that you only associate with work. &lt;/p&gt;

&lt;p&gt;And for the love of god, don't work in your bed, or you'll want to sleep while you work, and you'll think about work when you're trying to sleep. &lt;/p&gt;

&lt;h3&gt;
  
  
  ❌  Eliminate unhealthy micro-temptations
&lt;/h3&gt;

&lt;p&gt;When we finish a small task, it's in our nature to want to take a break. These breaks can be healthy – they provide us the opportunity to take stock of where we are, recharge, and focus on the next task at hand. &lt;/p&gt;

&lt;p&gt;However, there's a limit past which these breaks take us into unproductive territory. &lt;/p&gt;

&lt;p&gt;If too many micro-temptations (easily available snacks in the kitchen, the TV playing in the background) are present while you're trying to work, you may find yourself constantly getting distracted whenever you finish a task, no matter how small. &lt;/p&gt;

&lt;p&gt;To stay in the zone, keep the snack food farther out of reach, keep the TV off, and you'll get more done, and feel a greater reward when you take a well-deserved break. &lt;/p&gt;

&lt;h3&gt;
  
  
  👋  Find time for informal communication
&lt;/h3&gt;

&lt;p&gt;Spontaneous interactions, and unstructured time with friends and colleagues make us happier. To avoid feeling lonely while working remotely, proactively set aside time with your team to get to know eachother outside of a work context — coffee chats with folks in your area, social calls, or team offsites are all good ways to bond. &lt;/p&gt;

&lt;p&gt;Furthermore, and ironically, working remotely may also force you to be more proactive about scheduling time to spend time with your friends, particularly if you work non-traditional hours. &lt;/p&gt;

&lt;p&gt;Lastly, play with your work environment. Go to a coffee shop or a library where other people like you will be working. &lt;/p&gt;

&lt;h3&gt;
  
  
  📈  Measure results, not hours
&lt;/h3&gt;

&lt;p&gt;When you're in an office, it's natural to measure productivity in terms of "days of work". Remote work requires more self-discipline than simply clocking in and out of an office. At a company level, a culture of measuring results gives every employee more clarity about what he/she should be working on. &lt;/p&gt;

&lt;p&gt;At an individual level, setting measurable goals and tracking results is a great way to stay on track with the global objectives of the company without constantly checking in with other people, as well as a good way to determine when to stop working (when you accomplish the specific goal you set out for a day). &lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping it up
&lt;/h1&gt;

&lt;p&gt;As more companies adopt tools to enable better synchronous collaboration and asynchronous task management, the shift to remote work will continue to grow. &lt;/p&gt;

&lt;p&gt;To ensure this transition is successful, both for the company, and for the individual, specific measures should be taken to address the inherent disadvantages of remote work. &lt;/p&gt;

&lt;p&gt;At the end of the day, you can conquer these disadvantages by structuring your days to draw clear lines between work and life, triggering your mind to focus on work, and building in social time to your schedule. &lt;/p&gt;

&lt;p&gt;Have any other tips for living your best remote life? Let us know! &lt;/p&gt;

&lt;h1&gt;
  
  
  Are you a remote engineer?
&lt;/h1&gt;

&lt;p&gt;Remote teams use &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; to automate their integration tests. &lt;a href="http://walrus.ai"&gt;Try it out free!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C7Pyw0t3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/h5sqho8kaiheg4wakr0c.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C7Pyw0t3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/h5sqho8kaiheg4wakr0c.gif" alt="Integration test running in a console"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>mentalhealth</category>
      <category>remote</category>
    </item>
  </channel>
</rss>
