<?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: Greg Gorlen</title>
    <description>The latest articles on DEV Community by Greg Gorlen (@ggorlen).</description>
    <link>https://dev.to/ggorlen</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%2F1011395%2Ff01457e1-3f9f-42e6-857f-86448a750e3e.jpeg</url>
      <title>DEV Community: Greg Gorlen</title>
      <link>https://dev.to/ggorlen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ggorlen"/>
    <language>en</language>
    <item>
      <title>Avoiding False Positives in Node.js Tests</title>
      <dc:creator>Greg Gorlen</dc:creator>
      <pubDate>Wed, 04 Dec 2024 11:30:34 +0000</pubDate>
      <link>https://dev.to/appsignal/avoiding-false-positives-in-nodejs-tests-4gc2</link>
      <guid>https://dev.to/appsignal/avoiding-false-positives-in-nodejs-tests-4gc2</guid>
      <description>&lt;p&gt;When running tests, it's a great feeling to see dozens of green check marks indicating that a test suite is passing. It's especially gratifying after tackling a tricky bug or slogging through a tough feature. But those passing tests may be giving you a false sense of security.&lt;/p&gt;

&lt;p&gt;Often, bugs lurk in passing tests, undermining trust in the test suite and your application. Such tests can cause more harm than good, giving you a hearty pat on the back while hiding broken functionality. It can take months to weed out a false positive test, and that might be only after a customer complaint.&lt;/p&gt;

&lt;p&gt;This post examines several common false positive patterns that can crop up in test suites. Taken out of context, the examples may appear obvious, but they find their way into complex, real-world tests all the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;I will assume readers are familiar with ES6 JavaScript syntax and have written at least a handful of tests in NodeJS.&lt;/p&gt;

&lt;p&gt;Although I don't intend to be technology-specific, inevitably some of the pitfalls are specific to quirks in the JavaScript ecosystem (particularly its UI testing libraries). I expect readers will be unfamiliar with some (or most) of the libraries featured in the post, but I hope the underlying principles will be relevant.&lt;/p&gt;

&lt;p&gt;This post is current as of Node 22.11.1, &lt;code&gt;jest&lt;/code&gt; 29.7.0, &lt;code&gt;mocha&lt;/code&gt; 10.3.0, &lt;code&gt;chai&lt;/code&gt; 5.1.0, &lt;code&gt;chai-http&lt;/code&gt; 4.3.0, &lt;code&gt;supertest&lt;/code&gt; 6.3.3, &lt;code&gt;eslint&lt;/code&gt; 8.57.0, &lt;code&gt;@playwright/test&lt;/code&gt; 1.42.1, and &lt;code&gt;@testing-library/react&lt;/code&gt; 13.4.0.&lt;/p&gt;

&lt;p&gt;Let's begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common False Positive Test Patterns in Node.js
&lt;/h2&gt;

&lt;p&gt;First, we'll look at some common patterns when it comes to false positive test results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;equal()&lt;/code&gt; Rather Than &lt;code&gt;strictEqual()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;As a warmup, check out this Mocha test with Chai assertions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strict equality&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 equals '1'&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;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 equals false&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;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;null equals undefined&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;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;Here's the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx mocha strict-equality-bad.test.js


  strict equality
    ✔ 1 equals &lt;span class="s1"&gt;'1'&lt;/span&gt;
    ✔ 0 equals &lt;span class="nb"&gt;false&lt;/span&gt;
    ✔ null equals undefined


  3 passing &lt;span class="o"&gt;(&lt;/span&gt;2ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This classically illustrates a surprising JS type coercion, courtesy of the loose equality operator &lt;code&gt;==&lt;/code&gt;. Replacing &lt;code&gt;assert.equal()&lt;/code&gt; with &lt;code&gt;assert.strictEqual()&lt;/code&gt; (akin to &lt;code&gt;===&lt;/code&gt;) gives the more desirable result — these values being unequal (some output omitted for brevity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx mocha strict-equality-good.test.js


  strict equality
    1&lt;span class="o"&gt;)&lt;/span&gt; 1 equals &lt;span class="s1"&gt;'1'&lt;/span&gt;
    2&lt;span class="o"&gt;)&lt;/span&gt; 0 equals &lt;span class="nb"&gt;false
    &lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt; null equals undefined


  0 passing &lt;span class="o"&gt;(&lt;/span&gt;3ms&lt;span class="o"&gt;)&lt;/span&gt;
  3 failing

  1&lt;span class="o"&gt;)&lt;/span&gt; strict equality
       1 equals &lt;span class="s1"&gt;'1'&lt;/span&gt;:
     AssertionError: expected 1 to equal &lt;span class="s1"&gt;'1'&lt;/span&gt;
  2&lt;span class="o"&gt;)&lt;/span&gt; strict equality
       0 equals &lt;span class="nb"&gt;false&lt;/span&gt;:
     AssertionError: expected +0 to equal &lt;span class="nb"&gt;false
  &lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt; strict equality
       null equals undefined:
     AssertionError: expected null to equal undefined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following assertions also fail, as expected:&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;// Jest:&lt;/span&gt;
&lt;span class="nf"&gt;expect&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="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Chai:&lt;/span&gt;
&lt;span class="nf"&gt;expect&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;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The theme here (which will be a recurring theme in this post) is that matchers don't always behave as expected.&lt;/p&gt;

&lt;p&gt;These mistakes can be detected by experimenting with assertions to trigger failures. For example, consider the assertion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;be&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try plugging in temporary values like &lt;code&gt;.to.be(-1)&lt;/code&gt;, &lt;code&gt;.to.be(undefined)&lt;/code&gt;, and &lt;code&gt;.to.be(false)&lt;/code&gt; to ensure the assertion fails. If they don't, the assertion is too weak and might be hiding a bug. Failures are good!&lt;/p&gt;

&lt;p&gt;Test-driven development can help to avoid overly-permissive assertions. Tests that start out by failing before passing are more likely to function as intended than ones that pass from the outset, written against already implemented code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Overly General Assertions
&lt;/h3&gt;

