<?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: Anish Karandikar</title>
    <description>The latest articles on DEV Community by Anish Karandikar (@anishkny).</description>
    <link>https://dev.to/anishkny</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%2F102218%2Fb4b7d6ce-c91e-46fd-8785-5d665e8f2f48.jpeg</url>
      <title>DEV Community: Anish Karandikar</title>
      <link>https://dev.to/anishkny</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anishkny"/>
    <language>en</language>
    <item>
      <title>Code coverage for a running Deno server</title>
      <dc:creator>Anish Karandikar</dc:creator>
      <pubDate>Thu, 27 Oct 2022 01:12:57 +0000</pubDate>
      <link>https://dev.to/anishkny/code-coverage-for-a-running-deno-server-35ga</link>
      <guid>https://dev.to/anishkny/code-coverage-for-a-running-deno-server-35ga</guid>
      <description>&lt;p&gt;Deno (a modern Rust based TypeScript runtime) comes with code coverage measurement built in. Unfortunately, &lt;a href="https://github.com/denoland/deno/discussions/16292" rel="noopener noreferrer"&gt;right&lt;/a&gt; &lt;a href="https://github.com/denoland/deno/issues/16440" rel="noopener noreferrer"&gt;now&lt;/a&gt;, its only supported for &lt;code&gt;deno test&lt;/code&gt; but not for &lt;code&gt;deno run&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This seems unnecessarily limiting, since it makes measuring coverage in a running Deno server by running API tests against it convoluted - but not impossible. This blog outlines a workaround that lets you achieve just this. Thanks to an idea from &lt;a href="https://github.com/denoland/deno/discussions/16292#discussioncomment-3945897" rel="noopener noreferrer"&gt;zachauten&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's say you had a bunch of Postman API tests written up for testing a Deno HTTP REST API server that you execute using the excellent &lt;a href="https://learning.postman.com/docs/running-collections/using-newman-cli/command-line-integration-with-newman/" rel="noopener noreferrer"&gt;newman&lt;/a&gt; CLI collection runner. Further, lets say you wanted to measure code coverage from running these API tests.&lt;/p&gt;

&lt;p&gt;You would think you would be able to do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start Deno HTTP server and put it in background&lt;/span&gt;
deno run &lt;span class="nt"&gt;--coverage&lt;/span&gt; ./main.ts &amp;amp;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;

&lt;span class="c"&gt;# Run Postman API tests using newman&lt;/span&gt;
npx newman run ./api-tests.postman.json  

&lt;span class="c"&gt;# Kill server to generate coverage&lt;/span&gt;
&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nv"&gt;$PID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Unfortunately, the above does not work because &lt;code&gt;deno run --coverage&lt;/code&gt; is not supported.&lt;/p&gt;

&lt;p&gt;Instead, the trick is to do all these steps (start server, run API tests, stop server) inside a Deno &lt;strong&gt;test&lt;/strong&gt; and then use &lt;code&gt;deno test --coverage&lt;/code&gt;. Sigh 😞. Seems labyrinthine but here is how to do it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Make your server a testable module
&lt;/h2&gt;

&lt;p&gt;Make sure that your server is in a form that can be consumed from a Deno test and started and stopped there. For example a simple server can look like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.ts&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;requestHandler&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;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&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;This server can also be consumed from your main entry point thus:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.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;serve&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;std/http/server.ts&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;requestHandler&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;./server.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 2: Write a test that calls the server
&lt;/h2&gt;

&lt;p&gt;Now write a test that does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start server with abort controller&lt;/li&gt;
&lt;li&gt;Shell out to run &lt;code&gt;newman&lt;/code&gt; API tests&lt;/li&gt;
&lt;li&gt;Stop server using abort controller&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server_test.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;assert&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;std/testing/asserts.ts&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;serve&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;std/http/server.ts&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;requestHandler&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;./server.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API Tests&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="c1"&gt;// Start server (with abort controller)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&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;signal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;controller&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;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Run API tests&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="p"&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;npx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newman&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./api-tests.postman.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;p&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="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Stop server (using abort controller)&lt;/span&gt;
  &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Wait for server to be closed&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&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;
  
  
  Step 3: Run test in coverage mode
&lt;/h2&gt;

&lt;p&gt;Finally, we can run the server test with the &lt;code&gt;--coverage&lt;/code&gt; flag.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--allow-net&lt;/span&gt; &lt;span class="nt"&gt;--allow-run&lt;/span&gt; &lt;span class="nt"&gt;--coverage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;coverage ./server_test.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will run the outer &lt;code&gt;server_test.ts&lt;/code&gt; in coverage mode, generating code coverage in the &lt;code&gt;coverage&lt;/code&gt; folder which can be further processed. See help on &lt;a href="https://deno.land/manual/testing/coverage" rel="noopener noreferrer"&gt;Deno coverage&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Hopefully, in the long run, Deno will introduce a &lt;code&gt;deno run --coverage&lt;/code&gt; option. Till then, this workaround will have to do.&lt;/p&gt;

&lt;p&gt;For a more fleshed out version of the code in this post, please see:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anishkny" rel="noopener noreferrer"&gt;
        anishkny
      &lt;/a&gt; / &lt;a href="https://github.com/anishkny/deno-api-tests-coverage" rel="noopener noreferrer"&gt;
        deno-api-tests-coverage
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Measure code coverage of a Deno server when running Postman (newman) API tests
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Deno API Tests Coverage&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://coveralls.io/github/anishkny/deno-api-tests-coverage?branch=main" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ead5281bab53aecb98fa2abbfbaa9d026689ceac0af438a065f8a88cded5eeca/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f616e6973686b6e792f64656e6f2d6170692d74657374732d636f7665726167652f62616467652e7376673f6272616e63683d6d61696e" alt="Coverage Status"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demonstrates code coverage measurement of a Deno server when running Postman (&lt;code&gt;newman&lt;/code&gt;) API tests.&lt;/p&gt;

