<?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: walrus.ai</title>
    <description>The latest articles on DEV Community by walrus.ai (@walrusai).</description>
    <link>https://dev.to/walrusai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1521%2F054a6abf-9dbd-4916-9d0e-812c7bb591a4.png</url>
      <title>DEV Community: walrus.ai</title>
      <link>https://dev.to/walrusai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/walrusai"/>
    <language>en</language>
    <item>
      <title>Prompting Users to Reload Your Next.js App After an Update</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Mon, 05 Apr 2021 16:37:25 +0000</pubDate>
      <link>https://dev.to/walrusai/prompting-users-to-reload-your-next-js-app-after-an-update-2jpf</link>
      <guid>https://dev.to/walrusai/prompting-users-to-reload-your-next-js-app-after-an-update-2jpf</guid>
      <description>&lt;p&gt;The pages of a &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; application are served in one of two ways: server-side rendering, or client-side rendering. It's important to understand the distinction, and when each scenario occurs. (There is also &lt;a href="https://nextjs.org/docs/advanced-features/automatic-static-optimization" rel="noopener noreferrer"&gt;static generation&lt;/a&gt;, but we will disregard that for this walkthrough.)&lt;/p&gt;

&lt;p&gt;Server-side rendering is when the underlying Node.js server is handling the request, loading the corresponding page component (and any data dependencies), and returning the populated HTML that results. A page will be server-side rendered if it's the initial request to load the page, and the page implements either &lt;a href="https://nextjs.org/docs/api-reference/data-fetching/getInitialProps" rel="noopener noreferrer"&gt;&lt;code&gt;getInitialProps&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering" rel="noopener noreferrer"&gt;&lt;code&gt;getServerSideProps&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Client-side rendering is when the Javascript in the browser has taken over the handling of the request, and React will handle rendering the new page component and reconciling any differences. Client-side rendering occurs when the user has already loaded your application, and is navigating via the Next.js router (either &lt;a href="https://nextjs.org/docs/api-reference/next/router#userouter" rel="noopener noreferrer"&gt;directly&lt;/a&gt;) or via the &lt;a href="https://nextjs.org/docs/api-reference/next/link" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;Link /&amp;gt;&lt;/code&gt;&lt;/a&gt; component.&lt;/p&gt;

&lt;p&gt;The important  caveat with client-side rendering is that once the user has loaded the application and each of the pages, requests are no longer being made to the server to render any of them -- the client is handling it all. This means that if you deploy a new version of your application while someone is using it, they could continue seeing and using the previous version of your application until they happen to reload.&lt;/p&gt;

&lt;p&gt;This can cause issues if you're making breaking changes, or fixing bugs, or making any other changes you'd prefer your users see ASAP. This risk is multiplied by the number of people using your application. So how can you handle new deploys on the client to ensure our users get the latest version?&lt;/p&gt;

&lt;p&gt;Next.js allows &lt;a href="https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config" rel="noopener noreferrer"&gt;customizing the Webpack configuration&lt;/a&gt; used at build time via a &lt;code&gt;next.config.js&lt;/code&gt; file. It will automatically pass in various relevant arguments; the one that we're interested in is &lt;code&gt;buildId&lt;/code&gt;. By default, this is a random string unique to each build.&lt;/p&gt;

&lt;p&gt;Combined with Webpack's &lt;a href="https://webpack.js.org/plugins/define-plugin/" rel="noopener noreferrer"&gt;&lt;code&gt;DefinePlugin&lt;/code&gt;&lt;/a&gt;, you can expose this buildId to our application by replacing any checks for &lt;code&gt;process.env.BUILD_ID&lt;/code&gt; with the real &lt;code&gt;buildId&lt;/code&gt;:&lt;/p&gt;

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

&lt;span class="c1"&gt;// next.config.js&lt;/span&gt;

&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nf"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DefinePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process.env&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;BUILD_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

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


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

&lt;/div&gt;

&lt;p&gt;This means that the resulting bundles served on the client will have the real &lt;code&gt;buildId&lt;/code&gt; available to them when checking &lt;code&gt;process.env.BUILD_ID&lt;/code&gt;. Since these bundles stay loaded as client-side navigation occurs, this will remain a static reference to the &lt;code&gt;buildId&lt;/code&gt; loaded on the client.&lt;/p&gt;