&lt;p&gt;The next example looks at the dangers of broad matchers such as &lt;code&gt;.toBeTruthy()&lt;/code&gt; and &lt;code&gt;.toBeFalsy()&lt;/code&gt;, which are present in most assertion libraries. The following test has a serious bug, but passes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parser&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should parse `text` without throwing&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;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Parser&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;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&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 author wants to assert that the return value of the &lt;code&gt;parse()&lt;/code&gt; function is truthy; for example, that it returns an object. But the test author forgot to call the function, so the test is only asserting that the &lt;code&gt;parse&lt;/code&gt; property exists!&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;.toBeDefined()&lt;/code&gt; and &lt;code&gt;.toBeInstanceOf(Object)&lt;/code&gt; would fail in the same way. Variables and properties are typically defined and are often objects, so these assertions are too weak. Frequent use of loose assertions can indicate a code smell in the test or in the application under test. Functions should return values with predictable types, and assertions should be strict and specific to these types.&lt;/p&gt;

&lt;p&gt;General assertions tend to read poorly and emit vague failure messages. Rewrite assertions like &lt;code&gt;expect(meaningOfLife() === 42).toBe(true)&lt;/code&gt; to &lt;code&gt;expect(meaningOfLife()).toBe(42)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/77722646/how-to-modify-attribute-value-content-in-playwright-angular-15/" rel="noopener noreferrer"&gt;See this Playwright question on Stack Overflow&lt;/a&gt; for an example of a false positive &lt;code&gt;.toBeTruthy()&lt;/code&gt; assertion in the wild.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Shallow Equality for Deep Comparisons
&lt;/h3&gt;

&lt;p&gt;Similarly, false positives can occur when comparing objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deep equality&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[assert] two objects with same contents not equal&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;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notStrictEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// incorrect, passes&lt;/span&gt;
    &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notDeepEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// correct, fails&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[expect] two objects with same contents not equal&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// incorrect, passes&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// correct, fails&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 assertions labeled "incorrect" test identity rather than deep value equality.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;.not&lt;/code&gt; is a contributing factor to the problem. Avoiding &lt;code&gt;.not&lt;/code&gt; whenever possible can improve readability, as it often does in boolean logic in application code. Positive assertions tend to match a narrower set of values and are therefore more meaningful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Misunderstanding Assertion Behavior
&lt;/h3&gt;

&lt;p&gt;I've discussed some of the gotchas when using overly broad assertions, but fine-grained assertions can hide subtle, surprising behavior as well.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example: Playwright
&lt;/h4&gt;

&lt;p&gt;Setting aside unit testing for a moment, the browser automation library Playwright offers a Jest-style matcher called &lt;code&gt;.toBeHidden()&lt;/code&gt;. &lt;a href="https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-be-hidden" rel="noopener noreferrer"&gt;Playwright's documentation&lt;/a&gt; describes this matcher as follows (emphasis mine):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ensures that Locator either &lt;strong&gt;does not resolve to any DOM node&lt;/strong&gt;, or resolves to a non-visible one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first part of this sentence is surprising. &lt;code&gt;.toBeHidden()&lt;/code&gt; is a broader assertion than its name suggests. If an application never renders an element, &lt;code&gt;.toBeHidden()&lt;/code&gt; still passes, even if the author's intention was to ensure the element exists, but in a hidden state.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example: React Testing Library
&lt;/h4&gt;

&lt;p&gt;React Testing Library's &lt;code&gt;getBy&lt;/code&gt; queries implicitly assert by throwing an error when an element isn't located, and are &lt;a href="https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#using-get-variants-as-assertions" rel="noopener noreferrer"&gt;often used without &lt;code&gt;expect&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, it can be easy to forget that &lt;code&gt;queryBy&lt;/code&gt; variants return &lt;code&gt;null&lt;/code&gt; rather than throwing, and can't be used as implicit assertions like their &lt;code&gt;getBy&lt;/code&gt; complements.&lt;/p&gt;

&lt;p&gt;The lesson is to read the documentation for your assertions carefully. Names can be misleading. Always force assertions to fail to make sure they're working as advertised and intended.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forgetting to Call a Matcher
&lt;/h3&gt;

&lt;p&gt;Here's an embarrassing mistake I made migrating a Mocha/chai-http test to Jest/supertest.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;expect(response).to.be.json&lt;/code&gt; functions as expected in Mocha, but becomes a no-op in Jest when naively updated to &lt;code&gt;expect(response).toBe.json&lt;/code&gt;. The property &lt;code&gt;.json&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt;, and even if it were defined, it'd be a no-op without parentheses to call the function. The same mistake can appear in assertions like &lt;code&gt;expect(someValue).toBeTrue&lt;/code&gt;, which looks sensible from a linguistic perspective, but is do-nothing code.&lt;/p&gt;

&lt;p&gt;Jest offers &lt;a href="https://jestjs.io/docs/expect#expectassertionsnumber" rel="noopener noreferrer"&gt;&lt;code&gt;expect.assertions(number)&lt;/code&gt;&lt;/a&gt;, which holds you accountable to calling a certain number of assertions in a test. This adds a bit more protection against forgetting to call matchers and asynchronous assertions that run after the test ends. Linting can also identify do-nothing expressions and unused variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Misusing Mocks
&lt;/h3&gt;

&lt;p&gt;Writing mocks can be time-consuming so you can be tempted to cut corners. Poorly written test mocks can hide bugs in application code. Consider the following Sequelize database query:&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;User.findOne&lt;/code&gt; was mocked to ignore its argument and unconditionally return &lt;code&gt;{id: 1, name: "Alan"}&lt;/code&gt;, the test would still pass, even if the function call argument was incorrect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./user&lt;/span&gt;&lt;span class="dl"&gt;"&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="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./user&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="na"&gt;findOne&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="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should create and retrieve a user&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="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValueOnce&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// a bad call made by the code under test&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;nonsenseArg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// passes, even though the call to findOne is broken!&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectContaining&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;createdAt&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="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;updatedAt&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="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A solution is to assert that a particular argument was passed to the &lt;code&gt;User.findOne()&lt;/code&gt; mock using &lt;code&gt;expect(User.findOne).toHaveBeenCalledWith({where: 1})&lt;/code&gt;. Although this helps to avoid a false positive, it can make refactoring and writing tests frustrating.&lt;/p&gt;

&lt;p&gt;Similarly, in the early days of React, the Enzyme testing library supported invasive mocks of implementation details, like the &lt;code&gt;setState()&lt;/code&gt; hook. Such mocks can easily be misused to strongarm the suite into 100% coverage, failing to test the component's actual behavior by injecting values that cause the component to behave differently than it would at run time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Regex Matching Incorrectly
&lt;/h3&gt;

