<?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>Forem: Dmitry Sheiko</title>
    <description>The latest articles on Forem by Dmitry Sheiko (@dsheiko).</description>
    <link>https://forem.com/dsheiko</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F192419%2F99ffb828-b481-422a-958c-29d83ebd4040.jpeg</url>
      <title>Forem: Dmitry Sheiko</title>
      <link>https://forem.com/dsheiko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/dsheiko"/>
    <language>en</language>
    <item>
      <title>Optimizing End-to-End Testing with Playwright: Best Practices for Speed &amp; Reliability</title>
      <dc:creator>Dmitry Sheiko</dc:creator>
      <pubDate>Wed, 26 Mar 2025 11:23:24 +0000</pubDate>
      <link>https://forem.com/dsheiko/optimizing-end-to-end-testing-with-playwright-best-practices-for-speed-reliability-3d68</link>
      <guid>https://forem.com/dsheiko/optimizing-end-to-end-testing-with-playwright-best-practices-for-speed-reliability-3d68</guid>
      <description>&lt;p&gt;End-to-end tests are crucial but can become slow and flaky if not optimized. This article explores practical strategies to enhance Playwright tests, covering parallelization, network stubbing, test isolation, and debugging techniques. Whether you're a developer or QA engineer, these insights will help you improve test efficiency and reliability.&lt;/p&gt;

&lt;p&gt;🔗 Read more: &lt;a href="https://dsheiko.com/weblog/optimizing-end-to-end-testing-with-playwright" rel="noopener noreferrer"&gt;https://dsheiko.com/weblog/optimizing-end-to-end-testing-with-playwright&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Playwright #Testing #QA #DevOps #WebDev
&lt;/h1&gt;

</description>
      <category>playwright</category>
      <category>testing</category>
      <category>qa</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Puppetry 3 : test automation without coding</title>
      <dc:creator>Dmitry Sheiko</dc:creator>
      <pubDate>Wed, 27 Nov 2019 11:52:35 +0000</pubDate>
      <link>https://forem.com/dsheiko/puppetry-3-test-automation-for-everybody-f82</link>
      <guid>https://forem.com/dsheiko/puppetry-3-test-automation-for-everybody-f82</guid>
      <description>&lt;p&gt;Nowadays nobody would argue the importance of automated testing. Yet end-to-end tests are often hard to write and even harder to maintain. There are many solutions to help with it. Puppetry is a test constructor, which allows you building test suites without any coding. QA-engineer can record user scenario in a built-in browser, extend the generated test case with browser commands and assertions, manage the suite structure (like drag’n’drop) and run tests. Puppetry translates Gherkins-styled test specification into a Jest/Puppeteer project, executes it and shows the report. This project can be simply plugged in CI/CD pipeline. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2z-sXrsy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/main.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2z-sXrsy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/main.png" alt="Test case"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zy0AOF9r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/article-report-ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zy0AOF9r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/article-report-ok.png" alt="Test report"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may think of it as an advanced UI over Puppeteer with more than 60 visualized methods and assertions.  These are designed to make the test development as easy as possible. For example, with Puppetry you say “I assert that target FOO is currently located above BAR”. It results in the code computing styles of both targets and comparing them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pet2wJiV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/article-relative-position.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pet2wJiV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/article-relative-position.png" alt="Asserting element position"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It may sound as play-toy, but in reality we can implement with Puppetry quite sophisticated test strategies. For example we can address &lt;a href="https://docs.puppetry.app/testing-techniques/testing-dynamic-content"&gt;dynamic content&lt;/a&gt;, run &lt;a href="https://docs.puppetry.app/testing-techniques/performance-testing"&gt;performance budgeting&lt;/a&gt;, test &lt;a href="https://docs.puppetry.app/testing-techniques/css-regression-testing"&gt;CSS regression&lt;/a&gt;, &lt;a href="https://docs.puppetry.app/testing-techniques/testing-chrome-extensions"&gt;Chrome extensions&lt;/a&gt;, &lt;a href="https://docs.puppetry.app/testing-techniques/testing-shadow-dom"&gt;web-components&lt;/a&gt;, &lt;a href="https://docs.puppetry.app/testing-techniques/testing-emails"&gt;transactional emails&lt;/a&gt; and &lt;a href="https://docs.puppetry.app/testing-techniques/testing-google-analytics-tracking-code"&gt;Google Analytics tracking&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RxBnfeBT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/article-css-regression.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RxBnfeBT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/article-css-regression.png" alt="CSS Regression test report"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Eun0YN6I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/article-ga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Eun0YN6I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/puppetry/devto-271119/article-ga.png" alt="Asserting Google Analytics tracking"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Real-world test cases can be hard to debug. Modern web applications are highly dynamic, meaning the pages are changing in response to user actions. So we have to remember when addressing an element that it can be not yet rendered or already destroyed in that particular point in time. Luckily Puppetry let use set breakpoints as well as run test in interactive mode. In both cases the test run pause when reaching the problem point, so you can observe the actual page state and call DevTools to check the DOM tree. &lt;/p&gt;