&lt;p&gt;See accompanying &lt;a href="https://dev.to/anishkny/code-coverage-for-a-running-deno-server-35ga" rel="nofollow"&gt;blog post&lt;/a&gt; for more details.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Requirements&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Deno v1.26 (or higher) installed and available on path.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Clone this repository and execute:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;deno task &lt;span class="pl-c1"&gt;test&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This runs the test file &lt;a href="https://github.com/anishkny/deno-api-tests-coverage./test/server_test.ts" rel="noopener noreferrer"&gt;&lt;code&gt;./test/server_test.ts&lt;/code&gt;&lt;/a&gt; and collects resulting code coverage.&lt;/p&gt;

&lt;p&gt;The test file takes the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the Deno server to be tested (&lt;a href="https://github.com/anishkny/deno-api-tests-coveragesrc/server.ts" rel="noopener noreferrer"&gt;&lt;code&gt;src/server.ts&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Run Postman API tests using &lt;code&gt;newman&lt;/code&gt; in a subprocess.&lt;/li&gt;
&lt;li&gt;Stop the server.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Background&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Deno lacks a way to measure code coverage of a server when running API tests. See &lt;a href="https://github.com/denoland/deno/discussions/16292" rel="noopener noreferrer"&gt;this discussion&lt;/a&gt; on the Deno repo.&lt;/p&gt;

&lt;p&gt;Ideally, there would be a way to start a server thus:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;deno run --coverage=coverage src/server.ts &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Doesn't work&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Run API tests&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Kill server&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Instead you have to start the server, run API tests by shelling out to a process…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/anishkny/deno-api-tests-coverage" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



</description>
      <category>deno</category>
      <category>coverage</category>
      <category>node</category>
      <category>testing</category>
    </item>
    <item>
      <title>Code coverage for a Next.js app using Playwright tests</title>
      <dc:creator>Anish Karandikar</dc:creator>
      <pubDate>Tue, 05 Apr 2022 18:58:40 +0000</pubDate>
      <link>https://dev.to/anishkny/code-coverage-for-a-nextjs-app-using-playwright-tests-18n7</link>
      <guid>https://dev.to/anishkny/code-coverage-for-a-nextjs-app-using-playwright-tests-18n7</guid>
      <description>&lt;p&gt;&lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; is a remarkable tool that lets you author cross-browser cross-platform end-to-end tests for your web app. It even lets you &lt;a href="https://playwright.dev/docs/codegen" rel="noopener noreferrer"&gt;generate&lt;/a&gt; test code as you navigate your app (but that's probably a topic for another blog post)!&lt;/p&gt;

&lt;p&gt;Unfortunately, as of now, out of the box, it does not support measuring code coverage for the app being tested (see &lt;a href="https://github.com/microsoft/playwright/issues/7030" rel="noopener noreferrer"&gt;#7030&lt;/a&gt; and &lt;a href="https://github.com/microsoft/playwright/issues/9208" rel="noopener noreferrer"&gt;#9208&lt;/a&gt;). This post demonstrates a workaround.&lt;/p&gt;

&lt;p&gt;Specifically, this post shows you how to instrument a Next.js app using a custom &lt;code&gt;babel.config.js&lt;/code&gt; and how to measure code coverage using the &lt;a href="https://www.npmjs.com/package/playwright-test-coverage" rel="noopener noreferrer"&gt;&lt;code&gt;playwright-test-coverage&lt;/code&gt;&lt;/a&gt; plugin. If you want to jump straight into the demo, you can do that here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anishkny" rel="noopener noreferrer"&gt;
        anishkny
      &lt;/a&gt; / &lt;a href="https://github.com/anishkny/playwright-test-coverage-demo" rel="noopener noreferrer"&gt;
        playwright-test-coverage-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Demonstrates usage of playwright-test-coverage - a Playwright extension that collects code coverage from running end-to-end tests
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Playwright Test Coverage Demo&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://coveralls.io/github/anishkny/playwright-test-coverage-demo?branch=main" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fbc6db7c3da577311c2417a744aff5e50098503de07d9abc47d04d7360a5ad57/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f616e6973686b6e792f706c61797772696768742d746573742d636f7665726167652d64656d6f2f62616467652e7376673f6272616e63683d6d61696e" alt="Coverage Status"&gt;&lt;/a&gt;
&lt;a href="https://depfu.com/github/anishkny/playwright-test-coverage-demo?project_id=37920" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/58a118b4e9fae9ef0e112cb4c69c07b49a178564d37dbaad445b27f125b5d4b5/68747470733a2f2f6261646765732e64657066752e636f6d2f6261646765732f61656432343066653134343738336334643066616266366131636336373136372f6f766572766965772e737667" alt="Depfu"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Demonstrates usage of &lt;a href="https://github.com/anishkny/playwright-test-coverage" rel="noopener noreferrer"&gt;playwright-test-coverage&lt;/a&gt; - a &lt;a href="https://playwright.dev" rel="nofollow noopener noreferrer"&gt;Playwright&lt;/a&gt; extension that collects code coverage from running end-to-end tests.&lt;/p&gt;
&lt;p&gt;Read more in this &lt;a href="https://dev.to/anishkny/code-coverage-for-a-nextjs-app-using-playwright-tests-18n7" rel="nofollow"&gt;blog post&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install
npm &lt;span class="pl-c1"&gt;test&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Code coverage should be generated in &lt;code&gt;coverage/&lt;/code&gt; folder.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Sample&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://coveralls.io/builds/42662804/source?filename=pages/index.js" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fanishkny%2Fplaywright-test-coverage-demo.%2Fcoveralls.png" alt="Coveralls Report"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://choosealicense.com/licenses/mit/" rel="nofollow noopener noreferrer"&gt;MIT&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/anishkny/playwright-test-coverage-demo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Step 1: Instrument your code
&lt;/h2&gt;

&lt;p&gt;If you want to measure code coverage, first you will want to instrument your code (i.e. insert counters without causing any side-effects) using &lt;code&gt;istanbul&lt;/code&gt;. Most modern web apps (React, Vue etc), use &lt;code&gt;babel&lt;/code&gt; in their toolchain (which converts modern ES2015+ code into backwards compatible code to support older browsers). The recommended way to instrument code in this scenario is using &lt;code&gt;babel-plugin-istanbul&lt;/code&gt;:&lt;/p&gt;

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

npm i &lt;span class="nt"&gt;-D&lt;/span&gt; babel-plugin-istanbul


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

&lt;/div&gt;
&lt;p&gt;Now, we need to instruct &lt;code&gt;babel&lt;/code&gt; to use this plugin. The details may vary by framework, but for a Next.js app, create a file named &lt;code&gt;babel.config.js&lt;/code&gt; in the root folder:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// babel.config.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Instrument for code coverage in development mode&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;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;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Detected development environment. Instrumenting code for coverage.&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;istanbul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;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="na"&gt;presets&lt;/span&gt;&lt;span class="p"&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;next/babel&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;};&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;This will instrument code only in development mode e.g. when running &lt;code&gt;next dev&lt;/code&gt;. That's good because we do not want instrumentation cluttering our production code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Write tests using the &lt;code&gt;playwright-test-coverage&lt;/code&gt; extension
&lt;/h2&gt;

&lt;p&gt;Now that your code is instrumented, you are ready to measure code coverage. For this you will need to install this Playwright plugin:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm i &lt;span class="nt"&gt;-D&lt;/span&gt; playwright-test-coverage


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

&lt;/div&gt;
&lt;p&gt;This package exports overrides for Playwright's &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;expect&lt;/code&gt; methods which handle code coverage measurement correctly.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// tests/app.spec.js&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;test&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright-test-coverage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;basic test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Use Playwright as usual&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&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="nf"&gt;expect&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="nf"&gt;toHaveText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&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 you have existing Playwright tests, simply drop in the following replacement near the top:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;


&lt;span class="c1"&gt;// Replace this:&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;test&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// With this:&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;test&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright-test-coverage&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;
  
  
  Step 3: Measure code coverage
&lt;/h2&gt;

&lt;p&gt;Finally, you are ready to measure code coverage.&lt;/p&gt;

&lt;p&gt;Start the Next.js dev server using the Istanbul command line tool &lt;code&gt;nyc&lt;/code&gt; - which enables code coverage measurement:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npx nyc next dev


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

&lt;/div&gt;
&lt;p&gt;Then, in another terminal window, run the playwright tests as usual:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npx playwright &lt;span class="nb"&gt;test&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;If all goes well, you should see a code coverage report similar to:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Running 1 test using 1 worker

  ✓  tests/app.spec.js:5:1 › basic test (1s)


  1 passed (2s)
----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |
 index.js |     100 |      100 |     100 |     100 |
----------|---------|----------|---------|---------|-------------------


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Hopefully you now have some understanding of how to measure code coverage for a Next.js (or any Babel based) app when running Playwright tests.&lt;/p&gt;

&lt;p&gt;You can check out the full source code for the demo here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anishkny" rel="noopener noreferrer"&gt;
        anishkny
      &lt;/a&gt; / &lt;a href="https://github.com/anishkny/playwright-test-coverage-demo" rel="noopener noreferrer"&gt;
        playwright-test-coverage-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Demonstrates usage of playwright-test-coverage - a Playwright extension that collects code coverage from running end-to-end tests
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Playwright Test Coverage Demo&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://coveralls.io/github/anishkny/playwright-test-coverage-demo?branch=main" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fbc6db7c3da577311c2417a744aff5e50098503de07d9abc47d04d7360a5ad57/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f616e6973686b6e792f706c61797772696768742d746573742d636f7665726167652d64656d6f2f62616467652e7376673f6272616e63683d6d61696e" alt="Coverage Status"&gt;&lt;/a&gt;
&lt;a href="https://depfu.com/github/anishkny/playwright-test-coverage-demo?project_id=37920" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/58a118b4e9fae9ef0e112cb4c69c07b49a178564d37dbaad445b27f125b5d4b5/68747470733a2f2f6261646765732e64657066752e636f6d2f6261646765732f61656432343066653134343738336334643066616266366131636336373136372f6f766572766965772e737667" alt="Depfu"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Demonstrates usage of &lt;a href="https://github.com/anishkny/playwright-test-coverage" rel="noopener noreferrer"&gt;playwright-test-coverage&lt;/a&gt; - a &lt;a href="https://playwright.dev" rel="nofollow noopener noreferrer"&gt;Playwright&lt;/a&gt; extension that collects code coverage from running end-to-end tests.&lt;/p&gt;
&lt;p&gt;Read more in this &lt;a href="https://dev.to/anishkny/code-coverage-for-a-nextjs-app-using-playwright-tests-18n7" rel="nofollow"&gt;blog post&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install
npm &lt;span class="pl-c1"&gt;test&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Code coverage should be generated in &lt;code&gt;coverage/&lt;/code&gt; folder.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Sample&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://coveralls.io/builds/42662804/source?filename=pages/index.js" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fanishkny%2Fplaywright-test-coverage-demo.%2Fcoveralls.png" alt="Coveralls Report"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://choosealicense.com/licenses/mit/" rel="nofollow noopener noreferrer"&gt;MIT&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/anishkny/playwright-test-coverage-demo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The extension code is available here and contributions/issues are welcome:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anishkny" rel="noopener noreferrer"&gt;
        anishkny
      &lt;/a&gt; / &lt;a href="https://github.com/anishkny/playwright-test-coverage" rel="noopener noreferrer"&gt;
        playwright-test-coverage
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Extends Playwright test to measure code coverage
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Playwright Test Coverage&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/anishkny/playwright-test-coverage/actions/workflows/ci-cd.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/anishkny/playwright-test-coverage/actions/workflows/ci.yml/badge.svg" alt="CI/CD"&gt;&lt;/a&gt;
&lt;a href="https://depfu.com/github/anishkny/playwright-test-coverage?project_id=32640" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e11ca5e8e5f59b07dd416e2f3a5da03e34b652dd45dc9ad8c4c3e1495e4ab86d/68747470733a2f2f6261646765732e64657066752e636f6d2f6261646765732f39346432316461636462373332626164353535383336373266313338633765622f6f766572766965772e737667" alt="Depfu"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/playwright-test-coverage" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e9522755623780d973ecef886267cd9b189ef63daa921d4662a1f10462ebb182/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f706c61797772696768742d746573742d636f766572616765" alt="npm"&gt;&lt;/a&gt;
&lt;a href="https://npmtrends.com/playwright-test-coverage" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2a5dc7b9d9c9e4fb45001069b9c42f4850a4c8b04a23b95e1990bb3316dfcc5f/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64772f706c61797772696768742d746573742d636f766572616765" alt="npm"&gt;&lt;/a&gt;
&lt;a href="https://github.com/mxschmitt/awesome-playwright" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3418ba3754faddfb88c5cbdc94c31ad670fc693c8caa59bc2806c9836acc04e4/68747470733a2f2f617765736f6d652e72652f62616467652e737667" alt="Awesome"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A &lt;a href="https://playwright.dev" rel="nofollow noopener noreferrer"&gt;Playwright&lt;/a&gt; extension that collects code coverage from running end-to-end tests. Assumes that code has been instrumented with &lt;a href="https://github.com/istanbuljs/babel-plugin-istanbul" rel="noopener noreferrer"&gt;babel-plugin-istanbul&lt;/a&gt; during the build process.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Playwright test framework&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;babel-plugin-istanbul&lt;/code&gt; plugin&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nyc&lt;/code&gt; for running tests&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm i -D @playwright/test babel-plugin-istanbul nyc&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm i -D playwright-test-coverage&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Write your Playwright tests as usual, except &lt;code&gt;require&lt;/code&gt; &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;expect&lt;/code&gt; from this package as follows:&lt;/p&gt;
&lt;div class="highlight highlight-source-js notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;// tests/foo.spec.js&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; test&lt;span class="pl-kos"&gt;,&lt;/span&gt; expect &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;require&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"playwright-test-coverage"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-c"&gt;// Use test and expect as usual&lt;/span&gt;
&lt;span class="pl-en"&gt;test&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"basic test"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt; page &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;page&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;goto&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"https://playwright.dev/"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;title&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;page&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;locator&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;".navbar__inner .navbar__title"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-en"&gt;expect&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;title&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;toHaveText&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"Playwright"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then, instrument your front end source code for coverage using the &lt;code&gt;babel-plugin-istanbul&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;Finally, run your server via &lt;code&gt;nyc&lt;/code&gt;…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/anishkny/playwright-test-coverage" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Cheers!&lt;/p&gt;

&lt;h2&gt;
  
  
  PS: Using with fixtures
&lt;/h2&gt;

&lt;p&gt;Playwright lets you define custom fixtures in accordance with the Page Object Model pattern. This library should work out of the box with custom fixtures.&lt;/p&gt;

&lt;p&gt;You can see an example &lt;a href="https://github.com/anishkny/playwright-test-coverage-demo/blob/main/tests/app-with-fixture.spec.js" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>coverage</category>
      <category>qa</category>
      <category>automation</category>
    </item>
    <item>
      <title>The incredible power of Deno Deploy</title>
      <dc:creator>Anish Karandikar</dc:creator>
      <pubDate>Wed, 23 Mar 2022 16:40:51 +0000</pubDate>
      <link>https://dev.to/anishkny/the-incredible-power-of-deno-deploy-p2c</link>
      <guid>https://dev.to/anishkny/the-incredible-power-of-deno-deploy-p2c</guid>
      <description>&lt;p&gt;As you folks might know &lt;a href="https://deno.land/"&gt;Deno&lt;/a&gt; is a modern evolution of Node.js, from the original creator Ryan Dahl, which aims to improve on the design of and avoid many of the pitfalls of the original. It is a fast Rust based runtime for JavaScript/TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deno.com/deploy"&gt;Deno Deploy&lt;/a&gt; is an accompanying service that instantly deploys Deno code to dozens of &lt;a href="https://deno.com/deploy/docs/regions"&gt;regions&lt;/a&gt; around the world with a single publicly available URL in front of it. It automatically routes requests to the nearest deployed region based on the caller's location! And all this with one click of a button!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It... is... incredible!!!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In comparison, achieving something similar in AWS is almost comically &lt;a href="https://aws.amazon.com/blogs/compute/building-a-multi-region-serverless-application-with-amazon-api-gateway-and-aws-lambda/"&gt;complicated&lt;/a&gt; (sorry, I know AWS teams work hard but still... ¯\_(ツ)_/¯).&lt;/p&gt;

&lt;p&gt;Having dabbled in serverless development and ops in the past, I wanted to see if Deno Deploy indeed lived up to its promise. So I wrote and deployed a simple script that displays the environment variable &lt;code&gt;DENO_REGION&lt;/code&gt; in the response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serve&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;https://deno.land/std@0.120.0/http/server.ts&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="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`&amp;lt;pre style="font-size:10em;"&amp;gt;
       DENO_REGION=&amp;lt;br&amp;gt;
       &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Deno&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="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DENO_REGION&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
     &amp;lt;/pre&amp;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;headers&lt;/span&gt;&lt;span class="p"&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;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Listening on http://localhost:8000&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;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As soon as I clicked the &lt;strong&gt;Save &amp;amp; Deploy&lt;/strong&gt; button, my URL was live: &lt;a href="https://show-region.deno.dev"&gt;https://show-region.deno.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UTnsulxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hj64uqy6qqrz62twow4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UTnsulxV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hj64uqy6qqrz62twow4w.png" alt="Deployed Deno code" width="880" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But how do we know if requests from around the world will be routed to the nearest region as claimed?&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://geopeeker.com/"&gt;GeoPeeker&lt;/a&gt; - an excellent service that loads any webpage from different locations around the world and grabs screenshots from there.&lt;/p&gt;

&lt;p&gt;Passing our URL to GeoPeeker shows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://geopeeker.com/fetch/?url=https%3A%2F%2Fshow-region.deno.dev%2F"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BLigGr2v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v8fsgx2ndkk1s3sqhs1t.png" alt="Deno regions" width="880" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can clearly see, the request from Singapore is routed to &lt;code&gt;asia-southeast1&lt;/code&gt;, Brazil to &lt;code&gt;southamerica-east1&lt;/code&gt; and so on. Very cool indeed!&lt;/p&gt;

&lt;p&gt;You can get started with Deno Deploy &lt;a href="https://deno.com/deploy"&gt;here&lt;/a&gt; - have fun!&lt;/p&gt;

</description>
      <category>deno</category>
      <category>deploy</category>
      <category>distributedsystems</category>
      <category>global</category>
    </item>
    <item>
      <title>𝚒𝚗𝚝𝚎𝚐𝚛𝚒𝚏𝚢 - Firestore referential integrity via triggers</title>
      <dc:creator>Anish Karandikar</dc:creator>
      <pubDate>Wed, 23 Jan 2019 16:01:36 +0000</pubDate>
      <link>https://dev.to/anishkny/---firestore-referential-integrity-via-triggers-kpb</link>
      <guid>https://dev.to/anishkny/---firestore-referential-integrity-via-triggers-kpb</guid>
      <description>&lt;h1&gt;
  
  
  Firestore is amazing, but...
&lt;/h1&gt;

&lt;p&gt;Google Cloud Firestore is a serverless NoSQL document database that scales horizontally - which means it adds/removes nodes to serve your database based on demand automagically. It also does some fancy indexing that allows query times to be proportional to result size instead of total data size. So basically if your query returns 10 records, it will take the same time to run if the total data size is 10, 100, 1000 or squillions of records.&lt;/p&gt;

&lt;p&gt;It offers an expressive query language, but does have some &lt;a href="https://firebase.google.com/docs/firestore/query-data/queries#query_limitations" rel="noopener noreferrer"&gt;limitations&lt;/a&gt; which guarantee O(ResultSet) performance. Also, while designing NoSQL database schemas, we have to often "unlearn" data normalization principles we learnt building relational databases.&lt;/p&gt;

&lt;p&gt;For example, say you had a database that records comments made by users who have usernames and profile photos. Traditionally you would have stored a foreign key called userId in the comments table, and performed a "join" to get comments together with usernames and profile photos.&lt;/p&gt;

&lt;p&gt;But in a NoSQL schema, data is often denormalized - in this case for example, username and photo are repeated in each comment record for ease of retrieval.&lt;/p&gt;

&lt;p&gt;The key question then of course is how are updates to username/photo reflected across all comments made by a user? In the case of Firestore, one could write a Cloud Function &lt;a href="https://firebase.google.com/docs/functions/firestore-events" rel="noopener noreferrer"&gt;triggered&lt;/a&gt; by updates to any user record which replicates the update to all comment records.&lt;/p&gt;

&lt;h1&gt;
  
  
  𝚒𝚗𝚝𝚎𝚐𝚛𝚒𝚏𝚢 can help!
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/anishkny/integrify" rel="noopener noreferrer"&gt;𝚒𝚗𝚝𝚎𝚐𝚛𝚒𝚏𝚢&lt;/a&gt; is an npm library that offers pre-canned Firestore triggers that help maintain referential and data integrity in some commonly occurring scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attribute Replication
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt; - Continuing the users/comments example above, you could have a schema like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  /users/
    userId/
      username
      photoURL

  /comments/
    commentId/
      body
      userId       &amp;lt;-- foreign key
      username     &amp;lt;-- replicated field
      photoURL     &amp;lt;-- replicated field
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt; - To enforce referential integrity on updates of username/photoURL, simply use:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replUserAttrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;integrify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REPLICATE_ATTRIBUTES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&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;targets&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;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;comments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;foreignKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;attributeMapping&lt;/span&gt;&lt;span class="p"&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="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;photoURL&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;photoURL&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="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;
  
  
  Stale Reference Deletion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt; - Say you have an articles collection, where each article can have zero or more comments each with an articleId foreign key. And you want to delete all comments automatically if the corresponding article is deleted.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  /articles/
    articleId/
      body
      updatedAt
      isPublished
      ...

  /comments/
    commentId/
      articleId   &amp;lt;-- foreign key
      body
      ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt; - To delete all comments corresponding to a deleted article, use:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delArticleRefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;integrify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE_REFERENCES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articles&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;targets&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;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;comments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;foreignKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articleId&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="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;

&lt;h2&gt;
  
  
  Count Maintainence
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt; - Say you want to record which users have liked any particular article and also be able to quickly determine how many total likes an article has received.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  /likes/
    likeId/
      userId
      articleId    &amp;lt;-- foreign key

  /articles/
    articleId/
      likesCount   &amp;lt;-- aggregate field
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt; - To maintain a live count of number of likes stored in the corresponding article document, use:&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="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incrementLikesCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrementLikesCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;integrify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MAINTAIN_COUNT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;likes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;foreignKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articleId&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;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;likesCount&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;Notice that you get two triggers, one to increment and another to decrement the likesCount attributes for every addition or deletion in the likes collection.&lt;/p&gt;
&lt;h1&gt;
  
  
  Deploying
&lt;/h1&gt;

&lt;p&gt;𝚒𝚗𝚝𝚎𝚐𝚛𝚒𝚏𝚢 is meant to be used in conjunction with &lt;code&gt;firebase-functions&lt;/code&gt; and &lt;code&gt;firebase-admin&lt;/code&gt;. Indeed, they are peerDependencies for 𝚒𝚗𝚝𝚎𝚐𝚛𝚒𝚏𝚢. Typically, your setup would look like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firebase-functions&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;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firebase-admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initializeApp&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="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firestore&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;integrify&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;integrify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;integrify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&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;functions&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;span class="c1"&gt;// Use integrify here...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then you would deploy the functions returned by 𝚒𝚗𝚝𝚎𝚐𝚛𝚒𝚏𝚢 like any other &lt;a href="https://firebase.google.com/docs/functions/get-started" rel="noopener noreferrer"&gt;Firebase Function&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firebase deploy &lt;span class="nt"&gt;--only&lt;/span&gt; functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Source Code
&lt;/h1&gt;

&lt;p&gt;Check out the source code, and feel free to open any issues, send out PRs or general comments! &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anishkny" rel="noopener noreferrer"&gt;
        anishkny
      &lt;/a&gt; / &lt;a href="https://github.com/anishkny/integrify" rel="noopener noreferrer"&gt;
        integrify
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤝 Enforce referential and data integrity in Cloud Firestore using triggers
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;𝚒𝚗𝚝𝚎𝚐𝚛𝚒𝚏𝚢&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/anishkny/integrify/actions/workflows/build-and-test.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/anishkny/integrify/actions/workflows/build-and-test.yml/badge.svg?branch=main" alt="Build &amp;amp; Test"&gt;&lt;/a&gt;
&lt;a href="https://codecov.io/gh/anishkny/integrify" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/defda6db3147d5442fc68201da9e21e3ea8ec3b3635f2020f4972a33ff9251ff/68747470733a2f2f636f6465636f762e696f2f67682f616e6973686b6e792f696e746567726966792f6272616e63682f6d61696e2f67726170682f62616467652e737667" alt="Code Coverage Status"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/integrify" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fc70590a7a25d0fbf0f97de9e2468f1dbec675495166f30946b461cabd165ad1/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f696e746567726966792e737667" alt="npm package"&gt;&lt;/a&gt;
&lt;a href="https://github.com/jthegedus/awesome-firebase" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d8b2bde4796b67266f07c7a619f554c926ca4750d5d8861b4b740baaddc3fd1e/68747470733a2f2f617765736f6d652e72652f6d656e74696f6e65642d62616467652e737667" alt="Mentioned in Awesome Firebase"&gt;&lt;/a&gt;
&lt;a href="https://firebaseopensource.com/projects/anishkny/integrify/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b7c030be679c833b550740f79b054f685afa140094029a6d8eafdc6fd2fa913b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f2545322539442541342d46697265626173655f4f70656e5f536f757263652d464643413238" alt="Firebase Open Source"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🤝 Enforce referential and data integrity in &lt;a href="https://firebase.google.com/docs/firestore/" rel="nofollow noopener noreferrer"&gt;Cloud Firestore&lt;/a&gt; using &lt;a href="https://firebase.google.com/docs/functions/firestore-events" rel="nofollow noopener noreferrer"&gt;triggers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/anishkny/---firestore-referential-integrity-via-triggers-kpb" rel="nofollow"&gt;Introductory blog post&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-js notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;// index.js&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; integrify &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;require&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'integrify'&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;functions&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;require&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'firebase-functions'&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;admin&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;require&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'firebase-admin'&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-s1"&gt;admin&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;initializeApp&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;db&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;admin&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;firestore&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-en"&gt;integrify&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-c1"&gt;config&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt; functions&lt;span class="pl-kos"&gt;,&lt;/span&gt; db &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-c"&gt;// Automatically replicate attributes from source to target&lt;/span&gt;
&lt;span class="pl-smi"&gt;module&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;exports&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;replicateMasterToDetail&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;integrify&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c1"&gt;rule&lt;/span&gt;: &lt;span class="pl-s"&gt;'REPLICATE_ATTRIBUTES'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;source&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;collection&lt;/span&gt;: &lt;span class="pl-s"&gt;'master'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;targets&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;
    &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;collection&lt;/span&gt;: &lt;span class="pl-s"&gt;'detail1'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;foreignKey&lt;/span&gt;: &lt;span class="pl-s"&gt;'masterId'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;attributeMapping&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
        &lt;span class="pl-c1"&gt;masterField1&lt;/span&gt;: &lt;span class="pl-s"&gt;'detail1Field1'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
        &lt;span class="pl-c1"&gt;masterField2&lt;/span&gt;: &lt;span class="pl-s"&gt;'detail1Field2'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;collection&lt;/span&gt;: &lt;span class="pl-s"&gt;'detail2'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;foreignKey&lt;/span&gt;: &lt;span class="pl-s"&gt;'masterId'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;attributeMapping&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
        &lt;span class="pl-c1"&gt;masterField1&lt;/span&gt;: &lt;span class="pl-s"&gt;'detail2Field1'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
        &lt;span class="pl-c1"&gt;masterField3&lt;/span&gt;: &lt;span class="pl-s"&gt;'detail2Field3'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/anishkny/integrify" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Thanks for reading ✌️✌️✌️&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>firestore</category>
      <category>serverless</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Animating a commit based Sudoku game using Puppeteer</title>
      <dc:creator>Anish Karandikar</dc:creator>
      <pubDate>Wed, 31 Oct 2018 15:46:00 +0000</pubDate>
      <link>https://dev.to/anishkny/animating-a-commit-based-sudoku-game-using-puppeteer-185f</link>
      <guid>https://dev.to/anishkny/animating-a-commit-based-sudoku-game-using-puppeteer-185f</guid>
      <description>&lt;p&gt;A few months ago I came across an intriguing GitHub repo:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qF2jUiUG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-6a5bca60a4ebf959a6df7f08217acd07ac2bc285164fae041eacb8a148b1bab9.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xiegeo"&gt;
        xiegeo
      &lt;/a&gt; / &lt;a href="https://github.com/xiegeo/commit-sudoku"&gt;
        commit-sudoku
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Accepting pull requests to collectively finish a Sudoku puzzle.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="instapaper_body md"&gt;
&lt;h1&gt;
commit-sudoku&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://travis-ci.org/xiegeo/commit-sudoku" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/6a1b869a0812caac9afe105a5f1558ae50dd0ca4/68747470733a2f2f7472617669732d63692e6f72672f78696567656f2f636f6d6d69742d7375646f6b752e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tbody&gt;
&lt;tr&gt;
    &lt;td&gt;1
    &lt;/td&gt;
&lt;td&gt;5
    &lt;/td&gt;
&lt;td&gt;7
    &lt;/td&gt;
&lt;td&gt;2
    &lt;/td&gt;
&lt;td&gt;9
    &lt;/td&gt;
&lt;td&gt;6
    &lt;/td&gt;
&lt;td&gt;8
    &lt;/td&gt;
&lt;td&gt;3
    &lt;/td&gt;
&lt;td&gt;4
  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;8
    &lt;/td&gt;
&lt;td&gt;9
    &lt;/td&gt;
&lt;td&gt;2
    &lt;/td&gt;
&lt;td&gt;3
    &lt;/td&gt;
&lt;td&gt;4
    &lt;/td&gt;
&lt;td&gt;5
    &lt;/td&gt;
&lt;td&gt;6
    &lt;/td&gt;
&lt;td&gt;1
    &lt;/td&gt;
&lt;td&gt;7
  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;            
    &lt;td&gt;4
    &lt;/td&gt;
&lt;td&gt;3
    &lt;/td&gt;
&lt;td&gt;6
    &lt;/td&gt;
&lt;td&gt;1
    &lt;/td&gt;
&lt;td&gt;8
    &lt;/td&gt;
&lt;td&gt;7
    &lt;/td&gt;
&lt;td&gt;2
    &lt;/td&gt;
&lt;td&gt;5
    &lt;/td&gt;
&lt;td&gt;9
  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;            
    &lt;td&gt;7
    &lt;/td&gt;
&lt;td&gt;4
    &lt;/td&gt;
&lt;td&gt;1
    &lt;/td&gt;
&lt;td&gt;6
    &lt;/td&gt;
&lt;td&gt;5
    &lt;/td&gt;
&lt;td&gt;2
    &lt;/td&gt;
&lt;td&gt;3
    &lt;/td&gt;
&lt;td&gt;9
    &lt;/td&gt;
&lt;td&gt;8
  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;            
    &lt;td&gt;9
    &lt;/td&gt;
&lt;td&gt;2
    &lt;/td&gt;
&lt;td&gt;8
    &lt;/td&gt;
&lt;td&gt;7
    &lt;/td&gt;
&lt;td&gt;3
    &lt;/td&gt;
&lt;td&gt;4
    &lt;/td&gt;
&lt;td&gt;5
    &lt;/td&gt;
&lt;td&gt;6
    &lt;/td&gt;
&lt;td&gt;1
  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;            
    &lt;td&gt;3
    &lt;/td&gt;
&lt;td&gt;6
    &lt;/td&gt;
&lt;td&gt;5
    &lt;/td&gt;
&lt;td&gt;8
    &lt;/td&gt;
&lt;td&gt;1
    &lt;/td&gt;
&lt;td&gt;9
    &lt;/td&gt;
&lt;td&gt;4
    &lt;/td&gt;
&lt;td&gt;7
    &lt;/td&gt;
&lt;td&gt;2
  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;            
    &lt;td&gt;6
    &lt;/td&gt;
&lt;td&gt;8
    &lt;/td&gt;
&lt;td&gt;4
    &lt;/td&gt;
&lt;td&gt;9
    &lt;/td&gt;
&lt;td&gt;7
    &lt;/td&gt;
&lt;td&gt;3
    &lt;/td&gt;
&lt;td&gt;1
    &lt;/td&gt;
&lt;td&gt;2
    &lt;/td&gt;
&lt;td&gt;5
  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;            
    &lt;td&gt;2
    &lt;/td&gt;
&lt;td&gt;1
    &lt;/td&gt;
&lt;td&gt;9
    &lt;/td&gt;
&lt;td&gt;5
    &lt;/td&gt;
&lt;td&gt;6
    &lt;/td&gt;
&lt;td&gt;8
    &lt;/td&gt;
&lt;td&gt;7
    &lt;/td&gt;
&lt;td&gt;4
    &lt;/td&gt;
&lt;td&gt;3
  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;            
    &lt;td&gt;5
    &lt;/td&gt;
&lt;td&gt;7
    &lt;/td&gt;
&lt;td&gt;3
    &lt;/td&gt;
&lt;td&gt;4
    &lt;/td&gt;
&lt;td&gt;2
    &lt;/td&gt;
&lt;td&gt;1
    &lt;/td&gt;
&lt;td&gt;9
    &lt;/td&gt;
&lt;td&gt;8
    &lt;/td&gt;
&lt;td&gt;6
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
Rules&lt;/h2&gt;
&lt;p&gt;There are two types of pull requests, moves and others.&lt;/p&gt;
&lt;p&gt;A move must only include filling in one number in one cell in the puzzle.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A player can only make at most one move a day.&lt;/li&gt;
&lt;li&gt;A player can remove any moves made by himself or herself without the above limitation.&lt;/li&gt;
&lt;li&gt;Moves are accepted in order of first submission.&lt;/li&gt;
&lt;li&gt;Conflicting moves are skipped, they can be resubmitted…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xiegeo/commit-sudoku"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
 

&lt;p&gt;It said it was &lt;em&gt;"accepting pull requests to collectively finish a Sudoku puzzle"&lt;/em&gt;. I was fascinated. The idea was simple. The author had set up an empty &lt;a href="https://en.wikipedia.org/wiki/Sudoku"&gt;Sudoku&lt;/a&gt; board and anyone could add a number to it as long as it was a valid Sudoku move (&amp;amp; max 1 move/day/author). Pretty soon I found myself sending out PRs - adding a &lt;a href="https://github.com/xiegeo/commit-sudoku/commit/9a1934f61821835167f4533f13db911299e66e36"&gt;4 here&lt;/a&gt;, or a &lt;a href="https://github.com/xiegeo/commit-sudoku/commit/bad1ae0c46289740e23f986f9962dc1449546a43"&gt;7 there&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It was a bit difficult to specify your intended move because the board was represented as a simple HTML table in the repo's &lt;code&gt;README.md&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;1
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;7
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;9
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;4
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So I sent an enhancement PR &lt;a href="https://github.com/xiegeo/commit-sudoku/pull/32/files"&gt;embedding row numbers in the board&lt;/a&gt; - and lo! - not only was the PR accepted, but the author &lt;a href="https://github.com/xiegeo"&gt;@xiegeo&lt;/a&gt; even offered me commit access to the main repo! I was psyched! Soon along with sending out "move" PR's I also started checking and merging PR's sent out by other contributors and doing general housekeeping tasks.&lt;/p&gt;

&lt;p&gt;When I went back to the repo, after a longer delay, I noticed that the board was a lot more filled than I remembered. This got me thinking - how had the board evolved over time since the first move? So I decided to try and &lt;strong&gt;animate the evolution of the Sudoku board state from commit history&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Get a list of commits
&lt;/h2&gt;

&lt;p&gt;First, I needed to get a list of commit SHAs that were made to the repo in reverse (oldest first) order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commitSHAs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;git log --reverse --no-merges --format=format:%H&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="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;
  
  
  Step 2: Get Sudoku state for each commit
&lt;/h2&gt;

&lt;p&gt;Next, for each commit, I need to extract the state of the board from the repo's &lt;code&gt;README.md&lt;/code&gt; file. I used &lt;code&gt;git show&lt;/code&gt; to do 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;const&lt;/span&gt; &lt;span class="nx"&gt;readmeContents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`git show &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;commitSHA&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:README.md`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="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;
  
  
  Step 3: Render each state in Puppeteer
&lt;/h2&gt;

&lt;p&gt;Now comes the interesting part. I have the state of the board as HTML for each commit that I need to render as a PNG. For this, I used &lt;a href="https://pptr.dev"&gt;Puppeteer&lt;/a&gt; - a high level Node.js API for controlling headless Chrome.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`data:text/HTML,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HTMLHeader&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;tableState&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tmp/T&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;tableState&lt;/code&gt; contains the HTML representing table state retrieved from the &lt;code&gt;README.md&lt;/code&gt; file in the previous step. &lt;code&gt;HTMLHeader&lt;/code&gt; is a little boilerplate to apply GitHub's style.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;data:text/HTML,...&lt;/code&gt; is very interesting. It's called a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs"&gt;Data URL&lt;/a&gt;. Basically you can put any HTML after it and the browser will render it. For example, click on this URL: &lt;a href="https://dev.todata:text/HTML,&amp;lt;h1&amp;gt;hello,%20world!&amp;lt;/h1"&gt;&lt;code&gt;data:text/HTML,&amp;lt;h1&amp;gt;hello, world!&amp;lt;/h1&amp;gt;&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The spec recommends &lt;code&gt;text/html&lt;/code&gt; (lowercase) not &lt;code&gt;text/HTML&lt;/code&gt; (uppercase), but I was having issues rendering it correctly in this blog. Both seem to work.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Create animated GIF
&lt;/h2&gt;

&lt;p&gt;Finally, I used the excellent &lt;a href="https://github.com/eugeneware/gifencoder"&gt;&lt;code&gt;gifencoder&lt;/code&gt;&lt;/a&gt; package to generate an animated GIF from the list of PNG files that were generated from the previous step.&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;pngFileStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`tmp/T*.png`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;repeat&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="na"&gt;delay&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="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputGIF&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Result
&lt;/h1&gt;

&lt;p&gt;So here is the resulting animation showing how the game played out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3jF5pSyL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://commit-sudoku.surge.sh/output.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3jF5pSyL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://commit-sudoku.surge.sh/output.gif" alt="animation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 (Bonus): Hook up to TravisCI and Surge.sh
&lt;/h2&gt;

&lt;p&gt;I did not want to be running this script manually on my machine on every commit to update the animation. To begin with I had missed a few commits to the repo and that's why I started down this path in the first place. Luckily getting TravisCI to run this script on every commit (to master) was a breeze.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;after_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then cd animation &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; npm run generate; fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This tells TravisCI to only generate the animation if there's an actual commit to master and if all tests pass (i.e. Sudoku board is in a good state). &lt;/p&gt;

&lt;p&gt;Finally, I upload the resulting GIF to &lt;a href="https://surge.sh"&gt;Surge.sh&lt;/a&gt; - which offers simple static asset hosting - so that it can be showcased in the repo's &lt;code&gt;README.md&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SURGE_LOGIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxxxxxx@xxxx.com &lt;span class="nv"&gt;SURGE_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxxxxxxxxx npx surge folder my-endpoint.surge.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And that's it!&lt;/p&gt;

&lt;p&gt;There are a few details that were skipped in this post for brevity - but you can dig into the complete code &lt;a href="https://github.com/xiegeo/commit-sudoku/tree/master/animation"&gt;here&lt;/a&gt; - including how you can run it locally.&lt;/p&gt;

&lt;p&gt;Let me know what you thought of the post!&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>puppeteer</category>
      <category>sudoku</category>
      <category>gif</category>
    </item>
  </channel>
</rss>