&lt;p&gt;Testing libraries like Playwright, React Testing Library, and Jest offer many matchers which accept regexes. It's easy to forget that &lt;code&gt;/Hello./g&lt;/code&gt; matches &lt;code&gt;"Hello world"&lt;/code&gt; and &lt;code&gt;"Hello!"&lt;/code&gt; because &lt;code&gt;.&lt;/code&gt; is a regex special character, not a period, and the regex doesn't have anchors. Perhaps &lt;code&gt;/^Hello\.$/&lt;/code&gt; was intended. Using a regex when a plain string match would suffice is a testing code smell.&lt;/p&gt;

&lt;p&gt;Even when using plain strings, inadvertently using substring matches when exact matches were intended can cause tests to pass when they shouldn't. Luckily, modern UI testing frameworks like Playwright and React Testing Library offer strict assertions and queries by default, failing with a clear error if multiple elements match an overly broad query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Copy-Paste Errors
&lt;/h3&gt;

&lt;p&gt;Testing mistakes often come down to silly copy-paste errors and typos. These are particularly common in test suites with long chains of similar assertions.&lt;/p&gt;

&lt;p&gt;For example, when testing a form with a few buttons, a tester might create the following suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&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;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// simple page for demonstration&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;
&amp;lt;form&amp;gt;
  &amp;lt;input type="submit" value="Submit"&amp;gt;
  &amp;lt;input type="reset" value="Reset"&amp;gt;
  &amp;lt;button type="button"&amp;gt;Cancel&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&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;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit button exists&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancel button exists&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cancel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reset button exists&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&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;Here, the author forgot to adjust the content of the third test block after copy-pasting the first block twice. The test name was updated, so the output misleadingly makes it seem like the 'reset' button is being tested. Removing the &lt;code&gt;&amp;lt;input type="reset" value="Reset"&amp;gt;&lt;/code&gt; element from the page doesn't cause the test case to fail as it should.&lt;/p&gt;

&lt;p&gt;In the era of GPT-generated testing code, a hallucination like this is easy to overlook.&lt;/p&gt;

&lt;p&gt;On more complex tests that trigger JS code, a coverage tool can help catch these mistakes. Static analysis tools available as IDE plugins can also detect duplicate code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Incorrect Properties in Configuration Objects
&lt;/h3&gt;

&lt;p&gt;Configuration objects are a readable way to pass arguments to a function, making up for the fact that JS doesn't support named arguments. Modern UI testing libraries use them liberally.&lt;/p&gt;

&lt;p&gt;However, if you're using plain JS rather than TypeScript, it's easy to make a call to a function with a typo or incorrect property name in the configuration object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// incorrect!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This React Testing Library query should use the &lt;code&gt;name:&lt;/code&gt; key rather than &lt;code&gt;value:&lt;/code&gt;. JS will silently ignore the argument, possibly retrieving an unexpected element or failing to implicitly assert the header's user-visible text.&lt;/p&gt;

&lt;p&gt;This scenario can also creep up in global configuration files and strings in general. In addition to type checking and linting, the usual strategies for false positive avoidance described throughout this post apply.&lt;/p&gt;

&lt;h3&gt;
  
  
  Misusing Snapshot Tests
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://jestjs.io/docs/snapshot-testing" rel="noopener noreferrer"&gt;Snapshot tests&lt;/a&gt; let you diff a live UI component (for example, a React or Vue component) against a version-controlled, stringified snapshot of the component. If the strings match, the test passes.&lt;/p&gt;

&lt;p&gt;Be careful, though: snapshot testing can lead to false positives if the reference snapshot is updated to match a broken component. Jest makes it effortless to update all failing snapshots in one command, even when some shouldn't be updated. Hastily updating snapshots without careful examination can bake false positives into tests.&lt;/p&gt;

&lt;p&gt;Unlike traditional assertions, it's not as apparent from glancing at a large snapshot whether it's accurate or not, allowing bad snapshots to be handwaved through a code review. In some cases, the content of external snapshot files may not be examined at all.&lt;/p&gt;

&lt;p&gt;Having a strong code review culture that examines tests as critically and thoroughly as application code is another tool to mitigate false positives.&lt;/p&gt;

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

&lt;p&gt;After reading this article, I hope you'll walk away with a more critical eye on your test suites. This is far from an exhaustive list, so be on the lookout for other pitfalls, especially when you're onboarding a codebase or using an unfamiliar testing library.&lt;/p&gt;

&lt;p&gt;The testing errors we've examined are a subclass of logic errors in general applications. Working cautiously to guard against and surface silent errors helps ensure the code you're working with stays correct.&lt;/p&gt;

&lt;p&gt;Happy debugging!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you liked this post, &lt;a href="https://blog.appsignal.com/javascript-sorcery" rel="noopener noreferrer"&gt;subscribe to our JavaScript Sorcery list&lt;/a&gt; for a monthly deep dive into more magical JavaScript tips and tricks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S. If you need an APM for your Node.js app, go and &lt;a href="https://www.appsignal.com/nodejs" rel="noopener noreferrer"&gt;check out the AppSignal APM for Node.js&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>node</category>
    </item>
    <item>
      <title>Puppeteer in Node.js: More Antipatterns to Avoid</title>
      <dc:creator>Greg Gorlen</dc:creator>
      <pubDate>Wed, 21 Jun 2023 11:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/puppeteer-in-nodejs-more-antipatterns-to-avoid-2a9j</link>
      <guid>https://dev.to/appsignal/puppeteer-in-nodejs-more-antipatterns-to-avoid-2a9j</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/puppeteer/puppeteer"&gt;Puppeteer&lt;/a&gt; is a powerful browser automation library for web scraping and integration testing. However, the asynchronous, real-time API leaves plenty of room for gotchas and antipatterns to arise.&lt;/p&gt;

&lt;p&gt;This article is part of a series, starting with &lt;a href="https://serpapi.com/blog/puppeteer-antipatterns/"&gt;Avoiding Puppeteer Antipatterns&lt;/a&gt; and &lt;a href="https://blog.appsignal.com/2023/02/08/puppeteer-in-nodejs-common-mistakes-to-avoid.html"&gt;Puppeteer in Node.js: Common Mistakes to Avoid&lt;/a&gt;. In this post, we'll add another dozen antipatterns to the list. There will be no overlap with previous installments, so you may wish to start with those.&lt;/p&gt;