&lt;p&gt;Yeah, Puppetry is where the test automation makes fun. Not persuaded? Just check the video out.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/dfuNhTCRMRg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download links &lt;a href="https://github.com/dsheiko/puppetry/releases"&gt;https://github.com/dsheiko/puppetry/releases&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Official site &lt;a href="https://puppetry.app/"&gt;https://puppetry.app/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to create automated functional tests in no time</title>
      <dc:creator>Dmitry Sheiko</dc:creator>
      <pubDate>Fri, 26 Jul 2019 09:24:56 +0000</pubDate>
      <link>https://forem.com/dsheiko/how-to-create-automated-functional-tests-in-no-time-28mf</link>
      <guid>https://forem.com/dsheiko/how-to-create-automated-functional-tests-in-no-time-28mf</guid>
      <description>&lt;p&gt;A brief tutorial showing how to record a simple test suite.&lt;/p&gt;

&lt;p&gt;Puppetry is an open-source desktop application that gives non-developers the ability to create, manage, and integrate automated tests for Web&lt;br&gt;
puppetry.app/&lt;/p&gt;

&lt;p&gt;Credits:&lt;br&gt;
Pacific Sun by Nicolai Heidlas available under Attribution Creative Commons 4.0 license Royalty Free Music from HookSounds (hooksounds.com)&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to create a kick-ass image preview with LQIP</title>
      <dc:creator>Dmitry Sheiko</dc:creator>
      <pubDate>Thu, 11 Jul 2019 13:40:09 +0000</pubDate>
      <link>https://forem.com/dsheiko/how-to-create-a-kick-ass-image-preview-with-lqip-3e6a</link>
      <guid>https://forem.com/dsheiko/how-to-create-a-kick-ass-image-preview-with-lqip-3e6a</guid>
      <description>&lt;p&gt;Images in HTML, what could be easier? However when you have many of them on a page, they do not appear immediately. That depends on caching strategy and bandwidth, but still if you don’t take a special care it may look quite ugly. Basically we need to fill in the slots with something appropriate while images are loading. In other words we need placeholders. Probably the most prominent technique here is LQIP (&lt;a href="https://www.guypo.com/introducing-lqip-low-quality-image-placeholders" rel="noopener noreferrer"&gt;low quality image placeholder&lt;/a&gt;). It was adopted by Google, Facebook, Pinterest, Medium and others. The idea is to load page initially with low quality images and once the page is fully loaded replace them with full quality ones. As placeholder one can use embedded transparent SVG, spinner animated image, solid color, blurred and minified original image. But even more, with modern tools we can do something really fancy. For example, we can use images shape or silhouette as a placeholder. Moreover, we can generate Data-URLs with desired effect during the build and address from IMG tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basics
&lt;/h2&gt;