&lt;p&gt;Next, you'll want to also expose this &lt;code&gt;process.env.BUILD_ID&lt;/code&gt; variable in our server-side environment. This is because when you deploy a new version of your application, anything being handled by the server will immediately be operating on the newest version. You can do this via Next.js's &lt;a href="https://nextjs.org/docs/api-routes/introduction" rel="noopener noreferrer"&gt;API routes&lt;/a&gt;:&lt;/p&gt;

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

&lt;span class="c1"&gt;// pages/api/build-id.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;buildId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BUILD_ID&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;With this new endpoint exposing &lt;code&gt;process.env.BUILD_ID&lt;/code&gt; from the server, you have a route we can hit at any time to get the &lt;em&gt;newest&lt;/em&gt; deployed buildId: &lt;code&gt;/api/build-id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since the client will now have a static reference to its own &lt;code&gt;buildId&lt;/code&gt;, and the server now has the endpoint always returning the newest &lt;code&gt;buildId&lt;/code&gt;, we can implement our own polling and diffing to determine if the user needs to reload. Below is a component that encapsulates this logic, polling for the latest &lt;code&gt;buildId&lt;/code&gt; every 30 seconds via a &lt;code&gt;useInterval&lt;/code&gt; hook. This can then be rendered anywhere in your application.&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&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="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;superagent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useInterval&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Function&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;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lead&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&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;savedCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="nf"&gt;useEffect&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;savedCallback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;tick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;savedCallback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;lead&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interval&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&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;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DeployRefreshManager&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;JSX&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="nf"&gt;useInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/build-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BUILD_ID&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;buildId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BUILD_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// There's a new version deployed that we need to load&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="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;At walrus.ai, we display a non-closable modal to the user with just one possible action: reloading the page. Clicking this button simply  calls &lt;code&gt;window.location.reload()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgaaf3cemr5j748frb42x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgaaf3cemr5j748frb42x.png" alt="Screenshot of walrus.ai's new version modal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is achieved by simply setting a boolean state value in the above if statement, and conditionally returning your modal element from the component instead of always returning null.&lt;/p&gt;

&lt;p&gt;You'll now be able to rely on the fact that your users are always using the latest version of your application, or at least being prevented from taking actions until they've reloaded.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Using Google's OCR API with Puppeteer for Visual Testing</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Mon, 08 Feb 2021 23:11:53 +0000</pubDate>
      <link>https://dev.to/walrusai/using-google-s-ocr-api-with-puppeteer-for-visual-testing-42m6</link>
      <guid>https://dev.to/walrusai/using-google-s-ocr-api-with-puppeteer-for-visual-testing-42m6</guid>
      <description>&lt;p&gt;The &lt;a href="https://developers.google.com/web/tools/puppeteer" rel="noopener noreferrer"&gt;Puppeteer framework&lt;/a&gt; is a Node.js library that can be used for automated browser testing. This library provides high-level access to chromium-based browsers through the dev tools protocol. In automated testing, you can use assertion libraries like chai.js or should.js to make assertions about elements or objects that should appear in an application. &lt;/p&gt;

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

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// Iterate through all elements&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

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

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

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

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

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

&lt;p&gt;Let’s take a look at the following image, which contains a dense text block.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg006nx3c42f7419auzbl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg006nx3c42f7419auzbl.png" alt="Text overlaid on an image of a computer with app icons"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;TEXT_DETECTION Output&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn6o4ekultz3btlb1gfw1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn6o4ekultz3btlb1gfw1.png" alt="text detection from a document"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DOCUMENT_TEXT_DETECTION Output&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb1u1a54k3hpx422vkoqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb1u1a54k3hpx422vkoqv.png" alt="text detection from a document"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

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

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

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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

            }]
        });

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

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Captured image (ocr-text-block.png)&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0u0jq90i8o4gm2ntejt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0u0jq90i8o4gm2ntejt8.png" alt="example domain written on a white background"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OCR Result&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhzxrkcj3avjwuwrjq7yk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhzxrkcj3avjwuwrjq7yk.png" alt="terminal output using google ocr api"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



&lt;p&gt;Since the word “permissions” is inside a paragraph tag (&amp;lt;p&amp;gt;), the console output will show a DOM object description attribute like the following.&lt;/p&gt;

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

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

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

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

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