&lt;p&gt;While these antipatterns aren't quite full-fledged mistakes, weeding them out of your scripts (or being judicious when employing them) will increase the reliability of your Puppeteer code.&lt;/p&gt;

&lt;p&gt;Let's begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;We will assume you are familiar with ES6 JavaScript syntax, promises, the browser DOM, and Node, and have written a few Puppeteer scripts already.&lt;/p&gt;

&lt;p&gt;At the time of writing, the version of Puppeteer used was 20.3.0.&lt;/p&gt;

&lt;p&gt;Onward to the antipatterns!&lt;/p&gt;

&lt;h2&gt;
  
  
  Antipatterns to Avoid in Puppeteer for Node.js
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Underusing &lt;code&gt;page.goto&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I often see scraping scripts automating a search by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigating to a website's landing page.&lt;/li&gt;
&lt;li&gt;Accepting a cookie banner.&lt;/li&gt;
&lt;li&gt;Typing a search term into an input box.&lt;/li&gt;
&lt;li&gt;Clicking a button to submit the query.&lt;/li&gt;
&lt;li&gt;Waiting for the second navigation to complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While this may make sense for testing, in scraping contexts these steps can often be bypassed by adding a query parameter such as &lt;code&gt;https://www.some-site.com/search?q=search+term&lt;/code&gt; and using &lt;code&gt;page.goto(searchResultURL)&lt;/code&gt; directly. Skipping the intermediate page speeds up the script, requires less code, and typically improves reliability.&lt;/p&gt;

&lt;p&gt;The same is often true for automation involving iframes. In many cases, the frame source URL can be navigated to directly, bypassing the hassle of working with the parent document. If the frame source isn't known in advance, you can extract it and strip off the outer document with a &lt;code&gt;goto&lt;/code&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;frame&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;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iframe&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;src&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;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;domcontentloaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code simplification may be worth the cost of the extra (probably cached) load.&lt;/p&gt;

&lt;p&gt;Sometimes clicking links causes unpredictable, fussy navigation behavior, such as unwanted popups. In such cases, consider following the pattern in the above snippet, extracting the link's &lt;code&gt;href&lt;/code&gt; property and plugging it into &lt;code&gt;page.goto&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;page.on&lt;/code&gt; Vs. &lt;code&gt;page.waitForRequest&lt;/code&gt; or &lt;code&gt;page.waitForResponse&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;page.on("request", handler)&lt;/code&gt; and &lt;code&gt;page.on("response", handler)&lt;/code&gt; are callback-based event listeners. These are useful for intercepting and processing all requests or responses, but can be awkward to use with asynchronous control flow.&lt;/p&gt;

&lt;p&gt;In cases when you're waiting for one or more specific responses to arrive, instead of chaining your dependent code from the callback or promisifying &lt;code&gt;page.on&lt;/code&gt;, consider using &lt;code&gt;page.waitForRequest&lt;/code&gt; or &lt;code&gt;page.waitForResponse&lt;/code&gt;. These handy methods are essentially promisifed &lt;code&gt;page.on()&lt;/code&gt; handlers.&lt;/p&gt;

&lt;p&gt;For dialogs, promisification is unavoidable, as Puppeteer doesn't offer a &lt;code&gt;page.waitForDialog&lt;/code&gt; wrapper at the present time. However, &lt;code&gt;page.once&lt;/code&gt; is a handy way to avoid having to remove the listener once you've intercepted the dialog:&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;dialogDismissed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;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="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dialog&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;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="cm"&gt;/* take action to trigger the dialog */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&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;dialogDismissed&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 has a subtle issue: there's no timeout, so your script can &lt;a href="https://blog.appsignal.com/2023/02/08/puppeteer-in-nodejs-common-mistakes-to-avoid.html#using-infinite-timeouts"&gt;silently hang forever&lt;/a&gt;. You can add a timeout as follows:&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;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&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;dialogDismissed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;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="nx"&gt;reject&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;timeoutId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dialog&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;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutId&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;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="cm"&gt;/* take action to trigger the dialog */&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&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;dialogDismissed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since requests and responses are liable to occur in any order at any time, &lt;code&gt;page.once&lt;/code&gt; is less useful for those events than it is for dialogs, which are usually predictable.&lt;/p&gt;

&lt;p&gt;Note that, in general, you won't need to use &lt;code&gt;new Promise&lt;/code&gt; much in Puppeteer. Promisifying a promise-based API is known as the &lt;a href="https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it"&gt;explicit promise constructor antipattern&lt;/a&gt; and usually appears when programmers aren't accustomed to working with promises.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Using Specific Wait or Evaluate Methods
&lt;/h3&gt;

&lt;p&gt;As with request and response handlers, many Puppeteer methods have a hierarchy of generality. Here are the &lt;code&gt;evaluate&lt;/code&gt;-family calls (roughly), from general to specific:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;page.evaluate()&lt;/code&gt; can do just about anything any other Puppeteer API call can do in the browser. It's powerful but not specific.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;page.$eval()&lt;/code&gt; and &lt;code&gt;page.$$eval()&lt;/code&gt; are shorthands for common-case &lt;code&gt;page.evaluate()&lt;/code&gt; calls that immediately run a &lt;code&gt;document.querySelector()&lt;/code&gt; or &lt;code&gt;document.querySelectorAll()&lt;/code&gt; as their callback's first step.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;page.waitForFunction()&lt;/code&gt; is shorthand for a &lt;code&gt;page.evaluate()&lt;/code&gt; that registers a &lt;code&gt;MutationObserver&lt;/code&gt; or &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop that repeatedly checks a condition, then returns when the condition becomes true.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;page.waitForSelector()&lt;/code&gt; is shorthand for a &lt;code&gt;page.waitForFunction()&lt;/code&gt; that blocks until a specific selector matches an element in the DOM.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's an antipattern to use a general method when a specific one exists that's tailored for the job. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.foo-bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&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;trim&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;Versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.foo-bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elements&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;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&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;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Not Reusing Browsers
&lt;/h3&gt;

&lt;p&gt;Launching browsers is a heavy undertaking. It's a good idea to clear browser state after each run to maintain idempotency when testing, and in web applications that use Puppeteer with Express to perform tasks. But, in many cases, a browser can be reused safely, relying on pages to encapsulate tasks.&lt;/p&gt;