&lt;p&gt;Let’s get there step by step. First we come back to the basics. HTML IMG tag didn’t change much for last 30 years:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Lorem ipsum"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./img/test.jpg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yet , we have now &lt;code&gt;srcset&lt;/code&gt; attribute to tackle responsive web design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"./img/test-1x.jpg 1x,
                 ./img/test-2x.jpg 2x"&lt;/span&gt;
         &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./img/test.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Lorem ipsum"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we enlist image sources per display density (1x, 2x). Thus browser will load double sized version (test-2x.jpg) on Retina devices. Or we can be more specific:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"./img/test-320w.jpg 320w,
                 ./img/test-480w.jpg 480w,
                 ./img/test-800w.jpg 800w"&lt;/span&gt;
         &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./img/test.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Lorem ipsum"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we specify image source width (320w, 480w, 800w) and browser will use that information to pick the most suited source. Note that we still use &lt;code&gt;src&lt;/code&gt; attribute to specify fallback source, which will be used by legacy browsers.&lt;/p&gt;

&lt;p&gt;Now, to the point. What can we do to beautify image loading? The simplest thing is to add an animated spinner as background for image slots. So while image are loading we see the animation. As the loading completed, we see the images covering the background.&lt;/p&gt;

&lt;p&gt;But what if some images fail to load? Diverse browsers render “broken” images differently, but equally awful. To fix it you can target some of them with CSS. However, most universal way, I assume, is to use JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;img:not(.is-processed)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;img&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;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-processed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&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="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lazysizes
&lt;/h2&gt;

&lt;p&gt;Alternatively we can go with a loader library &lt;a href="https://github.com/aFarkas/lazysizes" rel="noopener noreferrer"&gt;Lazysizes&lt;/a&gt; to achieve better perceived performance. It unlocks new options. For example, we can achieve empty image placeholder like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;    
      &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./img/test-fallback.jpg"&lt;/span&gt;
      &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="&lt;/span&gt;
        &lt;span class="na"&gt;data-srcset=&lt;/span&gt;&lt;span class="s"&gt;"./img/test-320w.jpg 320w,
            ./img/test-480w.jpg 480w,
            ./img/test-800w.jpg 800w"&lt;/span&gt;
        &lt;span class="na"&gt;data-sizes=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"lazyload"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus the browser will show the embedded placeholder (transparent or low quality image) until it loads an image corresponding to the viewport from &lt;code&gt;data-srcset&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lazysizes adds &lt;code&gt;lazyloaded&lt;/code&gt; CSS class to image element on &lt;code&gt;load&lt;/code&gt; event and that we can use, for an instance, to implement blur-up placeholder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nc"&gt;.blur-up&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;-webkit-filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="m"&gt;400ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;-webkit-filter&lt;/span&gt; &lt;span class="m"&gt;400ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nc"&gt;.blur-up.lazyloaded&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;-webkit-filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./img/test-lqip.jpg"&lt;/span&gt; &lt;span class="na"&gt;data-src=&lt;/span&gt;&lt;span class="s"&gt;"./img/test.jpg"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"lazyload blur-up"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the low quality image (test-lqip.jpg) will be blurred until the original image (test.jpg) loaded.&lt;/p&gt;

&lt;p&gt;In the article &lt;a href="https://www.freecodecamp.org/news/using-svg-as-placeholders-more-image-loading-techniques-bed1b810ab2c/" rel="noopener noreferrer"&gt;How to use SVG as a Placeholder, and Other Image Loading Techniques&lt;/a&gt; you can find insights of LQIP techniques with drawing effect, based on shapes and silhouettes. Why not put it in practice? So we have to generate a low-quality image, precisely, a Data-URL with SVGO and specify it in &lt;code&gt;src&lt;/code&gt; or &lt;code&gt;srcset&lt;/code&gt; attribute of IMG tag, while full-quality image sources we set in &lt;code&gt;data-srcset&lt;/code&gt;, pretty much as we examined above. The most convenient way to achieve it would be with &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt;. The tool transforms imported images during the build. So we can refer the result (e.g. generated SVGO) straight in the application code. Let’s see in practice.&lt;/p&gt;