&lt;p&gt;&lt;strong&gt;Image&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxv7ahyfhaeva1hbuqpw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxv7ahyfhaeva1hbuqpw2.png" alt="The words Los Altos on a white background"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OCR Result&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F12qn7bslit0kc45g9gs8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F12qn7bslit0kc45g9gs8.png" alt="OCR result of predicting Los Altos image"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

  &lt;span class="c1"&gt;# Function to Test
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;p&gt;For unit tests, we generally follow best practice of mocking any interactions that are outside the unit. Consider the following function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;changeUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This function takes a handle to database connection, the userId, and a new username, and updates the username in the database. We abstract away the underlying SQL necessary to make the database update with the &lt;a href="https://deviq.com/repository-pattern/"&gt;Repository pattern&lt;/a&gt;. This allows us to test this function rather easily.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changeUserName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should update username in db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;getRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;changeUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;However, what if we want to test our actual Repository? The code for the repository probably looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
        UPDATE users SET username = :username WHERE id = :id
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

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

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

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

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

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

&lt;p&gt;The easiest solution for this would be something like the following. We could spin up a database before each Jest worker, and shut it down after. Since all tests within a worker run serially, each worker could operate on its individual database safely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;beforeWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`db_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JEST_WORKER_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;span class="nx"&gt;afterWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;destroyDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Unfortunately, while Jest exposes the &lt;code&gt;JEST_WORKER_ID&lt;/code&gt; environment variable to distinguish between workers, &lt;a href="https://github.com/facebook/jest/issues/8708"&gt;it doesn't expose any simple way of hooking in per-worker setup and teardown methods&lt;/a&gt;.&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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



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

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

&lt;p&gt;Now, we simply have to connect to the correct database in all of our tests. With the &lt;code&gt;JEST_WORKER_ID&lt;/code&gt; environment variable, this is trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`walrus_test_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JEST_WORKER_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;namingStrategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SnakeNamingStrategy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now all of our workers will use individual databases, allowing us to run tests in parallel.&lt;/p&gt;

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

&lt;p&gt;While parallel tests are no longer problematic, we still have a problem with serial tests. Consider the following example, again of our contrived &lt;code&gt;UserRepository&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserRepository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should create user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

    &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should delete user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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



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

&lt;p&gt;We considered two approaches to solve this problem. The first approach is to wrap each test in a database transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rollbackTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With this approach, any database updates made within the test are wrapped into the initialized transaction. When the test is finished, we simply rollback the transaction, discarding any of those updates. While this approach is fast and generally supported by all databases, it's not always the best for certain classes of integration-tests.&lt;/p&gt;

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