&lt;p&gt;When business logic and safety can accommodate it, browser (or even page reuse) can provide dramatic efficiency gains.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scraping the DOM Rather than Responses
&lt;/h3&gt;

&lt;p&gt;Many web applications rely on information from JSON data, either embedded inside a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; element or as XHR response payloads. Instead of figuring out how to extract the data from the DOM, it's often useful to intercept the responses or pull the information out of a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;. Removing the fickle presentation layer from the process can make your code more reliable.&lt;/p&gt;

&lt;p&gt;While raw data is likely more stable than the DOM, in some cases JSON payload structures are subject to change or may be harder to identify and parse than the DOM. Although responses aren't always a useful way to scrape data, it's worth popping open the network tab to try to find the response that has the data in it. Occasionally striking gold makes it worth the effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using XPath Instead of CSS Selectors
&lt;/h3&gt;

&lt;p&gt;Since XPath tends to be more verbose and trickier to write correctly than CSS selectors, CSS selectors should be preferred over XPath when possible.&lt;/p&gt;

&lt;p&gt;Puppeteer 19.7.1 introduced a &lt;code&gt;::-p-text&lt;/code&gt; selector, which covers a good deal of XPath's common use case in Puppeteer, selecting elements by text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Attribute CSS Syntax for Classes
&lt;/h3&gt;

&lt;p&gt;CSS selectors have special and useful syntax for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors"&gt;selecting elements by their attributes&lt;/a&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can select this with &lt;code&gt;page.$('[for="username"]')&lt;/code&gt;, which is fine, but problems arise when applying this syntax to classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row align-items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, there's good reason to prefer &lt;code&gt;.row.align-items-center&lt;/code&gt; over &lt;code&gt;[class="row align-items-center"]&lt;/code&gt;. The dot syntax is easier to read and write, and is agnostic of ordering and additional attributes. If the class list changes to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"align-items-center row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row align-items-center p-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the attribute selector fails. To make the two approaches interchangeable, the attribute selector &lt;code&gt;~&lt;/code&gt; could be used: &lt;code&gt;[class~="row"][class~="align-items-center"]&lt;/code&gt;. The verbosity makes the antipattern obvious.&lt;/p&gt;

&lt;p&gt;There are situations suited to the &lt;code&gt;[class="..."]&lt;/code&gt; pattern — for example, selecting elements that have a consistent prefix attribute name with a generated postfix: &lt;code&gt;[class^="p-"]&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Premature Abstractions
&lt;/h3&gt;

&lt;p&gt;In scraping and testing scenarios, I often see programmers writing classes and functions before they've correctly written their scraping or testing logic. In many cases, these abstractions make their scripts harder to debug than they would otherwise, introducing promise-management issues, memory leaks, and other sources of confusion.&lt;/p&gt;

&lt;p&gt;I typically follow the &lt;a href="https://grugbrain.dev/#grug-on-factring-your-code"&gt;Grug-Brained Dev&lt;/a&gt;'s advice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...one thing grug come to believe: not factor your application too early!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the scraper or testing block has been written correctly, then consider breaking the code into logical chunks.&lt;/p&gt;

&lt;p&gt;Even if this is done correctly, unnecessary abstractions can still be problematic. In the case of tests, seeing the imperative steps in a longer test can make for easier maintenance than a series of nested helper functions (at the expense of a bit of repetition).&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Cleaning Up Browser and Page Handles with &lt;code&gt;finally&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Even if you take care to ensure &lt;code&gt;browser.close()&lt;/code&gt; is called, it's common to forget errors, which can prevent the browser from closing.&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;finally&lt;/code&gt; block that calls &lt;code&gt;browser.close()&lt;/code&gt; for every &lt;code&gt;browser.launch()&lt;/code&gt; call to ensure proper resource cleanup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Using Built-in Selectors
&lt;/h3&gt;

&lt;p&gt;As mentioned above, Puppeteer offers a &lt;code&gt;::-p-text&lt;/code&gt; &lt;a href="https://pptr.dev/guides/query-selectors#p-selectors"&gt;p selector&lt;/a&gt;, along with &lt;code&gt;::-p-aria&lt;/code&gt; and &lt;code&gt;::-p-xpath&lt;/code&gt;. Prefer using these over hand-rolled alternatives.&lt;/p&gt;

&lt;p&gt;Here's an example of clicking a button based on its text content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;button&amp;gt;Click me&amp;lt;/button&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;btn&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;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button::-p-text(Click)&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;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code determines a match based on a substring of the text content, case-sensitively.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; combinators let you traverse shadow roots, with &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; traversing deep shadow roots and &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; exploring one root deep.&lt;/p&gt;

&lt;p&gt;Usual selection methods like &lt;code&gt;page.waitForSelector&lt;/code&gt;, &lt;code&gt;page.$eval&lt;/code&gt;, &lt;code&gt;page.$$eval&lt;/code&gt;, and &lt;code&gt;page.evaluate&lt;/code&gt; all work with built-in selectors. Additionally, Puppeteer has deprecated &lt;code&gt;page.waitForXPath&lt;/code&gt; and &lt;code&gt;page.$x&lt;/code&gt;, unifying the selector API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Using &lt;code&gt;userDataDir&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Logins can be tricky to automate. Two-factor auth, input fields with complex asynchronous validation and masks, redirects, and iframes abound.&lt;/p&gt;

&lt;p&gt;Instead of the hassle, you can set a &lt;code&gt;userDataDir&lt;/code&gt; and log in manually in an idling Chromium browser launched by Puppeteer. As long as the session persists, you can go about your automation task directly from the site's dashboard.&lt;/p&gt;

&lt;p&gt;Even in cases when you decide to automate login, persisting the session offers performance improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not Using Playwright for User-Facing Testing in Node.js
&lt;/h2&gt;

&lt;p&gt;Microsoft's newer &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt; library offers a different element selection philosophy than Puppeteer. Puppeteer scripts tend to rely on CSS selectors and XPath. In contrast, Playwright's approach prioritizes user-facing attributes such as accessible roles, text, and titles.&lt;/p&gt;