&lt;p&gt;First, we install dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    npm i -S lazysizes react react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see we are going to use Lazysizes library and React.js.&lt;/p&gt;

&lt;p&gt;Now it’s the turn to install developer dependencies. We start with babel packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    npm i -D @babel/cli @babel/core @babel/node @babel/preset-env @babel/preset-react babel-loader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then go Webpack ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    npm i -D webpack webpack-cli clean-webpack-plugin   file-loader image-webpack-loader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/webpack-contrib/file-loader" rel="noopener noreferrer"&gt;file-loader&lt;/a&gt; plugin makes Webpack resolving imports of images and &lt;a href="https://github.com/tcoopman/image-webpack-loader" rel="noopener noreferrer"&gt;image-webpack-loader&lt;/a&gt; optimizes imported&lt;/p&gt;

&lt;p&gt;As we have dependencies, we can create &lt;a href="https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/webpack.base.js" rel="noopener noreferrer"&gt;base webpack configuration&lt;/a&gt; for React.js/Babel application. We put in &lt;code&gt;src/img&lt;/code&gt; test-1x.jpg and double-sized test-2x.jpg demo images and to &lt;code&gt;src/index.jsx&lt;/code&gt; the entry script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom&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;Image&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./component/Image&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lazysizes&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;productImg1x&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./img/test-1x.jpg&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;productImg2x&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./img/test-2x.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;
          &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productImg1x&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;srcSet&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;productImg1x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;productImg2x&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"A farm"&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app&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;Here we load lazysizes library, importing out both images and passing them to Image component. The &lt;a href="https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/index.html" rel="noopener noreferrer"&gt;HTML file&lt;/a&gt; may look like that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"build/index.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Silhouette
&lt;/h2&gt;

&lt;p&gt;Silhouette placeholder we can generate with &lt;a href="https://github.com/EmilTholin/image-trace-loader" rel="noopener noreferrer"&gt;image-trace-loader&lt;/a&gt; . The plugin extracts image outlines and returns them as SVGO.&lt;/p&gt;