&lt;p&gt;Another, simpler but slower approach, is to just clear out the database after before every test. While this may be slower, it's less likely to bite us later. We can do this in a simple beforeEach block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;createQueryRunner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
      DO
      $func$
      BEGIN
        EXECUTE (
          SELECT 'TRUNCATE TABLE ' || string_agg(oid::regclass::text, ', ') || ' CASCADE'
            FROM pg_class
            WHERE relkind = 'r'
            AND relnamespace = 'public'::regnamespace
        );
      END
      $func$;
    `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

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

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

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

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

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



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpijoeofji50zrv12zjby.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpijoeofji50zrv12zjby.gif" alt="integration test written in plain English"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Writing Resilient End-to-End Tests</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Mon, 20 Apr 2020 18:38:03 +0000</pubDate>
      <link>https://dev.to/walrusai/writing-resilient-end-to-end-tests-mn</link>
      <guid>https://dev.to/walrusai/writing-resilient-end-to-end-tests-mn</guid>
      <description>&lt;h2&gt;
  
  
  What is end-to-end testing?
&lt;/h2&gt;

&lt;p&gt;End-to-end testing is the process of testing your application through replicating the expected full flows and behaviors of an end user. For example, if you wanted to test that an application's signup flow was working, you would use code to automatically navigate and complete the signup flow in the browser just like a user.&lt;/p&gt;

&lt;p&gt;By testing the full functionality of your application in this manner, you're ensuring everything is working as expected from the client, to the API, to the database. It eliminates the seams that occur when unit testing each parts of your application in isolation.&lt;/p&gt;

&lt;p&gt;If it's possible to gain more confidence with end-to-end tests, why don't people do more of it? &lt;strong&gt;Because it's hard&lt;/strong&gt;. One of the biggest issues is resiliency: what happens if you change the text of a button that your test was looking for? What happens if you hit a network spike and something takes longer than expected? Without handling cases like these, your end-to-end tests are likely to start failing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is resiliency important?
&lt;/h2&gt;

&lt;p&gt;In a perfect world, end-to-end tests should be run any time a change is made to your application. This is because the smallest (perceived) change is always capable of causing unforeseen issues throughout the rest of your application. To achieve this, teams will typically execute their end-to-end tests within their CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;If your end-to-end tests are running inside your CI/CD pipeline, that means they're directly intertwined with your deployment process. Any delays or failures will impede (or prevent) the deployment from completing. Change the button text in your signup flow from "Next" to "Continue"? If your tests weren't updated for that change, you're about to hit a deployment failure and lose up to 30 minutes of precious time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a flake, and what causes them?
&lt;/h2&gt;

&lt;p&gt;A "flake" is a (usually) non-deterministic failure caused by something that is either intermittent or unexpected. Due to the nature of end-to-end tests and the many layers involved, a flake can be caused by any number of things. Some of the more common examples are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous delays.&lt;/strong&gt; If submitting a form on your page suddenly takes 5 seconds instead of 2, the page will not be behaving as your tests may expect. Spinners may stick around, buttons may not re-enable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identifier changes.&lt;/strong&gt; As we've mentioned, changing something like the text of a button could cause failures. That's also true of non-user-facing identifiers such as HTML classes or IDs, which can change at any time due to unrelated engineering updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actual application/UX changes.&lt;/strong&gt; In the case that your team has actually carried out a large redesign or refactor of your application, your existing tests are very likely to start failing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Flakes and How to Avoid Them
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Asynchronous Delays
&lt;/h2&gt;

&lt;p&gt;Asynchronous delays, often resulting from network requests, are the largest and broadest category of test flakes. This is because asynchronous requests are happening quite often in an average web application, affecting many other parts of the app.&lt;/p&gt;

&lt;p&gt;Submitting the login form? Waiting for a response from the API. Trying to view a list of data? Waiting for data to be fetched. Just clicked a button? Waiting for the request to finish before updating the UI. You may even have to wait on something like an email being sent and arriving in your test inbox.&lt;/p&gt;

&lt;p&gt;These are all examples of things that most end-to-end tests will be doing often: navigating, filling out forms, clicking elements, waiting for things to happen. And they're all unpredictable!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What can you do?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There are a few things you can do in your end-to-end tests to better handle any form of asynchronous delay. None of these are foolproof, but they should get most of the way to resiliency around these issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Explicitly wait on network requests when possible.&lt;/strong&gt; Many testing libraries provide APIs to hook into the loading status of the browser page. This can be done when you're submitting a form that you know will result in a redirect, or when you're clicking a link. In Puppeteer, for example, they provide &lt;code&gt;page.waitForNavigation()&lt;/code&gt;. Clicking a button to submit a form, then, might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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="nx"&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="nx"&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="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button[type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Wait often to allow for arbitrary load times.&lt;/strong&gt; Even when using a helper like &lt;code&gt;waitForNavigation&lt;/code&gt;, there may be additional delay involved post-request. For example, the application may take a second or two to enable the button you'll be clicking next. In instances like these, it can be helpful to have a simple &lt;code&gt;wait&lt;/code&gt; helper to wait a small amount of hardcoded time before proceeding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Helper&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="c1"&gt;// Usage&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="nx"&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="nx"&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="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button[type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.some-later-rendered-element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Use polling.&lt;/strong&gt; Many testing libraries will provide a method (or its easy to write your own), that "polls" for an element on the screen. This makes your test more resilient to those millisecond differences when finding an element to operate on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Good&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="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;If you're testing some process that can be particularly long and/or unpredictable such as querying a large set of data for a new entry, or receiving an email, it can be helpful to carry out your logic and assertions in a custom polling method. This means you're checking on some defined interval for the assertion (i.e. "my data entry did appear"), with a hardcoded timeout that determines a "failure".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pollUntilTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.some-new-element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;intervalMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;timeoutMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New element not found.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Identifier Changes
&lt;/h2&gt;

&lt;p&gt;Most identifiers or attributes used in testing exist for other purposes. Classes, for example, are primarily used for styling. Element tags are used for DOM layout and specific to element purpose. Any time one of these identifiers is changed for their real purpose (updating a button's styling, changing a button to a link), your tests can break.&lt;/p&gt;

&lt;p&gt;One option is to define and enforce strict rules around naming behaviors and update processes for element identifiers. For example, you could add a new class to every element you'll be using in your test, prefixed with &lt;code&gt;test-&lt;/code&gt;, and ensure those are always kept in sync. However, this can be a painful process to remember and enforce.&lt;/p&gt;

&lt;p&gt;Ideally, you can write your tests to be better prepared for changes to identifiers. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What can you do?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Avoid relying on "internals"&lt;/strong&gt;. Things like &lt;code&gt;class&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; should generally be avoided and assumed to be unstable, as they may change at any time due to internal engineering efforts. Of course there may still be scenarios in which you have no other option, but be prepared for changes to affect your tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input.form--input.some-styling.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Good&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input[type='text'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Use selectors based on actual functionality to the end-user.&lt;/strong&gt; Since the goal of your end-to-end test is to ensure that an end-user is still able to complete some flow, it makes sense that it should be written in the same way a user may look for something. A user of your application isn't going to be looking for the selector &lt;code&gt;button.btn.btn--green&lt;/code&gt; when they want to create a new post, they'll look for a button that contains the words "Create Post".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button.btn.btn--green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Good&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForXPath&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[contains(., 'Create Post')]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Be (reasonably) lenient with your matchers.&lt;/strong&gt; In the example above, we're searching for a button with the text "Create Post". But what if a change is made and it now says "Create New Post"? Barely anything has changed, but your test is going to break. If there's no other conflicts on the page, it can be helpful to loosen your matcher. What if we just search for "Create"?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForXPath&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[contains(., 'Create Post')]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Good&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForXPath&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[contains(., 'Create')]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Real Application/UX Changes
&lt;/h2&gt;

&lt;p&gt;The last major cause of end-to-end test failures are larger instances of what we've already discussed: intentional changes to your application and/or UX. If your signup flow, for example, is completely redesigned from the ground up, your test logic and matchers will likely no longer be correct.&lt;/p&gt;

&lt;p&gt;Although this class of changes is least common, they are by far the most expensive in terms of time to update the applicable end-to-end tests. There's not much you can do in your tests to prepare for large sweeping changes, due to the need to balance non-colliding and specific matchers with the possibility of an entirely different page.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What can you do?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; is a new solution for end-to-end testing with ease. All you provide is a user story written in plain English instructions, and the test is then interpreted and executed by a human and automated for all future runs. Any changes or flakes are handled, and so your tests will only fail when there's a &lt;strong&gt;true&lt;/strong&gt; failure.&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

&lt;p&gt;&lt;strong&gt;Your&lt;/strong&gt; &lt;strong&gt;metrics should measure velocity and highlight chokepoints in your processes&lt;/strong&gt;&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;p&gt;And your tools should make all of that easy. At &lt;a href="http://walrus.ai" rel="noopener noreferrer"&gt;walrus.ai&lt;/a&gt;, we're trying to do just that.&lt;/p&gt;

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

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

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

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

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

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

&lt;h1&gt;
  
  
  Understanding continuous deployment
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fneyffv35bgoqswi1wk72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fneyffv35bgoqswi1wk72.png" alt="Flowchart of the whole ci cd lifecycle"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;p&gt;Try &lt;a href="http://walrus.ai" rel="noopener noreferrer"&gt;walrus.ai&lt;/a&gt; – integration tests in one line of code, in plain English. We handle the rest. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgu38dtozxl6k5zxmw62x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgu38dtozxl6k5zxmw62x.gif" alt="Console running an integration test"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

</description>
      <category>productivity</category>
      <category>mentalhealth</category>
      <category>remote</category>
    </item>
    <item>
      <title>The Five Most Common Bugs you Should be Writing Tests for</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Thu, 31 Oct 2019 17:09:01 +0000</pubDate>
      <link>https://dev.to/walrusai/the-five-most-common-bugs-you-should-be-writing-tests-for-11p2</link>
      <guid>https://dev.to/walrusai/the-five-most-common-bugs-you-should-be-writing-tests-for-11p2</guid>
      <description>&lt;p&gt;Users have higher expectations than ever before. At the same time, the customer experience is &lt;a href="https://www.forbes.com/sites/forbescommunicationscouncil/2018/12/11/why-customer-experience-has-never-been-more-vital/#3dfe634e7b7b"&gt;increasingly the top priority&lt;/a&gt; because of its far-reaching implications with respect to customer activation and retention. &lt;/p&gt;

&lt;p&gt;The easiest way to kill a good customer experience is with a bad bug. That's where tests come in. &lt;/p&gt;

&lt;p&gt;Tests are instrumental to delivering a good customer experience – they spot bugs before the customer ever has a chance to. &lt;/p&gt;

&lt;p&gt;Some technologies like static typing and linting can help get you part of the way there, but ultimately, you should be writing tests to ensure your business logic and the corresponding customer experience work as you'd expect them to.  &lt;/p&gt;

&lt;p&gt;At &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt;, we help teams automate their most annoying integration tests. We've outlined some of the most frequent bugs that our customers have experienced that you can write simple tests to prevent. &lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;What are the most common bugs you should be writing tests for?&lt;/strong&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Unresponsive interactive elements&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Unresponsive elements are deadly. Often they manifest themselves in onboarding experiences (continue buttons, input forms, etc.), and can prevent the customer from taking a valuable action, such as signing up, or in the instance below, getting a car loan! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Df2zQ6CZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/tuag3i86l7etsjsqg1i6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Df2zQ6CZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/tuag3i86l7etsjsqg1i6.gif" alt="A gif of an unresponsive slider on a car website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Form validation&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Forms are where you collect the most important business data from your customers. Whether it's their credit card information, or information that will be used to engage the customer (such as email and phone number, the quality of this data is certainly important to the business, and can have a meaningful impact on the customer experience (for example, when you are scheduling a demo and enter an incorrect phone number!). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eIUeaXHZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2ezby8aa989ccnto036x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eIUeaXHZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2ezby8aa989ccnto036x.gif" alt="A gif of someone entering 415 into a phone number field"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Asynchronous data&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;User actions that have asynchronous dependencies are particularly prone to bugs, and can have significant implications for the business. For example, if you don't evaluate whether someone has previously claimed a promotion code fast enough, your system provides a window for coupon fraud. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RrsMo8Gr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pam0ipagoe7j0zx8ydht.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RrsMo8Gr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pam0ipagoe7j0zx8ydht.gif" alt="Gif of a promo code being applied twice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Empty data&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Empty data errors occur when users expect certain results (like search results, or data represented in a report), but the frontend doesn't render any real results, despite the data existing. These bugs are particularly frustrating for the user, because it isn't even clear that an error is occurring. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mT9bO03h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/1c2s8c3kgn29r1fb5f68.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mT9bO03h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/1c2s8c3kgn29r1fb5f68.png" alt="An image of an empty report"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Multiple popups or modals&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Modals are a great way to engage users — whether it's introducing them to a new feature in your product, or merchandising a discount. But not both at the same time.&lt;/p&gt;

&lt;p&gt;Not only are overlapping modals problematic &lt;a href="https://www.w3.org/TR/wai-aria-practices/#dialog_modal"&gt;with respect to ADA compliance&lt;/a&gt;, they also dilute the call-to-action for the end-user, and generally make your product look untrustworthy.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V4SD79g3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wupr7be4mjs9sslxnl0w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V4SD79g3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/wupr7be4mjs9sslxnl0w.gif" alt="A gif of many popups being stacked on top of each other"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Writing tests sucks. How can I make it painless?
&lt;/h1&gt;

&lt;p&gt;It's hard to write tests because computers don't think like humans do. &lt;/p&gt;

&lt;p&gt;A human thinks about what the user is trying to accomplish in a particular experience (say, checkout), and wants to make sure that the user can successfully accomplish that task. But computers require much more specific instructions — make sure this button is green and contains specific text, for example. Writing tests requires humans to think like computers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But there are new ways of writing tests!&lt;/strong&gt; &lt;/p&gt;

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

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

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

</description>
      <category>testing</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why you suck at estimating timelines, and what you can do about it</title>
      <dc:creator>Scott White</dc:creator>
      <pubDate>Wed, 30 Oct 2019 17:08:01 +0000</pubDate>
      <link>https://dev.to/walrusai/why-you-suck-at-estimating-timelines-and-what-you-can-do-about-it-m1e</link>
      <guid>https://dev.to/walrusai/why-you-suck-at-estimating-timelines-and-what-you-can-do-about-it-m1e</guid>
      <description>&lt;p&gt;When it was announced, the Sydney opera house had a target completion year of 1963, and a budget of $7 million. &lt;/p&gt;

&lt;p&gt;Exactly a decade later (1973), and ~$100 million over budget (14x the original estimate!), the Sydney Opera house opened its doors. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Humans are &lt;a href="https://en.wikipedia.org/wiki/Planning_fallacy"&gt;predictably terrible&lt;/a&gt; at estimating timelines.&lt;/strong&gt; We're optimistic, we disregard our past experiences, and we consistently fail to grasp the intricacies of the tasks we need to accomplish. &lt;/p&gt;

&lt;p&gt;This phenomenon has a name –– the planning fallacy. &lt;/p&gt;

&lt;p&gt;The reach of the planning fallacy goes well beyond construction projects. It affects the projects we do at work, how we are evaluated, and ultimately whether we successfully accomplish our goals. &lt;/p&gt;

&lt;p&gt;The predictability of the planning fallacy is its biggest weakness. It's root causes are well-understood, and by taking a structured approach to timeline planning, you can overcome the planning fallacy.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Why does the planning fallacy happen?&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;The Planning Fallacy was originally proposed by famed behavioral economics Daniel Kahnmenan and Amos Tversky in 1979, and has been closely studied ever since. Researchers have coalesced on a consistent set of findings. &lt;/p&gt;

&lt;h3&gt;
  
  
  🌏  &lt;strong&gt;Humans think broadly&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A simple task like cleaning the kitchen is comprised of multiple smaller tasks: washing the pots and pans, unloading and filling the dishwasher, cleaning the stove, taking out the trash — the list goes on. If you simply think about the one large task, it's easy to assume it will be easy to tackle. Once you enumerate exactly what that task entails, you begin to see how it would take longer than you expect. &lt;/p&gt;

&lt;h3&gt;
  
  
  🔀  &lt;strong&gt;Humans are bad at managing dependencies&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Determining the interdependency of complex projects &lt;a href="https://www.cc.gatech.edu/~beki/t1.pdf"&gt;does not come naturally to us&lt;/a&gt;. We gravitate to what is easiest for us, not to what will unblock or accelerate the larger project. &lt;/p&gt;

&lt;h3&gt;
  
  
  🌟  &lt;strong&gt;Humans are optimistic&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Humans love the happy path. To help us complete the task at hand, we think about the desired end state. The natural consequence of this optimism is two-fold: we think tasks will be easier than they actually are, and we don't plan for failure. &lt;/p&gt;

&lt;p&gt;Given that projects contain a great deal of complexity and uncertainty, involve multiple people, and carry a large cost in the case of failure, it's completely understandable that we struggle with estimating how long they will take. &lt;/p&gt;

&lt;p&gt;Luckily, there's a way to conquer the planning fallacy. &lt;/p&gt;

&lt;h1&gt;
  
  
  How to come up with a timeline that conquers the planning fallacy
&lt;/h1&gt;

&lt;p&gt;Once we truly understand what leads to the Planning Fallacy, we can take steps to avoid those root-causes in our planning process.&lt;/p&gt;

&lt;p&gt;Below are the steps you can take to ensure your timeline is realistic, and doesn't become a victim of our flawed intuition. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Get granular&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As we've discussed, a seemingly simple task can be comprised of several harder-than-expected subtasks.&lt;/p&gt;

&lt;p&gt;The smaller the task, the easier it is to estimate. &lt;/p&gt;

&lt;p&gt;When determining a project timeline, break up the initiative to its smallest constituent elements, and estimate those instead. Once you have all of them, add them together to get the larger timeline. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start broad. What are the biggest separable parts of of the project.&lt;/li&gt;
&lt;li&gt;Within each of those, identify the user-specific interactions. In other words, put yourself in the shoes of a user to determine what they're seeing that you need to build.&lt;/li&gt;
&lt;li&gt;Within each user-story, break down exactly what needs to be done to deliver the experience. In the end, these should ideally be tasks that will be assigned to specific individuals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vPWaynwk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/1w8upevtakjlnv2ln843.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vPWaynwk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/1w8upevtakjlnv2ln843.png" alt="breaking down a project to subtasks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have enumerated the subtasks that comprise the entire project, estimate how long each of those subtasks will take. &lt;/p&gt;

&lt;p&gt;Then just add them up, and you have the overall number of days a hypothetical individual would take to complete the project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Determine your time budget
&lt;/h2&gt;

&lt;p&gt;Once you have the amount of time your project will take, it's time to figure out how much time &lt;strong&gt;&lt;em&gt;you actually have&lt;/em&gt;&lt;/strong&gt; with your current level of staffing. &lt;/p&gt;

&lt;p&gt;To determine your time budget, the first step is identifying who is working on the project, and how much time he/she has allocated to it specifically. &lt;/p&gt;

&lt;p&gt;In an ideal world, he/she is working on the project 8 hours a day, 5 days a week. But between onboarding new employees, vacation time, holidays, and other time out of the office, the realistic time budget looks much different than the real time budget.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QUHYt29c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vi88di5x10j113oezx89.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QUHYt29c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vi88di5x10j113oezx89.png" alt="time budget spreadsheet"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have your time budget, you can determine who should work on which specific tasks. &lt;/p&gt;

&lt;p&gt;But before you can do that, you need to determine the order of the tasks you need to accomplish. &lt;/p&gt;

&lt;h2&gt;
  
  
  Demystify dependencies
&lt;/h2&gt;

&lt;p&gt;Understanding how the tasks that comprise your project are dependent on each other is a prerequisite to determining the order in which you'll complete those tasks, and therefore who is working on them. &lt;/p&gt;

&lt;p&gt;To determine the order of the tasks that you'll need to accomplish, go through the entire list and identify which tasks need to be completed to start work on the other tasks. Go task-by-task and identify if it depends on another task or is a dependency on another task. &lt;/p&gt;

&lt;p&gt;Once you understand those relationships, make a graph to determine if there are any choke-points or risky tasks. Once you understand these relationships, you can determine how to tackle them in sequence with the staffing you have available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a03L0YBk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/sa1zn2dt481rfqz2mo4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a03L0YBk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/sa1zn2dt481rfqz2mo4z.png" alt="Project dependencies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HINT:&lt;/strong&gt; A good tool to graph dependencies of tasks is &lt;a href="https://rich-iannone.github.io/DiagrammeR/"&gt;DiagremmeR&lt;/a&gt;, which can be run in R. &lt;/p&gt;

&lt;h2&gt;
  
  
  Plan with failure in mind
&lt;/h2&gt;

&lt;p&gt;As we've discussed, humans are optimistic, and would prefer to visualize the happy path. Unfortunately, the happy path is often the path less trodden, so it's incumbent upon you to plan with failure in mind. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In your time budget, account for lost time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Few people spend their entire work week on whatever project they are working on. Between ramp-up, vacation time, bugs, and holidays, most engineers are working at less than 75% capacity on a given project. Explicitly account for this lost time in your timeline to provide a more realistic estimate. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add testing to the project plan&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a perfect world, everything we build would behave as expected. &lt;/p&gt;

&lt;p&gt;Unfortunately, we don't live in a perfect world. Building a resilient infrastructure to identify and resolve bugs is a costly but worthwhile investment, and ensures that your product offers a good user experience. &lt;/p&gt;

&lt;p&gt;As part of your project, carve out specific time for your developers to write the tests that will ensure your project behaves as you'd expect. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to spend less time writing these tests?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Try &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; – an API that lets you write integration tests in plain english, with one line of code. Your team can spend more time writing the code that your customers will see, and less time working on tests. Want to learn more? &lt;a href="http://walrus.ai"&gt;Get your first test results in five minutes&lt;/a&gt;. &lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Putting it all together&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Humans are bad at creating timelines, but at least we're predictably bad. By adopting a process that addresses the root-causes of the Planning Fallacy, we can begin to estimate projects with more accuracy. &lt;/p&gt;

&lt;p&gt;In the end, creating a better timeline comes down to a few simple steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify the tasks that comprise your project to the granularity of what an individual would work on in a given day, and estimate how long each of those tasks will take. Be prepared for the unhappy path, and add tasks like writing integration tests into the plan. &lt;/li&gt;
&lt;li&gt;Determine the budget of time you have available per person who is working on your project&lt;/li&gt;
&lt;li&gt;Outline the dependencies of the tasks that comprise your project to determine the order you need to complete them&lt;/li&gt;
&lt;li&gt;Based on the availability in your time budget, identify who should work on each task &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Than you just have to follow the plan. &lt;/p&gt;

&lt;h1&gt;
  
  
  Planning for failure, but want to move fast?
&lt;/h1&gt;

&lt;p&gt;Spend less time writing tests to ensure your product works as expected.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="http://walrus.ai"&gt;Try it for free&lt;/a&gt;, and get your test results within 5 minutes.&lt;/p&gt;

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

</description>
      <category>productivity</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