&lt;p&gt;Third-party testing packages like &lt;a href="https://github.com/argos-ci/jest-puppeteer/blob/main/packages/expect-puppeteer/README.md"&gt;expect-puppeteer&lt;/a&gt; and &lt;a href="https://github.com/testing-library/pptr-testing-library"&gt;pptr-testing-library&lt;/a&gt; attempt to bring the user-facing philosophy to Puppeteer. However, Playwright offers this style of testing out of the box. Playwright is opinionated and discourages non-user-facing selection methods as well as its inherited Puppeteer-style API, which it has mostly deprecated.&lt;/p&gt;

&lt;p&gt;For most web scraping tasks, however, it's natural to use Puppeteer-style CSS selectors. I haven't seen evidence of any benefits in adhering to user-facing principles when web scraping. Puppeteer's simpler API gets out of the way a bit more. I appreciate using it as a thin, unopinionated wrapper on already-working browser code, adding controlled events and waits when necessary.&lt;/p&gt;

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

&lt;p&gt;In this article, we covered a variety of antipatterns that can degrade the quality of Puppeteer automation scripts.&lt;/p&gt;

&lt;p&gt;A central theme that helps avoid these antipatterns is to use the most precise tool for the job. Choose focused, high-level API methods to avoid the complexities of more powerful, low-level methods that are less idiomatic and expose unnecessary details.&lt;/p&gt;

&lt;p&gt;Additionally, I've advocated for treating Puppeteer's evaluation and wait APIs as simple wrappers on the browser console, leaving user-facing testing principles to Playwright.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you liked this post, &lt;a href="https://blog.appsignal.com/javascript-sorcery"&gt;subscribe to our JavaScript Sorcery list&lt;/a&gt; for a monthly deep dive into more magical JavaScript tips and tricks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S. If you need an APM for your Node.js app, go and &lt;a href="https://www.appsignal.com/nodejs"&gt;check out the AppSignal APM for Node.js&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>node</category>
    </item>
    <item>
      <title>Puppeteer in Node.js: Common Mistakes to Avoid</title>
      <dc:creator>Greg Gorlen</dc:creator>
      <pubDate>Wed, 15 Feb 2023 15:20:19 +0000</pubDate>
      <link>https://dev.to/appsignal/puppeteer-in-nodejs-common-mistakes-to-avoid-34a8</link>
      <guid>https://dev.to/appsignal/puppeteer-in-nodejs-common-mistakes-to-avoid-34a8</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/puppeteer/puppeteer" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; is a powerful Node.js browser automation library for integration testing and web scraping. However, like any complex software, it comes with plenty of potential pitfalls.&lt;/p&gt;

&lt;p&gt;In this article, I'll discuss a variety of common Puppeteer mistakes I've encountered in personal and consulting projects, as well as when monitoring the &lt;a href="https://stackoverflow.com/questions/tagged/puppeteer" rel="noopener noreferrer"&gt;Puppeteer tag on Stack Overflow&lt;/a&gt;. Once you're aware of these problematic patterns, you can write more robust scraping and testing code, while spending less time debugging and wading through arcane Puppeteer errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;p&gt;The article was written using Node 18, Puppeteer 19.4.1, Chrome 108.0.5359.125, and Firefox 108.0.1.&lt;/p&gt;

&lt;p&gt;We will assume you are familiar with ES6 JavaScript syntax, browser development tools, the browser DOM, and Node, and have previously written some Puppeteer scripts.&lt;/p&gt;

&lt;p&gt;Now let's examine the pitfalls of Puppeteer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls in Puppeteer for Node.js
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Attempting to Return Objects and DOM Elements from &lt;code&gt;evaluate&lt;/code&gt; Callbacks
&lt;/h3&gt;

&lt;p&gt;The following snippet should be a familiar pattern to those who've previously used Puppeteer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;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="s2"&gt;puppeteer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;browser&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="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&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;pages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;domcontentloaded&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;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// executed in the browser&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&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&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;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;element&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; always an empty object, {}&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="o"&gt;=&amp;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="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: I'll skip the &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE" rel="noopener noreferrer"&gt;IIFE&lt;/a&gt;, import, and error-handling boilerplate in the remainder of this article.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The code above attempts to return a DOM element from the browser context back to Node for further processing (clicking it, typing into it, extracting its text content, etc.). However, the &lt;a href="https://pptr.dev/api/puppeteer.page.evaluate/" rel="noopener noreferrer"&gt;&lt;code&gt;evaluate&lt;/code&gt;&lt;/a&gt; call, which runs code in the browser context, resolves to an empty object in Node. DOM elements are complex structures with circular references and cannot be readily serialized and deserialized. These elements can't be decoupled from the browser environment in a meaningful way.&lt;/p&gt;

&lt;p&gt;This behavior isn't specific to Puppeteer. Running &lt;code&gt;JSON.stringify(document.querySelector("h1"))&lt;/code&gt; on a page with a header element should return &lt;code&gt;'{}'&lt;/code&gt; on a Chromium-based browser. Firefox gives &lt;code&gt;'null'&lt;/code&gt;, also indicating a serialization failure.&lt;/p&gt;

&lt;p&gt;One solution is to use &lt;a href="https://pptr.dev/api/puppeteer.page._" rel="noopener noreferrer"&gt;&lt;code&gt;page.$("h1")&lt;/code&gt;&lt;/a&gt; (or the more general &lt;a href="https://pptr.dev/api/puppeteer.page.evaluatehandle" rel="noopener noreferrer"&gt;&lt;code&gt;page.evaluateHandle&lt;/code&gt;&lt;/a&gt;) to create a Puppeteer &lt;a href="https://pptr.dev/api/puppeteer.elementhandle" rel="noopener noreferrer"&gt;&lt;code&gt;ElementHandle&lt;/code&gt;&lt;/a&gt; that exposes an interface to the DOM element. Alternatively, you can use &lt;a href="https://pptr.dev/api/puppeteer.jshandle" rel="noopener noreferrer"&gt;&lt;code&gt;JSHandle&lt;/code&gt;&lt;/a&gt; which exposes an interface to the JS object. These interfaces enable you to run code in the browser context on the element, possibly to extract serializable data such as text content or element properties, or issue &lt;a href="https://pptr.dev/faq/#q-whats-the-difference-between-a-trusted-and-untrusted-input-event" rel="noopener noreferrer"&gt;trusted events&lt;/a&gt;. Be sure to dispose of these handles when you no longer need them to avoid memory leaks.&lt;/p&gt;