&lt;p&gt;We have to extend our Webpack configuration with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module: {
  rules: [
    {
      test: /\.(gif|png|jpe?g)$/i,
      use: [
        {
          loader: "image-trace-loader"
        },
        {
          loader: "file-loader",
          options: {
            name: "src-[name].[ext]"
          }
        },
        {
          loader: "image-webpack-loader",
          options: {
            bypassOnDebug: true, // webpack@1.x
            disable: true // webpack@2.x and newer
          }
        }
      ]
    }
  }
]    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in the code we can receive imported images as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trace&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;./image.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where trace is generated SVGO Data-URL and src the full-quality image. It gives us the following Image component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/src/component/Image.jsx" rel="noopener noreferrer"&gt;src/component/Image.jsx&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;srcSet&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt;
          &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"lazyload"&lt;/span&gt;
          &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;data-srcset&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;srcSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inx&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;inx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;x`&lt;/span&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;data-sizes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt;
          &lt;span class="p"&gt;/&amp;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 we run Webpack and get the following loading frames:&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/http%3A%2F%2Fdsheiko.com%2Fdownload%2F000000293%2Fsilhouette.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/http%3A%2F%2Fdsheiko.com%2Fdownload%2F000000293%2Fsilhouette.gif" title="SQIP with silhouette" alt="SQIP with silhouette"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Shape
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/EmilTholin/sqip-loader" rel="noopener noreferrer"&gt;Sqip-loader&lt;/a&gt; split a given picture in arbitrary number of primitive shapes as as triangles, rectangles, ellipses, circles, polygons and others.&lt;/p&gt;

&lt;p&gt;For shape-based placeholder in Webpack configuration the loader rule may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  loader: "sqip-loader",
  options: {
    numberOfPrimitives: 20,
    mode: 1,
    blur: 0
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we require 20 triangle-based shapes and no blur. That makes image imports available in the code as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;preview&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;./image.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where preview is generated SVGO Data-URL and src the full-quality image. So we have to modify &lt;a href="https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/src/component/Image.jsx" rel="noopener noreferrer"&gt;src/component/Image.jsx&lt;/a&gt;. Instead of { placeholder.trace } we go with { placeholder.preview }.&lt;/p&gt;

&lt;p&gt;Well, let’s run Webpack and check the page in the browser:&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/http%3A%2F%2Fdsheiko.com%2Fdownload%2F000000293%2Fshapes.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/http%3A%2F%2Fdsheiko.com%2Fdownload%2F000000293%2Fshapes.gif" title="SQIP with shapes" alt="SQIP with shapes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Blur up
&lt;/h2&gt;

&lt;p&gt;This technique is often refereed as SQIP. While image load we see a blurred low quality placeholders similar to how it works on &lt;a href="https://medium.com/@jmperezperez/how-medium-does-progressive-image-loading-fd1e4dc1ee3d" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;. The placeholders can be also generated by &lt;a href="https://github.com/EmilTholin/sqip-loader" rel="noopener noreferrer"&gt;Sqip-loader&lt;/a&gt;. However this time we are going to set blur:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  loader: "sqip-loader",
  options: {
    numberOfPrimitives: 20,
    mode: 1,
    blur: 30
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result looks so:&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/http%3A%2F%2Fdsheiko.com%2Fdownload%2F%2F000000293%2Fblurup.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/http%3A%2F%2Fdsheiko.com%2Fdownload%2F%2F000000293%2Fblurup.gif" title="SQIP with blurup" alt="SQIP with blurup"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;We we brushed up on &lt;code&gt;src&lt;/code&gt; and &lt;code&gt;srcset&lt;/code&gt; image attributes. We learnt how to use them together with their data-counterparts and Lazysizes library to take advantage of LQIP technique. We set up Webpack and a simple React.js example to fiddle with three SQIP approaches: silhouette, shapes and blurup.&lt;/p&gt;

&lt;p&gt;The full code source of the example can be found here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/" rel="noopener noreferrer"&gt;https://github.com/dsheiko/boilerplate/blob/master/webpack-sqip/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>design</category>
      <category>webdev</category>
      <category>ux</category>
      <category>react</category>
    </item>
    <item>
      <title>Testing signup flow with activation by email</title>
      <dc:creator>Dmitry Sheiko</dc:creator>
      <pubDate>Tue, 09 Jul 2019 10:21:00 +0000</pubDate>
      <link>https://forem.com/dsheiko/testing-signup-flow-with-activation-by-email-h2m</link>
      <guid>https://forem.com/dsheiko/testing-signup-flow-with-activation-by-email-h2m</guid>
      <description>&lt;p&gt;Functional testing isn’t something new. We all do it, less or more, with different tools and approaches. However when it comes to flows, where transactional emails (signup confirmations, password resets, purchase notifications and others) involved that may still bring questions. For example, we instruct the testing tool to navigate to the registration page, fill out the form and press the submit button. The web-application sends email with activation link. So we need the testing tool to read the email message, parse it and navigate the link. The first challenge is to connect the testing tool with the mail server. It’s not a big deal if your mail server exposes a REST API. Otherwise you need to consider a specialized service such as &lt;a href="https://sendgrid.com/"&gt;Sendgrid&lt;/a&gt;, &lt;a href="https://www.mailgun.com/"&gt;Mailgun&lt;/a&gt;, &lt;a href="http://www.emailyak.com/"&gt;Email Yak&lt;/a&gt;, &lt;a href="http://www.postmarkapp.com/"&gt;Postmark&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mail Server API
&lt;/h2&gt;

&lt;p&gt;To say truth, it can be also achieved with &lt;a href="http://restmail.net/"&gt;Restmail.net&lt;/a&gt;. It's free, it's requires no registration, it allows to create dynamically inboxes, it exposes a REST API to read received emails. However all the sent messages are public. The REST API is dead simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    GET /mail/&amp;lt;user&amp;gt;
    DELETE /mail/&amp;lt;user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So you can send an email to, let’s say, &lt;code&gt;joe1@restmail.net&lt;/code&gt; and receive its contents with &lt;code&gt;GET /mail/joe1&lt;/code&gt;. Naturally you can delete it afterwards with &lt;code&gt;DELETE /mail/joe1&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Polling Inbox
&lt;/h2&gt;

&lt;p&gt;Well, but how we can use it in test cases? We need a function, which polls mail server API for inbox updates. The function shall find the email messages sent during the testing session, parse the action link and return it for testing methods. I suggest the following implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     function pollForValue({ url, interval, timeout, parserFn, parserPayload = {}, requestFn = null }) {
        const request = requestFn ? requestFn : async ( url ) =&amp;gt; {
          const rsp = await fetch( url );
          if ( rsp.status &amp;lt; 200 || rsp.status &amp;gt;= 300  ) {
            return {};
          }
          return await rsp.json();
        };

        return new Promise(( resolve, reject ) =&amp;gt; {
          const startTime = Date.now();
          pollForValue.attempts = 0;

          async function attempt() {
            if ( Date.now() - startTime &amp;gt; timeout ) {
              return reject( new Error( `Polling: Exceeded timeout of ${ timeout }ms` ) );
            }
            const value = parserFn( await request( url ), parserPayload );
            pollForValue.attempts ++;
            if ( !value ) {
              return setTimeout( attempt, interval );
            }
            resolve( value );
          }
          attempt();

        });
     }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you call the function it polls a given URL until message(s) received or timeout. It returns the parsed value (e.g. activation link) and accepts an options object with the following properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;url&lt;/code&gt; – REST API resource. Here &lt;a href="http://restmail.net/mail/&amp;lt;user"&gt;http://restmail.net/mail/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;interval&lt;/code&gt; – interval between polling requests in ms&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;timeout&lt;/code&gt; – maximal allowed time span for the function to loop in ms&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;parserFn&lt;/code&gt; – callback that receives the REST API response and parses it for the desired value. The pollForValue function will poll the provided URL until parserFn returns a truthy value (or timeout)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;requestFn&lt;/code&gt; – (OPTIONAL) a callback to replace default window.fetch&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;parserPayload&lt;/code&gt; - (OPTIONAL) extra payload for parserFn callback&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test Application
&lt;/h2&gt;

&lt;p&gt;So we have mail server API and polling function. Next, we going to try it in diverse testing tools. For that we will need a real world example. Imagine, we are testing ACME forum application built with NodeBB. Our goal is to fill out the registration form (&lt;a href="http://localhost:4567/register"&gt;http://localhost:4567/register&lt;/a&gt;) and submit it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p3xaP-RX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/example-app-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p3xaP-RX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/example-app-1.png" alt="ACME forum registration form" title="ACME forum registration form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It brings us to the next page where we have tick on the GDPR checkboxes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p3xaP-RX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/example-app-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p3xaP-RX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/example-app-1.png" alt="ACME forum GDPR form" title="ACME forum GDPR form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the form submitted the application sends confirmation email. Here we go with &lt;code&gt;pollForValue&lt;/code&gt; function. We call it to poll the REST API until the email message arrived. The function will use the following parsing logic to get the activation link from NodeBB default email template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    function parseActivationLink( text ) {
        const re = /(http\:[^\"]+4567\/con[^\"]+)/g,
              res = text.match( re );
        return res ? res[ 0 ].replace( "=\r\n", "" ) : null;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Thus we obtain the activation URL, which we follow to complete the registration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with Selenium WebDriver
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/SeleniumHQ/selenium"&gt;Selenium WebDriver&lt;/a&gt; is probably the most popular testing tool. Not the most effortless, I would say, but still, it’s definetelly one you’ve heard about. So we setup the &lt;a href="https://www.npmjs.com/package/selenium-webdriver"&gt;dev environment for Node.js&lt;/a&gt; and write our test case. Untill the point where we make ACME forum to send activation email everything is certain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    const { Builder, By, Key, until } = require( "selenium-webdriver" );

    (async function main() {
      const driver = await new Builder().forBrowser("chrome").build(),
            USER = "ctest1";
      try {
        await driver.get( "http://localhost:4567/register" );

        await driver.findElement( By.id("email" ) )
          .sendKeys( `${ USER }@restmail.net`, Key.RETURN );
        await driver.findElement( By.id("username" ) )
          .sendKeys( USER , Key.RETURN );
        await driver.findElement( By.id("password" ) )
          .sendKeys( `Password1234`, Key.RETURN );
        await driver.findElement( By.id("password-confirm" ) )
          .sendKeys( `Password1234`, Key.RETURN );

        await driver.findElement( By.id("gdpr_agree_email" ) )
          .click();

        await driver.findElement( By.id("gdpr_agree_data" ) )
          .click();

        await driver.findElement( By.css("#content form button" ) )
          .click();

        //…  

      } catch ( e ) {
        console.log( e );
      } finally {
        await driver.quit();
      }
    })();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We populate the first form with test values, where email shall be in restmail.net domain. As we are done with the last field, the form gets automatically submitted. Then we tick on the checkboxes and click on submit button. Now let’s do the polling. So we put at the beginning of the script a module to simplify HTTP(S) requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    const fetch = require( "node-fetch" );
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next we place our &lt;code&gt;pollForValue&lt;/code&gt; and &lt;code&gt;parseActivationLink&lt;/code&gt; functions. Now we can extend test steps with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       const activationLink = await pollForValue({ url: `http://restmail.net/mail/${ USER }`, 
          interval: 1000, 
          timeout: 600000,  
          parserFn: ( messages ) =&amp;gt; {
              if ( !messages ) {
                return null;
              }
               const sentAt = new Date( Date.now() - 1000 ),
                     unseen = messages.find( msg =&amp;gt; new Date( msg.receivedAt ) &amp;gt; new Date( sentAt ) );                
              return parseActivationLink( messages[0].html );
          }
        });

        console.log( "Activation link:", activationLink );

        await driver.get( activationLink );
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Thus after submitting the second form we make the script polling for newly sent email message. When it received we parse the message body for the activation link. Bingo! We get the link and we make the driver navigating to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_XnK5QrN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/selenium-res.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_XnK5QrN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/selenium-res.png" alt="Selenium Webdriver results" title="Selenium Webdriver results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with Cypress
&lt;/h2&gt;

&lt;p&gt;Recently is gaining momentum a tool called &lt;a href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt;. I do like it personally for the test debugging. Without polling for mail messages the test script may look like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    const USER = "ctest1";
    describe("User registration flow", () =&amp;gt; {
        it( "registers user", ( done ) =&amp;gt; {
          cy.visit( "http://localhost:4567/register" );

          cy.get( "#email" ).type( `${ USER }@restmail.net` );
          cy.get( "#username" ).type( USER );
          cy.get( "#password" ).type( "Password1234" );
          cy.get( "#password-confirm" ).type( "Password1234" );
          cy.get( "#register" ).click();
          cy.wait( 1000 );
          cy.get("#gdpr_agree_email").click();
          cy.get("#gdpr_agree_data").click();
          cy.get("#content form button.btn-primary").click();

          //...
          done();

        })
      })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Similar to what we did with Selenium we extend the script with &lt;code&gt;pollForValue&lt;/code&gt; and &lt;code&gt;parseActivationLink&lt;/code&gt; functions. However this time instead of using node-fetch we rather go with built-in cy.request function. That’s where pollForValue’s &lt;code&gt;requestFn&lt;/code&gt; option jumps in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          pollForValue({ url: `http://restmail.net/mail/${ USER }`, 
                interval: 1000, 
                timeout: 600000,  
                parserFn: ( messages ) =&amp;gt; {
                    if ( !messages ) {
                        return null;
                      }
                       const sentAt = new Date( Date.now() - 1000 ),
                             unseen = messages.find( msg =&amp;gt; new Date( msg.receivedAt ) &amp;gt; new Date( sentAt ) );                
                      return parseActivationLink( messages[0].html );
                },
                requestFn: ( url ) =&amp;gt; {
                    return new Promise(( resolve ) =&amp;gt; {
                        cy.request( url )
                            .then( (response) =&amp;gt; {
                                resolve( response.body );
                            } );
                    });
                }
          }).then(( link ) =&amp;gt; { 
            activationLink = link;
            console.log( "Activation link:", activationLink );
            done(); 
          });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So it’s just left to declare &lt;code&gt;activationLink&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let activationLink;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and visit the activation link&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    it( "follows the activation link", () =&amp;gt; {
       cy.visit( activationLink );
    })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RALCGIYY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/cypress-res.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RALCGIYY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/cypress-res.png" alt="Cypress results" title="Cypress results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with Puppetry