&lt;p&gt;I've used &lt;code&gt;evaluate&lt;/code&gt; here as it's the most general way to run code in the browser, but the same behavior applies to &lt;a href="https://pptr.dev/api/puppeteer.elementhandle._eval" rel="noopener noreferrer"&gt;&lt;code&gt;$eval&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pptr.dev/api/puppeteer.elementhandle.__eval" rel="noopener noreferrer"&gt;&lt;code&gt;$$eval&lt;/code&gt;&lt;/a&gt; as well. These methods are shorthands for the common case when &lt;code&gt;document.querySelector&lt;/code&gt; or &lt;code&gt;document.querySelectorAll&lt;/code&gt; is the first line in the &lt;code&gt;evaluate&lt;/code&gt; callback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying to Access Variables from an &lt;code&gt;evaluate&lt;/code&gt; Callback
&lt;/h2&gt;

&lt;p&gt;At times, variables may appear to be in scope from an &lt;code&gt;evaluate&lt;/code&gt; callback in Puppeteer when they aren't. The following example is a bit contrived because we could use &lt;code&gt;$eval&lt;/code&gt;, but it nevertheless illustrates the pattern succinctly:&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;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h1&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;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="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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;selector&lt;/code&gt; seems like it should be in scope of the &lt;code&gt;evaluate&lt;/code&gt; callback, but it doesn't exist when the callback runs in the browser, throwing &lt;code&gt;Evaluation failed: ReferenceError: selector is not defined&lt;/code&gt;. Yet again, serialization is the culprit: the browser is a completely separate process from Node that doesn't have your Node variables in scope.&lt;/p&gt;

&lt;p&gt;Using the string version of &lt;code&gt;evaluate&lt;/code&gt; makes the situation clearer:&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;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h1&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;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="s2"&gt;`
  document.querySelector("&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;").textContent
`&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;span class="c1"&gt;// =&amp;gt; Example Domain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, building a string can lead to quoting problems. This motivates the more general approach — passing data to the browser by including extra arguments to &lt;code&gt;evaluate&lt;/code&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;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h1&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;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;selector&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&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="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This parameter-passing pattern also applies to other important Puppeteer calls, such as &lt;a href="https://pptr.dev/api/puppeteer.page.waitforfunction/" rel="noopener noreferrer"&gt;&lt;code&gt;page.waitForFunction&lt;/code&gt;&lt;/a&gt;. A subtle difference is that &lt;code&gt;waitForFunction&lt;/code&gt;'s second argument is a configuration options object, followed by the variable parameter 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="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForFunction&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg2&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;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/58870660/puppeteer-converting-circular-structure-to-json-are-you-passing-a-nested-jshand/68294113#68294113" rel="noopener noreferrer"&gt;This Stack Overflow post&lt;/a&gt; offers tips on passing complex arguments to &lt;code&gt;evaluate&lt;/code&gt; calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assuming Browser DevTools Is the Same as Node
&lt;/h2&gt;

&lt;p&gt;Puppeteer programmers stuck on a bug often claim their selectors work in browser developer tools, but fail in Puppeteer. Unfortunately, there's no guarantee that code that works in browser developer tools will also work in Puppeteer. Here are some reasons why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developer tools exposes &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" rel="noopener noreferrer"&gt;iframe&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot" rel="noopener noreferrer"&gt;shadow root&lt;/a&gt; subtrees. Puppeteer requires these trees to be explicitly expanded. It’s worth noting that Microsoft's Playwright library has locators that &lt;a href="https://playwright.dev/docs/locators#locate-in-shadow-dom" rel="noopener noreferrer"&gt;expand the shadow DOM by default&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;By the time you get around to interacting with developer tools, the page has typically loaded its resources and executed its JS scripts. In Puppeteer, you can use &lt;code&gt;waitForSelector&lt;/code&gt; calls to ensure JS-injected elements are available before interaction.&lt;/li&gt;
&lt;li&gt;When working in an unautomated browser's developer tools, the website's server trusts you and delivers a full experience. In Node, Puppeteer scripts are often detected as bots, so they are blocked outright or served a restricted version of a page. Adding &lt;code&gt;console.log(await page.content())&lt;/code&gt; is an easy way to verify that your HTML structure in Puppeteer is what you expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recognizing developer tools and Node as distinct environments goes a long way to ensuring smooth translations from your developer tools exploration code to the final Puppeteer script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assuming Puppeteer's Headless Mode Works the Same as Headful
&lt;/h2&gt;

&lt;p&gt;Just as DevTools is distinct from Node, it's a mistake to assume that Puppeteer's headless mode works the same as headful mode. Websites have a much easier time detecting scripts as bots when in headless mode than in headful mode.&lt;/p&gt;

&lt;p&gt;As with the above tip, &lt;code&gt;console.log(await page.content())&lt;/code&gt; is a great way to ensure a document is what you expect. If a selector you see in the developer tools or in headful mode isn't in the headless log, there's a good chance you've been blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not Using &lt;code&gt;Promise.all&lt;/code&gt; When Triggering Navigation with a Click
&lt;/h2&gt;

&lt;p&gt;Navigation is a common point of failure in Puppeteer scripts. The following code is unsafe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// trigger a navigation&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, this is a race condition. If the navigation resolves before &lt;a href="https://pptr.dev/api/puppeteer.page.waitfornavigation/" rel="noopener noreferrer"&gt;&lt;code&gt;waitForNavigation&lt;/code&gt;&lt;/a&gt; has the chance to run, the script may throw a timeout error. The correct pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// trigger a navigation&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&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;navigationPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// trigger a navigation&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;navigationPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In these examples, the navigation wait promise is set before the navigation is triggered, ensuring that it will resolve as intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Unnecessary Calls to &lt;code&gt;waitForNetworkIdle&lt;/code&gt; or &lt;code&gt;waitForNavigation&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Another navigation-related mistake is making spurious calls to &lt;a href="https://pptr.dev/api/puppeteer.page.waitfornetworkidle/" rel="noopener noreferrer"&gt;&lt;code&gt;waitForNetworkIdle&lt;/code&gt;&lt;/a&gt; or &lt;code&gt;waitForNavigation&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;some url&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;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is logical: we want to trigger a navigation with &lt;a href="https://pptr.dev/api/puppeteer.page.goto/" rel="noopener noreferrer"&gt;&lt;code&gt;goto&lt;/code&gt;&lt;/a&gt;, then wait for that navigation to settle. But &lt;code&gt;goto&lt;/code&gt; already waits for navigation, so the second &lt;code&gt;waitForNavigation&lt;/code&gt; is waiting for a navigation that's already occurred, causing a timeout.&lt;/p&gt;

&lt;p&gt;It's a similar story when waiting for an idle network state, either with a &lt;code&gt;waitForNetworkIdle&lt;/code&gt; call or &lt;code&gt;page.goto(url, {waitUntil: "networkidle0"})&lt;/code&gt;. In fact, waiting for an idle network can make a script hang forever if the automated page keeps enough long-running connections open.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;networkidle2&lt;/code&gt; is usually safer since it tolerates two long-running connections. However, it is often used out of laziness or lack of awareness in place of the clear-cut &lt;code&gt;waitForSelector&lt;/code&gt; and &lt;code&gt;waitForFunction&lt;/code&gt; predicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Infinite Timeouts
&lt;/h2&gt;

&lt;p&gt;It's a mistake to set any timeout to 0 — for example, with &lt;a href="https://pptr.dev/api/puppeteer.page.setdefaulttimeout/" rel="noopener noreferrer"&gt;&lt;code&gt;page.setDefaultTimeout(0)&lt;/code&gt;&lt;/a&gt;. Infinite timeouts introduce the potential for the script to block forever when encountering an unexpected state, without giving a clear error message. Under most circumstances, when a script hangs on a selector or navigation for more than a few minutes, it should log an error and either exit so its maintainer can fix the problem, or restart itself if it should keep attempting to do something.&lt;/p&gt;

&lt;p&gt;Usually, when I come across infinite timeouts in Puppeteer scripts, it's an artifact of attempting to fix a deeper issue, like the script being detected as a bot and blocked. But the infinite timeout makes these errors harder to detect and resolve by stifling them and causing a silent hang.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forgetting to Await a Puppeteer Call
&lt;/h2&gt;

&lt;p&gt;Almost all Puppeteer API calls are asynchronous. The reason for the asynchronous interface is that the browser runs in a separate process from Node. Puppeteer's methods send and receive data and wait for the browser process to respond, much like networking or file system operations. The Node process can use this time to perform CPU-bound work.&lt;/p&gt;

&lt;p&gt;A common mistake is forgetting to &lt;code&gt;await&lt;/code&gt; a promise returned by a Puppeteer call. This can lead to confusing and non-deterministic errors and race conditions.&lt;/p&gt;

&lt;p&gt;For example, omitting &lt;code&gt;await&lt;/code&gt; on a &lt;code&gt;page.goto&lt;/code&gt; call may result in a &lt;code&gt;Protocol error (Page.navigate): Target closed&lt;/code&gt; or &lt;code&gt;Execution context was destroyed, most likely because of a navigation&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Awaiting Synchronous Puppeteer Calls
&lt;/h2&gt;

&lt;p&gt;One solution to the missing &lt;code&gt;await&lt;/code&gt; problem described above is to &lt;code&gt;await&lt;/code&gt; everything, but this can lead to confusion as well.&lt;/p&gt;

&lt;p&gt;For example, I see the following pattern often:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* handle the request */&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// do stuff after the request has been handled&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;page.on&lt;/code&gt; doesn't return a promise, it's easy to forget that &lt;code&gt;// do stuff after the request has been handled&lt;/code&gt; runs before the request handler callback. The callback is in a different promise chain.&lt;/p&gt;

&lt;p&gt;To solve this, use Puppeteer's &lt;code&gt;page.waitForRequest&lt;/code&gt; (or &lt;code&gt;page.waitForResponse&lt;/code&gt;) instead of &lt;code&gt;page.on&lt;/code&gt;, which acts as a shorthand for manually &lt;a href="https://javascript.info/promisify" rel="noopener noreferrer"&gt;promisifying&lt;/a&gt; the &lt;code&gt;page.on&lt;/code&gt; callback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Node.js Memory Leaks with &lt;code&gt;page.on&lt;/code&gt; Listeners
&lt;/h2&gt;

&lt;p&gt;Consider the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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="nf"&gt;setTimeout&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="c1"&gt;// from "timers/promises"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code reinstalls an event listener over and over again, slowly eating memory. The problem seems obvious in this minimal example, but I've seen it buried in the midst of moderately-complex, long-running jobs that eventually crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misunderstanding &lt;code&gt;page.exposeFunction&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pptr.dev/api/puppeteer.page.exposefunction/" rel="noopener noreferrer"&gt;&lt;code&gt;page.exposeFunction&lt;/code&gt;&lt;/a&gt; enables your browser code to trigger Node code. As with &lt;code&gt;evaluate&lt;/code&gt;, you can't pass DOM elements or other non-serializable structures as parameters, so a typical use case is passing serialized data like JSON or text for periodic processing. In the common case, you'll use &lt;code&gt;evaluate&lt;/code&gt; to extract data rather than &lt;code&gt;exposeFunction&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing Too Much Work in Parallel
&lt;/h2&gt;

&lt;p&gt;Another obvious pattern when seen in isolation is:&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;urlsToScrape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="cm"&gt;/* large array */&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;urlsToScrape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;domcontentloaded&lt;/span&gt;&lt;span class="dl"&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;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="s2"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;element&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="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 &lt;code&gt;urlsToScrape&lt;/code&gt; happens to be large enough, the memory and processor load from spawning dozens or hundreds of pages can bring a system down quickly. Consider &lt;a href="https://github.com/thomasdondorf/puppeteer-cluster" rel="noopener noreferrer"&gt;puppeteer-cluster&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;In this article, we've seen a variety of mistakes and gotchas that every Puppeteer programmer should be aware of.&lt;/p&gt;

&lt;p&gt;I hope these tips will save you from making the same mistakes I've made over the years, so you can keep your tests and scripts running smoothly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you liked this post, &lt;a href="https://blog.appsignal.com/javascript-sorcery" rel="noopener noreferrer"&gt;subscribe to our JavaScript Sorcery list&lt;/a&gt; for a monthly deep dive into more magical JavaScript tips and tricks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S. If you need an APM for your Node.js app, go and &lt;a href="https://www.appsignal.com/nodejs" rel="noopener noreferrer"&gt;check out the AppSignal APM for Node.js&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ux</category>
      <category>uidesign</category>
      <category>design</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