&lt;/h2&gt;

&lt;p&gt;We’ve just examined how we can do the trick with script-based testing tools. Let’s take now a code-less one – &lt;a href="https://puppetry.app"&gt;Puppetry&lt;/a&gt;. With this tool we don’t script, but use GUI to fulfill our test specifications. Alternatively we &lt;a href="https://docs.puppetry.app/suite#record-suite"&gt;record user behavior&lt;/a&gt;. Anyways we end up with a test suite, which contains a table of the target elements:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RbyWFZDi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RbyWFZDi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-1.png" alt="Test target elements" title="Test target elements"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the test case:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6tRILkfc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6tRILkfc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-2.png" alt="Test case" title="Test case"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example I used template variables. First I defined a new variable &lt;code&gt;TEST_USER_ALIAS&lt;/code&gt; that resolves with every test run in &lt;code&gt;ctest1&lt;/code&gt;, &lt;code&gt;ctest2&lt;/code&gt; and so on. Then I referred to the variable when typing into email field. Besides I applied template expression &lt;code&gt;{{ faker( "internet.userName", "en" ) }}&lt;/code&gt; to generate real-world-like user name. And I also addressed and few environment-dependent variables. Other then that I don’t think you may have difficulties to read the test steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nBRNU8zt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-vars.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nBRNU8zt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-vars.png" alt="Environment-dependent variables" title="Environment-dependent variables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we extend the test for mail server polling. So we add the command corresponding to the earlier described function &lt;code&gt;pollForValue&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aKXdGT1O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aKXdGT1O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-3.png" alt="Polling for newly sent mail message" title="Polling for newly sent mail message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We give in options the retrieving and parsing function similar to one we used with Selenium and Cypress. That will resolve into new template variable &lt;code&gt;ACTIVATION_LINK&lt;/code&gt;, which we use to visit the page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_mEUw_1h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_mEUw_1h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-4.png" alt="Test case to visit activation page" title="Test case to visit activation page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There it is. We’ve got the results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wao-xj9u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-res1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wao-xj9u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-res1.png" alt="Test results in browser" title="Test results in browser"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CJtqFW4b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-res2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CJtqFW4b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/http://dsheiko.com/download/000000292/puppetry-res2.png" alt="Test report" title="Test report"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Testing user flows that involve transactional emails in a nutshell is not that complex as it’s may be seen. You just need an API to access the mail server and polling method (for example the function from this article). You can achieve with different testing tools, likely with one you’re currently working with.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
