<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>molsson&apos;s blog</title>
    <description></description>
    <link>http://mo.github.io/</link>
    <atom:link href="http://mo.github.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 16 Oct 2026 07:25:23 +0000</pubDate>
    <lastBuildDate>Fri, 16 Oct 2026 07:25:23 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Migrating from MySQL to PostgreSQL</title>
        <description>&lt;p&gt;I recently migrated an old node webapp from MySQL to PostgreSQL. The application
is a little over 10k lines of code and does not use any ORM / SQL builder tool.
To convert the schema and move the data I used a little Common Lisp tool called
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pgloader&lt;/code&gt; which can convert from various other databases like MySQL, sqlite
or MSSQL.&lt;/p&gt;

&lt;p&gt;The reasons&lt;sup&gt;&lt;a href=&quot;#footnote1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; why I did this migration was:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;PostgreSQL feels a bit more stable / long term (I don’t like that Oracle bought
MySQL). This was the last side-project I had that was still using MySQL, so
now I don’t have to deal with MySQL anymore.
    &lt;ul&gt;
      &lt;li&gt;Porting this project to MongoDB would have been a lot more work and the
license situation is not ideal there and also I don’t like the “schemaless”
aspect of MongoDB.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;MySQL’s timestamp datatype has the 2032 problem and a long range of various
other similar little technical limitations.&lt;/li&gt;
  &lt;li&gt;PostgreSQL Ubuntu packages are quite well maintained. There are separate
packages for 9.4, 9.5, 9.6, 10, 11, 12 available in parallel on 18.04&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just installing pgloader using “sudo apt install pgloader” didn’t work because
it crashed and I found some GitHub issue that was fixed but the apt packaged
version did not have the fix yet. Instead I tried to run the docker version, but
it was quite painful to get that working. I had to reconfigure both mysql and
postgres to accept connections from any interface rather than just localhost,
then set a password for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postgres&lt;/code&gt; user (it doesn’t allow password
logins by default) and add a user in MySQL that was allowed to connect from a
remote IP. Finally, I had to use the docker host IP (visible on the docker0
interface if you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ip addr&lt;/code&gt;) both as the source and the destination. I
ended up running something like:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; pgloader dimitri/pgloader:latest pgloader &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    mysql://myuser:mysqlpasswo@172.17.0.1/sourceDbName &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    pgsql://postgres:passwordForPostgresUser@172.17.0.1/targetDbName
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Later, when I went through a similar process for a staging environment I
installed the latest version of pgloader from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt.postgresql.org&lt;/code&gt; instead
and that was a lot easier.&lt;/p&gt;

&lt;h1 id=&quot;migrating-the-source-code&quot;&gt;Migrating the source code&lt;/h1&gt;

&lt;p&gt;The first step was to convert the &lt;a href=&quot;https://www.npmjs.com/package/mysql&quot;&gt;Mysql
SQL&lt;/a&gt; placeholders from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt; to
&lt;a href=&quot;https://node-postgres.com&quot;&gt;PostgreSQL&lt;/a&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$N&lt;/code&gt; placeholders:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;SELECT * FROM users WHERE user_id = ?&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;SELECT * FROM users WHERE user_id = $1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Doing this manually, would be very tedious/boring so I wrote a program that
edited my source code instead. babel is a great tool for JavaScript
source-to-source transformation, but it doesn’t preserve source formatting when
it emits code. However, there is a great utility on npm called
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@codemod/cli&lt;/code&gt; that parses and emits code using
&lt;a href=&quot;https://github.com/benjamn/recast&quot;&gt;recast&lt;/a&gt; instead of the babels default
parser. I implemented my SQL migration utility as a babel plugin and I used the
excellent JavaScript &lt;a href=&quot;https://astexplorer.net&quot;&gt;AST Explorer&lt;/a&gt; to prototype the
first version. It was possible to implement the entire plugin in just 45 lines
of code:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/*
 Tiny babel plugin that helps migrate node Mysql SQL to Postgres SQL.
 Run via:
 npx -p @codemod/cli codemod --plugin ./scripts/mysql-to-postgres.js src/server/foo.js
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;needsMigration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ustr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toUpperCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;ustr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ustr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;SELECT&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;ustr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;INSERT&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;ustr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;ustr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;WHERE&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;ustr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;DELETE&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;migrateSql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\?&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;babel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;babel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mysql-to-postgres-sql-converter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;visitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;StringLiteral&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;needsMigration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replaceWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                        &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;StringLiteral&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;migrateSql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;TemplateElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;needsMigration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cooked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replaceWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                        &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;templateElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                                &lt;span class=&quot;na&quot;&gt;raw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;migrateSql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;raw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                                &lt;span class=&quot;na&quot;&gt;cooked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;migrateSql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cooked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                            &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This fixed all my SQL and preserved all source formatting but unfortunately it
also added a few semicolons. I was able to remove these automatically using
eslint though:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;eslint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;babel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eslint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ecmaVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;\&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sourceType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;no&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eslintrc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;no&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ignore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;\&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rule&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;quotes: [2, single]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After this I did a few manual fixups like (some fixes can be done using a tool like &lt;a href=&quot;https://aarrange.com/mysql-to-postgresql&quot;&gt;https://aarrange.com/mysql-to-postgresql&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Changed all my timestamp datatypes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp(3) with time zone&lt;/code&gt;. By default postgres has microsecond precision but that causes problems if you use node because the JavaScript &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Date&lt;/code&gt; can only represent milliseconds. If you really need microsecond precision you can use a custom type parser (see note at the bottom of &lt;a href=&quot;https://node-postgres.com/features/types&quot;&gt;this&lt;/a&gt; page).&lt;/li&gt;
  &lt;li&gt;Adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RETURNING ...&lt;/code&gt; on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT&lt;/code&gt; statements with auto incrementing ids.&lt;/li&gt;
  &lt;li&gt;Change a few &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT ... ON DUPLICATE KEY UPDATE&lt;/code&gt; into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT ... ON CONFLICT ...&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Fixed all references to the MySQL specific field &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.affectedRows&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Fixing references to camel case column names. pgloader &lt;a href=&quot;https://github.com/dimitri/pgloader/issues/649&quot;&gt;renames camel case columns&lt;/a&gt; to all lowercase since this is the convention on postgresql (you can enable case sensitive column names in postgres but I thought it was better to stay with postgresql default settings).&lt;/li&gt;
  &lt;li&gt;Fixed some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rand(someSeed)&lt;/code&gt; calls to transactions with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SET LOCAL SEED = someSeed&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Ported by web session handling from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;express-mysql-session&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connect-pg-simple&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Rewrote the setup / deploy scripts to install postgres and create a new database + schema.&lt;/li&gt;
  &lt;li&gt;Created a new blank &lt;a href=&quot;https://www.npmjs.com/package/db-migrate&quot;&gt;db-migrate&lt;/a&gt; seed.&lt;/li&gt;
  &lt;li&gt;Making sure that all E2E tests were passing again. I have a very good test
suite for this project, and it was quite amusing to see all the, sometimes
quite unexpected, places that broke when I changed the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the migration was done, I noted that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;PostgreSQL ended up taking just 94MB of RAM memory (RSS) versus the 194MB that
MySQL had required for the exact same data / workload (which is great because this project is deployed on a tiny Digital Ocean droplet with 1GB RAM).&lt;/li&gt;
  &lt;li&gt;I learned that PostgreSQL doesn’t support &lt;a href=&quot;https://stackoverflow.com/a/50090939/5169216&quot;&gt;logical column
ordering&lt;/a&gt; yet, which kind of
sucks.&lt;/li&gt;
  &lt;li&gt;PostgreSQL has a TODO page that lists things that are &lt;a href=&quot;https://wiki.postgresql.org/wiki/Todo&quot;&gt;not implemented yet&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;pg_dump emits SQL to lock down the search path to protect against &lt;a href=&quot;https://wiki.postgresql.org/wiki/A_Guide_to_CVE-2018-1058:_Protect_Your_Search_Path&quot;&gt;CVE-2018-1058&lt;/a&gt; and this ended up
causing lots of problems when I was setting up db-migrate package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;footnote&quot;&gt;&lt;a name=&quot;footnote1&quot;&gt;1&lt;/a&gt;:
Most people seem to prefer PostgreSQL, even though there are certainly examples of the &lt;a href=&quot;https://www.uber.com/us/en/blog/postgres-to-mysql-migration/&quot;&gt;opposite&lt;/a&gt; for certain very specific situations.
&lt;/p&gt;
</description>
        <pubDate>Tue, 09 Jul 2019 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2019/07/09/upgrading-node-mysql-to-postgres-sql-syntax.html</link>
        <guid isPermaLink="true">http://mo.github.io/2019/07/09/upgrading-node-mysql-to-postgres-sql-syntax.html</guid>
        
        <category>programming</category>
        
        <category>backend</category>
        
        
      </item>
    
      <item>
        <title>Introduction to Elasticsearch</title>
        <description>&lt;p&gt;Elasticsearch (ES) is a distributed open-source search and analytics engine
built on the text search library Apache Lucene. Given enough machines, an ES
cluster can typically execute search and aggregation queries across very large
datasets. While ES is great if you want traditional full text search over a set
of text documents (similar to Google search), it is also very useful for
searching and aggregating over large sets of more structured or semi-structured
datasets like system logs, network traffic data and collected system metrics.&lt;/p&gt;

&lt;p&gt;The very first version of Elasticsearch (0.4.0) was written by Shay Banon
(&lt;a href=&quot;https://github.com/kimchy&quot;&gt;kimchy&lt;/a&gt;) who is the current CEO of Elastic. The
very first version of ES was published on Github Feburary 8th 2010. Shay had
previously (2006-2010) developed another search related open source project
called Compass&lt;sup&gt;&lt;a href=&quot;#footnote1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; which was a high level API wrapping
Lucene that also added a few features. After 209 days, the first PR by another
developer was merged into ES, by that time kimchy had managed to land an
incredible 1145 additional commits (5.5 commits per calendar day). Clearly, Shay
knew very well both what he wanted to build and how it should be built. On 13th
Feb 2012, Shay created the company “Elasticsearch BV” together with Steven
Schuurman (who became the initial CEO), &lt;a href=&quot;https://github.com/uboness&quot;&gt;Uri Boness&lt;/a&gt;
and &lt;a href=&quot;https://github.com/s1monw&quot;&gt;Simon Willnauer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Steven Schuurman had previous co-founded various other companies. Before Elastic
he was the CEO of Orange11, a software company that amongst other things
developed enterprise search solutions based on Solr / Lucene. Uri Boness has
been working as “Software Architecture / Head of Search” at Orange11 and Simon
Willnauer was (and still is) a core committer in the Apache Lucene project.&lt;/p&gt;

&lt;p&gt;Two years later, on the 12th Feburary 2014, Elasticsearch 1.0 was released. Shay
Banon eventually took over the CEO role in early 2017, even though he was still
committing code up to mid-2015. In fact, a quick “git log” in the ES repo
reveals that the developer with the most commits is still, to this day, Shaw
Banon. Here is a graph showing “how many percent of ES commits to date that was
authored by kimchy” :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/elasticsearch-kimchy-commit-count-percent.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile, &lt;a href=&quot;https://github.com/jordansissel&quot;&gt;Jordan Sissel&lt;/a&gt; had been working on
Logstash (an open source ruby/java based ingestion tool that could process and
stash log data into multiple backends). Logstash predated Elasticsearch but
using Logstash with Elasticsearch became very common and Jordan joined Elastic
in 2013&lt;sup&gt;&lt;a href=&quot;#footnote2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. Similarly, Kibana was an open source project
for visualizing and working with Elasticsearch data. It started outside of
Elastic but &lt;a href=&quot;https://github.com/rashidkpc&quot;&gt;Rashid Khan&lt;/a&gt;, who worked for Elastic,
quickly became the lead developer. Because Elastic grew out of various open
source projects, it also grew into a very distributed company with employees in
over 35 countries and quite a few employees working remote. Elastic NV
eventually filed for an IPO in June 2018 and was listed on the New York Stock
Exchange in October 2018. The
&lt;a href=&quot;https://www.bloomberg.com/news/articles/2018-10-06/elastic-s-soaring-ipo-stretches-valuation-to-4-9-billion&quot;&gt;IPO&lt;/a&gt;
was at $36 per stock but they closed at $70 after the first trading day. The
ESTC market cap is 6.11B as of today, and at the time of the IPO, Schuurman
owned a 19 percent stake and Banon owned 12 percent.&lt;/p&gt;

&lt;h1 id=&quot;the-elastic-stack&quot;&gt;The Elastic stack&lt;/h1&gt;

&lt;p&gt;ES is often used together with Logstash and Kibana (forming the ELK-stack).
Logstash is a tool for processing data from log files and feeding them into ES
(it has plugins for inputs, transforms and outputs so it’s essentially an
&lt;a href=&quot;https://en.wikipedia.org/wiki/Extract,_transform,_load&quot;&gt;ETL&lt;/a&gt; tool), and Kibana
is a web UI that allows you to query ES using search queries in Lucene syntax.
Kibana also lets you aggregate data and render it into various charts and
visualizations. It is implemented in typescript with a node backend and React
for the frontend. The Elastic stack also includes a set of programs, referred to
as “Beats”, that feed various other kinds of data into ES (sometimes via
Logstash). Examples include
&lt;a href=&quot;https://www.elastic.co/products/beats/filebeat&quot;&gt;FileBeat&lt;/a&gt; (feeds file data like
logs into ES), &lt;a href=&quot;https://www.elastic.co/products/beats/metricbeat&quot;&gt;MetricBeat&lt;/a&gt;
(collects system metrics like CPU/network/memory usage and feeds it into ES) and
&lt;a href=&quot;https://www.elastic.co/products/beats/packetbeat&quot;&gt;PacketBeat&lt;/a&gt; (records network
traffic and feeds it into ES).&lt;/p&gt;

&lt;p&gt;This chart shows the number of unique git authors seen per 90 days for the major Elastic.co repositories:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/elasticsearch-active-authors-per-90-days.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The above chart includes some open source “one-off patching”, and if you instead
count the number of unique git authors that had 5 or more commits in each 90 day
window, you get &lt;a href=&quot;/assets/elasticsearch-active-authors-with-above-5-commits-per-90-days.png&quot;&gt;
this chart instead&lt;/a&gt; (I suspect this latter metric is better correlated with
the actual number of full time developers working on a project).&lt;/p&gt;

&lt;h1 id=&quot;example-how-the-elastic-stack-can-work-together-to-process-logs&quot;&gt;Example: How the Elastic stack can work together to process logs&lt;/h1&gt;

&lt;p&gt;When collecting system logs, a common setup is to run FileBeat on each machine
and have it send data to Logstash. Logstash will then parse the raw log files
into sets of field values to make them more search friendly (this is referred to
as “grokking”). For example, if your log lines have the following format:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2019-02-17 00:05:02,061 INFO FoobarComponent  Connecting to service at foo:1234
2019-02-17 00:05:03,038 DEBUG FoobarComponent  Loaded 60 objects.
2019-02-17 00:05:04,012 DEBUG CoreComponent  Finished initialization.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can use grokking to indicate that the first two “words” are a timestamp, the
third word is a log level, the fourth word is a component name and the rest is
the “log message”. You can define custom grokking using regexp patterns etc, or
you can rely on pre-defined grok patterns that ships with Logstash (it knows how
to parse common log file types like Apache access/error logs etc). Once the data
is sent to ES the log lines will have been translated to something like this
instead:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2019-02-17 00:05:02,061&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;severity&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INFO&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;component&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;FoobarComponent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Connecting to service at foo:1234&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the end, this makes it possible to find this data in Kibana (or
programmatically via ES) using a query like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;severity:INFO AND
message:connecting&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before FileBeat existed a lot of people ran Logstash directly on each node but
it’s written in Ruby + Java so it typically consumes quite a lot of memory
compared to FileBeat (which is implemented using the Go programming language).
Also FileBeat is more lightweight because, unlike Logstash, it only forwards
data; it doesn’t know how to parse hundreds of different log formats etc.
Another nice benefit is that the network protocol used between FileBeat and
Logstash has a notion of back-pressure so if the Logstash server gets overloaded
it can tell FileBeat to slow down a bit temporarily and spool the data.
Searching logs is of course just one use case for ES among many many others.&lt;/p&gt;

&lt;h1 id=&quot;why-non-full-text-search-queries-are-not-good-enough&quot;&gt;Why non “full text search” queries are not good enough&lt;/h1&gt;

&lt;p&gt;Before adding ES to your infrastructure it’s important to understand what
problem it solves and why. Suppose you want to implement full text search over a
SQL database of “products”, where each product has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.title&lt;/code&gt;,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.description&lt;/code&gt; and set of comments (stored in a different table). It’s not
really possible to implement search using a crude solution like:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;products&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;comments&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;product_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;product_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;%searchstring%&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OR&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;%searchstring%&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OR&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comment_text&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;LIKE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;%searchstring%&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are many reasons why the above query won’t work well. The database cannot
build an index for this so this type of query will be slow unless the table is
really small. If the user searches for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;word1 word2&lt;/code&gt; they will get no
results even if a product contains &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;word2 word1&lt;/code&gt; in the description. If the
user searches for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;satisfies&lt;/code&gt;, documents containing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;satisfy&lt;/code&gt; will not
be returned. Each item will either match the condition or it will not, the
database will not rank the search results. For example, if “productA” has the
search term both in its title and in its description but “productB” only had the
search term in its description, then it would be reasonable to expect that
“productA” would be returned as the top search result but the SQL query won’t
ensure that.&lt;/p&gt;

&lt;p&gt;In relational databases you should normalize the data, splitting it into many
tables and connecting them with foreign keys (this helps ensure that there is
one source of truth and that the data is consistent). However, when you
implement a search feature you typically want to search in several of these
fields / columns so you are often forced to JOIN the data which can be
expensive. If a row points at another row via a foreign key, those rows needs to
live on the same machine / shard for the JOIN to be efficient; so the more such
dependencies you have within the data, the more difficult it becomes to do
horizontal scaling / efficient automatic sharding.&lt;/p&gt;

&lt;h1 id=&quot;wait-what-about-the-built-in-full-text-search-in-my-rdbms-&quot;&gt;Wait, what about the built-in full text search in my RDBMS ?&lt;/h1&gt;

&lt;p&gt;If you dip into vendor specific SQL, most RDBMS have some sort of “full text
index” that you can create for a text column to mitigate the matching issues
above.&lt;/p&gt;

&lt;p&gt;For example, PostgreSQL actually offers two different kinds of full text search
indices. GIN based full text indexes are very fast for searching but slower to
build and take up more space (these are based on an “inverted index” just like
ES). GIST based indexes are slower for searching (but still pretty fast) while
providing faster inserts / updates. Creating and searching using a PostgreSQL
GIN index is quite straightforward:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;products&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;product_vectors&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tsvector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idx_fts_vec&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;products&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;USING&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;product_vectors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;products&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;product_vectors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;Nikon D5&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;The Nikon D5 is a full frame professional DSLR camera&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;to_tsvector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Nikon D5&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;to_tsvector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;The Nikon D5 is a full frame professional DSLR camera&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;to_tsvector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;wow super nice camera&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* &amp;lt;-- text from comments */&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* The actual search is done using: */&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;products&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;product_vectors&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to_tsquery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;searchword&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, if you already depend on the SQL database, then using the built-in full
text search saves you having to install, integrate, upgrade and maintain
separate software for handling search. Another benefit of using the built-in
full text search is that the database can automatically keep the full text index
up-to-date, just like it does with a regular database index. With Elasticsearch,
the application has to submit a re-indexing request to ES whenever the SQL data
changes (so there will typically be a short period of time where the data is out
of sync). On the other hand, rebuilding a GIN-index in PostgreSQL for example,
can be quite expensive and it really depends on your use case whether you want
this to happen immediately or not.&lt;/p&gt;

&lt;p&gt;If your dataset is small enough, or if search is not a primary focus for your
users; you might might get away with using built-in full text indices. For
example, Stackoverflow used&lt;sup&gt;&lt;a href=&quot;#footnote3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; this approach for the
first few years, before they switched to Elasticsearch.&lt;/p&gt;

&lt;h1 id=&quot;built-in-full-text-search-works-until-it-doesnt&quot;&gt;Built-in full text search works until it doesn’t&lt;/h1&gt;

&lt;p&gt;One of the main arguments against built-in full text indices is that the RDBMS
typically is the least scalable part of your infrastructure, so anything that
can move a significant fraction of the load elsewhere is valuable. Elasticsearch
also offers much better fine-tuning / customization of the full text indexing
process. This gives you more detailed control over which search terms should
match which documents (e.g. the matching of “satisfy” vs “satisfies” in the
above example).&lt;/p&gt;

&lt;p&gt;Another key benefit is that even if the data is normalized in the SQL server,
you can index it in denormalized form in Elasticsearch. From the example above,
above we can create a single “document” in ES that contains the product title,
description and all the comments for that product. In practice, this means that
with ES you get horizontal scaling that actually works.&lt;/p&gt;

&lt;p&gt;In general, you have to think carefully about:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;how much space are you willing to allocate for the index&lt;/li&gt;
  &lt;li&gt;how fast must the search queries execute&lt;/li&gt;
  &lt;li&gt;how important is horizontal scaling&lt;/li&gt;
  &lt;li&gt;how fine-tuned control over indexing / search matching do you need&lt;/li&gt;
  &lt;li&gt;how quick must the re-indexing be&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;competitors-to-elasticsearch&quot;&gt;Competitors to Elasticsearch&lt;/h1&gt;

&lt;p&gt;Even though Elasticsearch is extremely popular, there is of course other
software you can use for search. Just to give a super quick overview; other
search engine solutions include the open-source project &lt;a href=&quot;http://lucene.apache.org/solr/&quot;&gt;Apache
Solr&lt;/a&gt; and the outdated / discontinued
&lt;a href=&quot;http://sphinxsearch.com/&quot;&gt;Sphinx&lt;/a&gt; project as well as the closed source products
Splunk (on prem or SaaS) and Algolia (SaaS only). Many cloud providers also have
offerings in this space, e.g. Amazon CloudSearch. AWS also operates a hosted
Elasticsearch service which runs a forked version of ES (it’s not 100% API
compatible but most of the commonly used APIs are available). There are also a
few up-and-coming open source projects that look interesting but that cannot yet
be compared to Elasticsearch, for example
&lt;a href=&quot;https://github.com/tantivy-search/tantivy&quot;&gt;tantivy&lt;/a&gt; (search library similar to
Lucene, but implemented in rust) and
&lt;a href=&quot;https://github.com/toshi-search/Toshi&quot;&gt;Toshi&lt;/a&gt; (search engine implemented in
rust, uses tantivy). Two other ones are the search library
&lt;a href=&quot;https://github.com/blevesearch/bleve&quot;&gt;bleve&lt;/a&gt; (implemented in golang, named
after
&lt;a href=&quot;https://en.wikipedia.org/wiki/Boiling_liquid_expanding_vapor_explosion&quot;&gt;this&lt;/a&gt;)
and &lt;a href=&quot;https://github.com/mosuka/blast&quot;&gt;blast&lt;/a&gt; which is a server using Bleve. Also
note that the full-text search in Couchbase is powered by Bleve. Another nice
full text solution (in rust) is
&lt;a href=&quot;https://github.com/valeriansaliou/sonic&quot;&gt;Sonic&lt;/a&gt;. A few people have tried to
implement &lt;a href=&quot;https://wiki.apache.org/lucene-java/LuceneImplementations&quot;&gt;Lucene index-compatible search
libraries&lt;/a&gt; in
non-Java languages but none of them took off. &lt;a href=&quot;https://xapian.org&quot;&gt;Xapian&lt;/a&gt; is a
fairly mature search engine library (like Lucene) and
&lt;a href=&quot;https://kronuz.io/Xapiand/&quot;&gt;Xapiand&lt;/a&gt; is a server built around it.&lt;/p&gt;

&lt;p&gt;Another (larger) open source project that should be mentioned is
&lt;a href=&quot;https://vespa.ai&quot;&gt;Vespa&lt;/a&gt;, which was &lt;a href=&quot;https://www.reddit.com/r/programming/comments/72r7uq/yahoo_open_sources_its_search_engine_vespa/&quot;&gt;open sourced by
Yahoo&lt;/a&gt;
in September 2017. Vespa has a page in their documentation that &lt;a href=&quot;https://docs.vespa.ai/documentation/elastic-search-comparison.html&quot;&gt;compares it to
Elasticsearch&lt;/a&gt;.
Vespa was not used for the main Yahoo search engine but rather it was powering
the alltheweb.com search engine that Yahoo acquired in 2003 from Overture which
had in turn acquired it when it bought parts of the Norwegian company FAST
(other parts of this company was acquired by Microsoft). FAST itself had its
origin in the old “FTP Search” website operated by Tor Egge at NTNU. Finally,
there is also &lt;a href=&quot;https://github.com/manticoresoftware/manticoresearch&quot;&gt;manticore
search&lt;/a&gt;, which is an open
source project founded by some of the core members from Sphinx (and it started
as a fork of Sphinx).&lt;/p&gt;

&lt;p&gt;To visualize the size of these projects, here is the number git commits over
time (compared to ES). One thing to note is that Lucene and Solr lives in the
same git repository (and ES uses Lucene). Also, note that the first commit in
the Vespa repository is a 2.5 MLOC behemoth diff that represents the squashed
history up to that point (i.e. this codebase is literally &lt;em&gt;decades&lt;/em&gt; old).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/elasticsearch-open-source-competitors.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I also plotted the number of commits pushed to each project per 180 day window
(this says nothing about the quality or size of the commits ofc). It shows that
Vespa has a higher commit rate than ES right now:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/elasticsearch-open-source-commit-rates.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This final plot shows the number of git authors with 5 or more commits per 90
days (I use this as a proxy metric for counting the number of “full time
developers” working on a project). For example, the Xapiand project have
essentially 3 developers working on it:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/elasticsearch-open-source-author-counts.png&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;how-elasticsearch-is-licensed&quot;&gt;How Elasticsearch is licensed&lt;/h1&gt;

&lt;p&gt;In ES 6.2 and earlier, the Elasticsearch Github repository contained only
Elasticsearch and Elasticsearch was and is open source. On top of that, there
has always been a set of commercial plugins available as a separate download
called X-Pack. Even, critically important functionality like authentication and
TLS was part of X-Pack, which also contained functionality for alerting,
reporting and some machine learning features. For Elasticsearch 6.3, Elastic
decided to add all the X-Pack code into the main Elasticsearch git repository
but the new code was still not published under an open source license like the
rest of Elasticsearch. Shay Banon described the rationale for that change
&lt;a href=&quot;https://www.youtube.com/watch?v=gR3OhOnCMf8&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are various projects that tries to offer open source replacements for the
X-Pack plugins. For example, SearchGuard is an open source authentication plugin
for Elasticsearch, even though they eventually launched a paid version of their
open source plugin. There is an overview of various X-Pack alternatives
available &lt;a href=&quot;https://sematext.com/blog//x-pack-alternatives/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AWS offers a hosted Elasticsearch service which runs a slightly modified version
of Elasticsearch (which is API compatible with the open source version of ES for
the most common API calls). This service offers authentication for example, but
it does not have X-Pack installed. This service competes with the hosted cloud
service that Elastic themselves maintain where you get ES instances with X-Pack
preinstalled.&lt;/p&gt;

&lt;p&gt;In March 2019, AWS launched &lt;a href=&quot;https://opendistro.github.io/for-elasticsearch/&quot;&gt;Open Distro for
Elasticsearch&lt;/a&gt; which is
essentially a set of open source ES plugins that AWS uses to provide
security, alerting, SQL support and performance analysis tools.&lt;/p&gt;

&lt;h1 id=&quot;example-of-what-elasticsearch-offers-search&quot;&gt;Example of what Elasticsearch offers: Search&lt;/h1&gt;

&lt;p&gt;Elasticsearch offers synonymization, handling of typos, match highlighting, auto
completion and it can compute a relevance score for each search result
indicating how well that particular item matched the search query. In
particular:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Synonymization allows you to configure your indexing so that a document that
mentions a “courageous knight” is returned whenever a user searches for “brave
knight” and so on, based on custom or predefined lists of synonyms.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Typo handling ensures that a document that mentions something “outrageous” is
found when a user searches for “outragous” (or vice versa). ES also comes with
a “Phrase Suggester” that can be used to implement “Did you mean &lt;em&gt;XYZ&lt;/em&gt;”
suggestions to the end user.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Match highlighting gives you a machine readable map of which particular words
matched which search term (so that you can make them bold in the search
results or similar).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Auto complete can be implemented using ES in-memory “completion suggesters”.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Relevance scoring means that documents that match the search query very well
will be given a higher score and will be returned up top among the search
results.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not a good idea to rank documents based only on the number of search terms
they contain, because suppose you had a database of recipes and two of them were
titled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;How to make the best pasta ever&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Tasty Fudge&lt;/code&gt;. If a user
searches for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;how to make the tastiest fudge ever&lt;/code&gt; and you only ranked
results based on the number of search terms that matched, then the pasta recipe
would get the highest ranking because 5 words/terms are matching, whereas with
the fudge recipe there is only one match (or two if you have enabled stemming so
that tasty/tastiest count as a match). If the database has many recipies with
titles that begin with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;How to make&lt;/code&gt; then matches for the words &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;how&lt;/code&gt;,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;to&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt; are not going to be very useful for understanding which
document(s) the user is &lt;em&gt;really&lt;/em&gt; looking for.&lt;/p&gt;

&lt;p&gt;The problem is that matches for common words like “the” and “to” should not
increase document ranking as much as a match for the less common word “fudge”.
This idea is formalized in an algorithm called
&lt;a href=&quot;https://en.wikipedia.org/wiki/Tf–idf&quot;&gt;TF/IDF&lt;/a&gt; which stands for Term Frequency /
Inverse Document Frequency. This algorithm was the default algorithm for
relevance ranking in ES for many years. In practice though, extremely common
words like “the” was often not even indexed in the first place (such words are
referred to as stopwords) but the general idea holds; that matches for less
common terms cause higher relevance scores compared to hits for words that are
more common in the overall index. Other than term frequence and inverse document
frequency, Elasticsearch also computes something called the “field-length norm”
which means that if two documents both have a single match, but one of the
documents have the match in a shorter field, then that document gets slightly
higher relevance score. You can also customize relevance scores heavily, for
example you might want to let a “product rating” field weigh into the ranking.&lt;/p&gt;

&lt;p&gt;The current version of Elasticsearch uses another algorithm called &lt;a href=&quot;https://en.wikipedia.org/wiki/Okapi_BM25&quot;&gt;Okapi
BM25&lt;/a&gt; for computing relevance scores.
This algorithm is quite similar to TF/IDF but handles stop words more
gracefully.&lt;/p&gt;

&lt;h1 id=&quot;example-of-what-elasticsearch-offers-cardinality-estimation&quot;&gt;Example of what Elasticsearch offers: Cardinality Estimation&lt;/h1&gt;

&lt;p&gt;When a SQL database executes a query like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT COUNT(DISTINCT(type)) FROM
products&lt;/code&gt; it will insert all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.type&lt;/code&gt; values into a hash set and then count
the number of elements in the end. If the dataset had &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N&lt;/code&gt; values and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M&lt;/code&gt;
unique values, then this requires &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(M)&lt;/code&gt; memory and takes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(N)&lt;/code&gt; time
(since each hash set insert takes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(1)&lt;/code&gt; amortized). For high cardinality
sets, M might exhaust available RAM so that the database needs to sort it on
disk there which is very slow. Also, if you split the data into two parts and
determine the number of unique .type values in each part, that information
cannot be combined to deduce the number of unique .type values in the entire
dataset. If the set of unique values is so large that it needs to span across a
large cluster of machines, then the above algorithm is simply impracticable.
This problem is called the &lt;a href=&quot;https://en.wikipedia.org/wiki/Count-distinct_problem&quot;&gt;count-distinct
problem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are radically more efficient algorithms available, given that you are
willing to settle for approximately correct counts. For example, you could hash
all your items into integers and set them in a bitset (this has the same time
complexity but it has orders of magnitude better space complexity but of course
each hash collision would cause the final count to be slightly off); this is
called “linear counting”. An even better solution was published in 1984, namely
the &lt;a href=&quot;https://en.wikipedia.org/wiki/Flajolet–Martin_algorithm&quot;&gt;Flajolet–Martin
algorithm&lt;/a&gt;. Eventually
that work was refined into “LogLog Counting”, which in turn evolved into
&lt;a href=&quot;https://en.wikipedia.org/wiki/HyperLogLog&quot;&gt;HyperLogLog&lt;/a&gt; (HLL, the most commonly
used algorithm for this problem today) which was &lt;a href=&quot;http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf&quot;&gt;first
published&lt;/a&gt; in 2007 by
Philippe Flajolet et al. In general, HLL is a cornerstone of “big data” and
variants of this algorithm is used in Redis, Spark, Redshift, BigQuery etc. The
HLL commands in Redis are prefixed with “PF” in honor of &lt;a href=&quot;https://en.wikipedia.org/wiki/Philippe_Flajolet&quot;&gt;Philippe
Flajolet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you count the number of &lt;a href=&quot;http://highscalability.com/blog/2012/4/5/big-data-counting-how-to-count-a-billion-distinct-objects-us.html&quot;&gt;distinct words in Shakespeare’s
works&lt;/a&gt;
using a hash set, linear counting and HLL you get these results:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Counting Algorithm&lt;/th&gt;
      &lt;th&gt;Bytes Used&lt;/th&gt;
      &lt;th&gt;Count&lt;/th&gt;
      &lt;th&gt;Error&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;HashSet&lt;/td&gt;
      &lt;td&gt;10447016&lt;/td&gt;
      &lt;td&gt;67801&lt;/td&gt;
      &lt;td&gt;0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Linear Counting&lt;/td&gt;
      &lt;td&gt;3384&lt;/td&gt;
      &lt;td&gt;67080&lt;/td&gt;
      &lt;td&gt;1%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HyperLogLog (HLL)&lt;/td&gt;
      &lt;td&gt;512&lt;/td&gt;
      &lt;td&gt;70002&lt;/td&gt;
      &lt;td&gt;3%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The cardinality aggregation feature in Elasticsearch uses a slightly improved
HLL variant called HyperLogLog++, and this makes it possible to count the number
of distinct elements in a field on a per shard basis and then use the results of
those computations to provide an approximate total. This algorithm is explained
more in-depth by Adrien Grand (ES/Lucene committer) in &lt;a href=&quot;https://www.youtube.com/watch?v=eQ-rXP-D80U&amp;amp;t=24m56s&quot;&gt;this
video&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;nodes-shards-indices-and-routing&quot;&gt;Nodes, Shards, Indices and Routing&lt;/h1&gt;

&lt;p&gt;An Elasticsearch cluster consists of a set of nodes (typically a node is a
single machine running ES). Elasticsearch stores documents (essentially blobs of
JSON) into indices; an ES index is conceptually similar to SQL table. Each
document is marked with a built-in field called “_id” which uniquely identifies
that document. An ES cluster can have multiple indices, for example one for
“products” and one for “customers”. Each index is typically partitioned into
primary shards so that each document is stored in exactly one primary shard. For
example, you can load a document with ID &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UMNkYH&lt;/code&gt; from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;products&lt;/code&gt;
index using:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/products/_search?q=_id:UMNkYH&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When ES receives a “find by document id” request like this, it will hash the
document ID to determine which primary shard the document is stored in (this
process is called routing). Similarly, when a set of new documents are inserted
into the index, the uniformity of the hash function ensures that they are added
evenly across all shards. The number of shards is set when the index is created
and can not be changed, unless you create a new index and reinsert all your data
into it (which often ends up being a quite expensive operation). When ES
receives a regular search request like “find all products with a price below
100” or “find all documents that contain the words foo and bar”, it does not
know which shards contain the requested documents so it will instead broadcast
the query to all shards and then merge the results from each shard.&lt;/p&gt;

&lt;h1 id=&quot;how-to-execute-basic-queries&quot;&gt;How to execute basic queries&lt;/h1&gt;

&lt;p&gt;There are two formats that can be used when sending queries to ES. The curl
command in the previous section runs a &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html&quot;&gt;URI
search&lt;/a&gt;
and passes the search criteria via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q=&lt;/code&gt; parameter. The other format is
&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html&quot;&gt;request body
search&lt;/a&gt;
where the search criteria is passed as JSON in the request body instead. If you
rewrite the “find by id” query from above as a request body query you get:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/products/_search&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
       &lt;span class=&quot;nt&quot;&gt;--data-raw&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;query&quot;:{&quot;term&quot;:{&quot;_id&quot;:&quot;UMNkYH&quot;}}}&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ES returns JSON and you can get it pretty-printed by adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pretty&lt;/code&gt; as a URL param to the request:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/products/_search?q=price:42&amp;amp;pretty&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/products/_search?pretty&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
       &lt;span class=&quot;nt&quot;&gt;--data-raw&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;query&quot;:{&quot;term&quot;:{&quot;_id&quot;:&quot;UMNkYH&quot;}}}&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another (better) way to pretty print the JSON is to pipe it to
&lt;a href=&quot;https://stedolan.github.io/jq/&quot;&gt;jq&lt;/a&gt; or &lt;a href=&quot;https://github.com/antonmedv/fx&quot;&gt;fx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The syntax used within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;q=&lt;/code&gt; is very similar to the Lucene syntax you
enter in Kibana. Although, you have to keep URL escaping in mind so replace
space with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; etc. For example, if you want to find all products with
category set to “camera” and a price of 1999, use:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/products/_search?q=price:1999+AND+category:camera&apos;&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    jq &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; | less &lt;span class=&quot;nt&quot;&gt;-RSFX&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you need to query datetime fields with this syntax ES offers some basic
“date math” support that you can use within square brackets. However, note that
curl processes square brackets as shell globbing unless you pass the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-g&lt;/code&gt;
parameter to curl. So if you need to find all products postings with category
“camera” that was modified in the last 3 hours, use:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/products/_search?q=lastmodified:[now-3h+TO+now]&apos;&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; .hits.total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In ES versions before 5.x, a lot of people installed a plugin called
&lt;a href=&quot;https://github.com/lmenezes/elasticsearch-kopf&quot;&gt;kopf&lt;/a&gt;. This plugin allowed you
to get an overview of nodes, shards, indices and it also had a “rest” tab where
you could submit JSON queries. If the kopf plugin is installed, you can open it
by navigating to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es-hostname:9200/_plugin/kopf&lt;/code&gt;. In ES 5.x and later you
can use &lt;a href=&quot;https://github.com/lmenezes/cerebro&quot;&gt;cerebo&lt;/a&gt; which is essentially kopf
but as a standalone program (written by the same author).&lt;/p&gt;

&lt;p&gt;Another really nice way to submit JSON queries is via the “devtools console” in
Kibana.&lt;/p&gt;

&lt;h1 id=&quot;examples-of-a-few-other-es-api-calls&quot;&gt;Examples of a few other ES API calls&lt;/h1&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Show cluster name, health and node/shard count&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/_cluster/health&apos;&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{ cluster_name, status, number_of_nodes, active_primary_shards }&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# List available indices&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/_cat/indices&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# List available indices, with column captions&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/_cat/indices?v&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# List available indices, storage sizes in bytes instead of human readable units&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/_cat/indices?v&amp;amp;bytes=b&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# List nodes with current CPU/mem usage&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/_cat/nodes?v&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;replicas-and-cluster-health&quot;&gt;Replicas and cluster health&lt;/h1&gt;

&lt;p&gt;Each primary shard is stored on a single node, but each node can manage several
shards. If more nodes are added, the ES cluster automatically rebalances itself
by moving a few shards over to the newly added nodes. Each index also has a
replica count defined which ensures that for each primary shard there is also N
identical “replica shards” that are kept up to date at all times. For example,
if you set the replica count to 1, ES writes all data to both the primary shard
and the replica and it constantly works to ensure that the replica lives on a
different node from the primary. If a machine malfunctions ES can promote a
replica to primary or spawn a new replica. All incoming writes are executed on
both the primary and the replica shard, but reads can be served directly from a
replica shard so other than failover they also improve read performance.&lt;/p&gt;

&lt;p&gt;If ES is unable to maintain the configured replica count, the index health
status changes from green to yellow (and to red if data is lost). This also
means that the overall cluster health status changes. You can see the cluster
health status using the _cluster API call from the previous section. Tools like
kopf/cerebro make is quite clear that the cluster is unhealthy, for example
cerebro renders a the line underneath the main menu using the cluster health
status color. When the cluster switches from unhealthy to healthy this line goes
from yellow to green. When it’s yellow you typically also see some error message
saying that you have for example “15 unassigned shards”. The default replica
count is 1 so whenever you add data to ES on localhost for development purposes,
the cluster health immediately becomes unhealthy/yellow since the localhost
“cluster” only has a single node so ES cannot store the replica on a different
node from the primary. You can fix this by selecting the dropdown menu on the
index which has “unassigned shards”, then select “index settings” and set the
replica count to 0 (NOTE: only do this for localhost dev instances where you
don’t want replication).&lt;/p&gt;

&lt;h1 id=&quot;rollover-indices&quot;&gt;Rollover Indices&lt;/h1&gt;

&lt;p&gt;When you store immutable chronological data like logs (and also recorded
metrics), it’s quite common to write the data into an index where the name
contains a timestamp, for example logstash writes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logstash-yyyy.mm.dd&lt;/code&gt;
by default. If you want to delete the oldest data periodically you can do this
by deleting an entire index at once (which is much faster and more resource
efficient than trying to delete documents inside a single index). When you do
this, you would typically define an &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-templates.html&quot;&gt;index
template&lt;/a&gt;
in ES that specifies the settings that should be used whenever a new
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logstash-*&lt;/code&gt; index is created (i.e. the number of shards/replicas as well as
the field mappings and so on).&lt;/p&gt;

&lt;p&gt;People have been using setups like that for years, but there are some drawbacks
with it; for example since all write operations target the active index (and for
use cases like this often a majority of the read queries target the active index
as well) you often want more nodes backing the active index compared to older
indices. Also, it’s quite common that not “all days are the same” (e.g. you
might be process log data for a stock exchange that happens to be closed during
weekends). To mitigate these issues you can use the &lt;a href=&quot;https://www.elastic.co/blog/managing-time-based-indices-efficiently&quot;&gt;rollover
API&lt;/a&gt;
instead, which allows you to specify conditions for new index creation on a more
granular basis and since the data is immutable you can automatically &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-forcemerge.html&quot;&gt;force
merge&lt;/a&gt;
older indices and move them off hot nodes (assuming the cluster is setup with
&lt;a href=&quot;https://www.elastic.co/blog/hot-warm-architecture&quot;&gt;hot warm architecture&lt;/a&gt;).&lt;/p&gt;

&lt;h1 id=&quot;memory-for-es&quot;&gt;Memory for ES&lt;/h1&gt;

&lt;p&gt;Just like other large JVM applications, ES is sensitive to the Java heap limit
for &lt;a href=&quot;https://blog.codecentric.de/en/2014/02/35gb-heap-less-32gb-java-jvm-memory-oddities/&quot;&gt;compressed ordinary object
pointers&lt;/a&gt;
(oops). If you give a Java process 31gb or less memory it can use 32-bit
pointers and multiply them to by 8 to find the real address. If you give it
33gb, the JVM will start in 64-bit pointer mode instead and since ~20% of any
Java heap is object pointers, you will have less actual memory availble compared
to running with the 31gb limit. Running with limited Java heap is not a problem
since you can use more nodes instead. There is also an even more efficient mode
called “zero based compressed oops” and to use this mode you need ~26GB or less
Java heap. Also, as with other Java applications; resizing the heap at runtime
can be quite slow so it’s a good idea to run with identical min/max values, e.g.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-Xms31g -Xmx31g&lt;/code&gt;. By default, ES starts with 1GB heap which is pretty much
always wrong for production clusters, you can configure your JVM heap options in
the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/jvm.options&lt;/code&gt;. You can also configure which JVM GC to use in
this file, notably the Java 8 G1GC has less stop-the-world GC pauses compared to
the default CMS collector.&lt;/p&gt;

&lt;p&gt;Because Lucene uses quite a lot of disk-backed data structures, so it’s
important to leave memory (ideally at least 50% of physical RAM) for operating
system file caches. If you use a smaller Java heap for ES you get shorter GC
pauses and more memory for Lucene’s file system caches. If you won’t be
aggregating on analyzed string fields you can make the ES Java heap even smaller
than 50%. You should also &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration-memory.html&quot;&gt;disable
swapping&lt;/a&gt;,
either completely using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo swapoff -a&lt;/code&gt;, and/or by setting
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bootstrap.memory_lock: true&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;elasticsearch.yml&lt;/code&gt;. Even if you
allocate plenty of memory for file system caches, it’s often a good idea to use
fast SSD disks (and if you do, double check &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat
/sys/block/{DEVICE-NAME}/queue/scheduler&lt;/code&gt; so you’re not accidentally using
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cfq&lt;/code&gt; which is not suitable for SSDs). You can setup RAID 0 if you want (but
skip other RAID variants since failover is handled by replicas).&lt;/p&gt;

&lt;h1 id=&quot;the-inverted-index-vs-doc-values&quot;&gt;The Inverted Index vs Doc Values&lt;/h1&gt;

&lt;p&gt;The main data structure backing full text analyzed fields in ES is an inverted
index that maps terms to a set of postings (document IDs of documents containing
that term). For example:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;berry&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;postings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;88&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;fish&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;postings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;88&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;91&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;92&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;pasta&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;postings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;55&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;67&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;111&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;red&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;postings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;99&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;                 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;zucchini&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;postings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;121&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;                      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because the terms in the left column are sorted alphabetically, it’s possible to
find search terms quickly in that list and hence also the associated document
IDs. This is a good data structure for answering queries like “find documents
matching ‘red berries’” because ES can just normalize each search word using the
configured analysis algorithm (so that “berries” becomes “berry” for example),
and then lookup the matching documents. ES tracks various other information like
“term frequency” (number of documents in the index that contains the relevant
term) and “which character offset the term appeared at inside the field” etc.
Also on a lower level, Lucene considers the same string in different fields to
be different terms (i.e. a &lt;a href=&quot;https://lucene.apache.org/core/3_6_2/fileformats.html#Definitions&quot;&gt;Lucene
term&lt;/a&gt; is
essentially tuple of (FIELD_NAME, SOME_WORD)).&lt;/p&gt;

&lt;p&gt;For use cases where you instead want to sort, aggregate or use field values from
scripts, the inverted index is not a suitable data structure, but ES can also
store the data in another way, namely as &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/guide/current/_deep_dive_on_doc_values.html&quot;&gt;doc
values&lt;/a&gt;.
They are essentially an inverted inverted index because given a field name and a
document ID you can use the doc values data structure to efficiently retrieve
the value of that field in the given document. Doc values are supported for all
fields that are not analyzed strings (i.e. all field data types except “text”),
and they are enabled by default whenever they are supported. If you are
absolutely sure that you will never need to aggregate or sort based on a field
you can set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doc_values: false&lt;/code&gt; in the mapping and save a small amount of
disk space and gain some indexing speed. Note that doc values are stored in a
disk based data structures and read via the file system cache (just like the
Lucene indices) so they don’t bloat the ES Java heap.&lt;/p&gt;

&lt;p&gt;For example, imagine we have an ES index with recipes and each recipe has fields
for “title”, “cooking_instructions”, “minutes_required” as well as a
“recipe_type” field that indicates whether the recipe is a “entrée”, “main
course” or “dessert”. It makes sense to store “title” and “cooking_instructions”
as text fields (analyzed) so we can do full text search over them, but we will
store the “minutes_required” field as an integer so that we can sort the recipes
by how long they will take to prepare. For “recipe_type” we will use the
datatype “keyword” which is a non-analyzed string type, ensuring that we’ll have
doc values available so we can use this field for aggregation (e.g. calculate
the number of recipes of each “recipe_type”).&lt;/p&gt;

&lt;p&gt;However, we might also want to generate a list of recipes sorted by their
“title” but since “title” is a text field we cannot use it for sorting. You
could overcome this by indexing the recipe title into two different fields, one
with datatype “text” and another with datatype “keyword”) but this would cause
some storage overhead in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_source&lt;/code&gt; metafield. A better solution is to
declare a single multifield mapping that will store the data in both formats at
once, so instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;title&quot;: { &quot;type&quot;: &quot;text&quot; }&lt;/code&gt;, you would use a mapping
like:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fields&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;keyword&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;keyword&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Multifields like the above are also used by logstash by default which is why
you often see both “foobar” and “foobar.raw” when you query data in Kibana.&lt;/p&gt;

&lt;h1 id=&quot;fielddata&quot;&gt;FieldData&lt;/h1&gt;

&lt;p&gt;As outlined in the previous section, you should always use doc values for all
fields that you will use for aggregation and sorting. Strictly speaking though,
it is possible to execute an aggregation query for a field that contains
analyzed text, but often it is not a good idea to do this because A) it can lead
to memory related performance problems, and B) the result of such aggregation is
quite often not what the caller expected.&lt;/p&gt;

&lt;p&gt;The memory issues happens because analyzed text fields are stored as an inverted
tree (see earlier section) but aggregation would need a data structure similar
to doc values to run efficiently. So, when ES is asked to aggregate over a field
that contains analyzed text, it will (at query time!) invert the inverted tree
and then also cache the inverted inverted tree permanently on the JVM heap. This
in-memory data structure is called
&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/fielddata.html&quot;&gt;fielddata&lt;/a&gt;.
Unlike memory used by Lucene via the file cache, this fielddata cannot be
reclaimed by the OS if the machine runs low on memory.&lt;/p&gt;

&lt;p&gt;It’s sufficient for a single “one off” aggregation on analyzed text to generate
and permanently keep this fielddata in memory, after which Lucene will have less
memory to work with and every single GC will take longer to process. Logstash
tends to use multifields so you get both “component” and “component.raw” (where
the latter is using doc values) and a single innocent pie chart aggregation on
“component” could, in theory at least, permanently impair cluster performance.
In reality, this particular case is blocked since the analyzed text field
“component” would likely be marked with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;fielddata&quot; : { &quot;format&quot; :
&quot;disabled&quot;}&lt;/code&gt; which means that you’d get an error in Kibana if you tried to
generate such a pie chart of death. The right way is to aggregate based on
“component.raw” instead.&lt;/p&gt;

&lt;p&gt;You can see how much fielddata you have allocated for each node, by running:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;es-hostname:9200/_cat/fielddata?v&amp;amp;fields=*&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is possibe to clear the fielddata cache using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl -s
&apos;localhost:9200/_cache/clear?fielddata=true&apos;&lt;/code&gt; but this will of course cause
performance issues if your application relies on this data.&lt;/p&gt;

&lt;p&gt;When it comes to B) above (why aggregation on analyzed text often doesn’t
produce the desired result anyway), consider this this example:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; PUT localhost:9200/bad_recipes
curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; PUT &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     localhost:9200/bad_recipes/_mapping/_doc &lt;span class=&quot;nt&quot;&gt;--data-raw&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;
{
  &quot;properties&quot;: {
    &quot;title&quot;: {
        &quot;type&quot;: &quot;text&quot;,
        &quot;fields&quot;: {
            &quot;keyword&quot;: {
                &quot;type&quot;: &quot;keyword&quot;
            }
        }
    },
    &quot;recipe_type&quot;: {
      &quot;type&quot;: &quot;text&quot;,
      &quot;fielddata&quot;: true
    }
  }
}
&apos;&lt;/span&gt; | jq &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;s1&quot;&gt;&apos;localhost:9200/bad_recipes/_doc&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;--data-raw&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;title&quot;: &quot;first title&quot;, &quot;recipe_type&quot;: &quot;main course&quot; }&apos;&lt;/span&gt; | jq
curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;s1&quot;&gt;&apos;localhost:9200/bad_recipes/_doc&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;--data-raw&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;title&quot;: &quot;second title&quot;, &quot;recipe_type&quot;: &quot;dessert&quot; }&apos;&lt;/span&gt; | jq
curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;s1&quot;&gt;&apos;localhost:9200/bad_recipes/_doc&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;--data-raw&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;title&quot;: &quot;third title&quot;, &quot;recipe_type&quot;: &quot;weird&quot; }&apos;&lt;/span&gt; | jq
curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;s1&quot;&gt;&apos;localhost:9200/bad_recipes/_doc?refresh=wait_for&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;--data-raw&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;title&quot;: &quot;fourth title&quot;, &quot;recipe_type&quot;: &quot;weird&quot; }&apos;&lt;/span&gt; | jq
curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     localhost:9200/bad_recipes/_search &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
     &lt;span class=&quot;nt&quot;&gt;--data-raw&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;aggs&quot;:{&quot;my_agg_name&quot;:{&quot;terms&quot;:{&quot;field&quot;:&quot;recipe_type&quot;}}}}&apos;&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The final aggregation outputs the following buckets:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      ...
      &quot;buckets&quot;: [
        {
          &quot;key&quot;: &quot;weird&quot;,
          &quot;doc_count&quot;: 2
        },
        {
          &quot;key&quot;: &quot;course&quot;,
          &quot;doc_count&quot;: 1
        },
        {
          &quot;key&quot;: &quot;dessert&quot;,
          &quot;doc_count&quot;: 1
        },
        {
          &quot;key&quot;: &quot;main&quot;,
          &quot;doc_count&quot;: 1
        }
      ],
      ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That is, because the field contains analyzed text the value “main course” was
treated as the field having two values “main” and “course” and the aggregation
was computed accordingly (i.e. the counts are “wrong”).&lt;/p&gt;

&lt;p&gt;Because it is easy to shoot yourself in the foot with fielddata, it has been
disabled by default in recent ES versions, which is why the above example has
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;fielddata&quot;: true&lt;/code&gt; in the mapping. In older versions of ES (e.g. 2.x)
fielddata was enabled by default, and in ES 1.x doc values were also disabled by
default, making the pit of success even smaller.&lt;/p&gt;

&lt;p&gt;There are use cases where aggregation on analyzed text does give the correct
output; for example if you wanted to make a tag cloud (word cloud) visualization
so that words that appeared more often was rendered with a bigger font size etc
and you didn’t want to do this for the entire index but rather for a subset of
documents. Another such use case is &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-significantterms-aggregation.html&quot;&gt;significant terms
aggregation&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;refresh&quot;&gt;Refresh&lt;/h1&gt;

&lt;p&gt;When you write data into ES you cannot find it using a search until after the
next “refresh”. By default the refresh interval in ES is 1s, but this can be
tweaked to a higher value to give ES more room to optimize throughput. You can
see the current refresh interval for a given index using:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; es-hostname:9200/indexName/_settings &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
      | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;to_entries | .[0].value.settings.index.refresh_interval // &quot;1s&quot;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you write data to ES with curl you can pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?refresh=wait_for&lt;/code&gt; if you
want it to block until the data is visible to searches.&lt;/p&gt;

&lt;h1 id=&quot;more-about-elasticsearch&quot;&gt;More About Elasticsearch&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://thoughts.t37.net/designing-the-perfect-elasticsearch-cluster-the-almost-definitive-guide-e614eabc1a87&quot;&gt;Designing the Perfect Elasticsearch Cluster: the (almost) Definitive Guide&lt;/a&gt; by Fred de Villamil&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://fdv.github.io/running-elasticsearch-fun-profit/&quot;&gt;Running Elasticsearch for Fun and Profit&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dzharii/awesome-elasticsearch&quot;&gt;awesome-elasticsearch&lt;/a&gt; repo on GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;footnote&quot;&gt;&lt;a name=&quot;footnote1&quot;&gt;1&lt;/a&gt;:
&lt;a href=&quot;https://github.com/kimchy/compass&quot;&gt;https://github.com/kimchy/compass&lt;/a&gt;
&lt;/p&gt;
&lt;p class=&quot;footnote&quot;&gt;
    &lt;a name=&quot;footnote2&quot;&gt;2&lt;/a&gt;: Elasticsearch blog: &lt;a href=&quot;https://www.elastic.co/blog/welcome-jordan-logstash&quot;&gt;Welcome Jordan &amp;amp; Logstash&lt;/a&gt;
&lt;/p&gt;
&lt;p class=&quot;footnote&quot;&gt;
    &lt;a name=&quot;footnote3&quot;&gt;3&lt;/a&gt;: Marc Gravell, who works for Stackoverflow, described the reasons why they moved away from MSSQL full text search to Elasticsearch in this
    &lt;a href=&quot;https://stackoverflow.com/questions/20515069/elasticsearch-vs-sql-full-text-search/20515223#20515223&quot;&gt;comment (from 2013)&lt;/a&gt;. They periodically post details of their setup, including how &lt;a href=&quot;https://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/#search-elasticsearch&quot;&gt;they use Elasticsearch&lt;/a&gt;.
&lt;/p&gt;
&lt;p class=&quot;footnote&quot;&gt;
    &lt;a name=&quot;footnote4&quot;&gt;4&lt;/a&gt;: For example, MySQL has support for full text search via ```MATCH() ...
AGAINST``` but as soon as you combine such a search with a regular SQL condition
performance &lt;a href=&quot;https://hackernoon.com/dont-waste-your-time-with-mysql-full-text-search-61f644a54dfa&quot;&gt;suffers heavily&lt;/a&gt;.
&lt;/p&gt;

</description>
        <pubDate>Mon, 08 Apr 2019 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2019/04/08/elastic-search-introduction.html</link>
        <guid isPermaLink="true">http://mo.github.io/2019/04/08/elastic-search-introduction.html</guid>
        
        <category>programming</category>
        
        <category>backend</category>
        
        
      </item>
    
      <item>
        <title>End-to-end testing command line utilities</title>
        <description>&lt;p&gt;A quick and clean way to write end-to-end testcases for command line utilities
is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cram&lt;/code&gt; testing framework. You can install cram using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install
cram&lt;/code&gt; (or inside virtualenv), and then create a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test1.t&lt;/code&gt; with
the following contents:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;The first testcase

  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;abc
  abc

  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;a\nb\nc&apos;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;
  3

Launch /bin/false and verify that it exits with &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;code 1

  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;/bin/false
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;1]

Launch sub-shell and verify that it exits with &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;code 42

  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;abc &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;42&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  abc
  &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;42]

cram will verify that &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;code is 0 &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;no expected &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;code N is written as &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;N]

  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true

&lt;/span&gt;Testcases will be skipped &lt;span class=&quot;k&quot;&gt;if do &lt;/span&gt;you &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;exit 80&quot;&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;abc &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;80

cram can also invoke multiline commands

  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /proc/cpuinfo | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;bogo | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; 2 &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; : | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{s+=$1} END {print s}&apos;&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;
  1

You can also invoke multiple commands

  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ABC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;def
  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ABC&lt;/span&gt;
  def
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can then run the test using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cram test1.t&lt;/code&gt;. Another nice feature is the
interactive mode where you can automatically make the actual output become the
expected output (i.e. just like snapshot testing in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Writing tests with cram is extremely powerful because you have all your regular
command line tools available. For example, you can curl some API and assert that
the response looks reasonable.&lt;/p&gt;

&lt;p&gt;Kudos to &lt;a href=&quot;https://github.com/brodie&quot;&gt;Brodie Rao&lt;/a&gt; for creating this awesome tool!&lt;/p&gt;
</description>
        <pubDate>Sat, 29 Dec 2018 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2018/12/29/e2e-command-line-utils.html</link>
        <guid isPermaLink="true">http://mo.github.io/2018/12/29/e2e-command-line-utils.html</guid>
        
        <category>programming</category>
        
        <category>quicktip</category>
        
        
      </item>
    
      <item>
        <title>Detecting unused files with webpack</title>
        <description>&lt;p&gt;In a small (9 kloc) webapp I’m working on occasionally I typically use eslint
(or similar) to detect unused code and stylelint to find unused CSS. Having zero
issues reported is part of the automatic tests that I run on all code before
it’s pushed to git. However, I just found a whole unused file in my project
which these linters did not warn me about. Luckily, I also found a good way to
detect unused files in my pre-push quality check script: the
&lt;a href=&quot;https://github.com/MatthieuLemoine/unused-webpack-plugin&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unused-webpack-plugin&lt;/code&gt;&lt;/a&gt; webpack plugin.&lt;/p&gt;

&lt;p&gt;To use it, install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unused-webpack-plugin&lt;/code&gt; as a dev dependency and then add
something like this to your webpack.prod.config.js:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UnusedWebpackPlugin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;unused-webpack-plugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UnusedWebpackPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;directories&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;src/client&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)],&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;*.test.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;failOnUnused&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Fri, 28 Dec 2018 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2018/12/28/detect-unused-files-in-webpack.html</link>
        <guid isPermaLink="true">http://mo.github.io/2018/12/28/detect-unused-files-in-webpack.html</guid>
        
        <category>programming</category>
        
        <category>javascript</category>
        
        <category>web</category>
        
        <category>quicktip</category>
        
        
      </item>
    
      <item>
        <title>Episode recommendations for the Functional Geekery podcast</title>
        <description>&lt;p&gt;Lately, I’ve been listening to a podcast about functional programming called &lt;a href=&quot;https://www.functionalgeekery.com/&quot;&gt;Functional Geekery&lt;/a&gt;. I ended up eventually listening to the entire backlog of episodes, but if you
just want to see what the podcast is about, below you’ll find a list of good episodes. The first few minutes
usually lists a bunch of functional programming conferences, just skip past those if you’re listening to old
episodes.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-69-bartosz-milewski/&quot;&gt;Bartosz Milewski&lt;/a&gt;. Category theory.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-51-brian-lonsdorf/&quot;&gt;Brian Lonsdorf&lt;/a&gt;. Category theory, Frisby videos, JavaScript, PureScript.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-67-hardy-jones/&quot;&gt;Hardy Jones&lt;/a&gt;, Haskell, Elm, PureScript.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-57-mark-seemann/&quot;&gt;Mark Seemann&lt;/a&gt;, F#, Haskell, testing.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-61-susan-potter/&quot;&gt;Susan Potter&lt;/a&gt;, Haskell.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-17-jose-valim/&quot;&gt;Jose Valim&lt;/a&gt;, creator of Elixir.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-68-matthew-butterick/&quot;&gt;Matthew Butterick&lt;/a&gt;, racket, typography.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-54-edwin-brady/&quot;&gt;Edwin Brady&lt;/a&gt;, idris.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-66-scott-wlaschin/&quot;&gt;Scott Wlaschin&lt;/a&gt;, about &lt;a href=&quot;https://vimeo.com/113707214&quot;&gt;Railway Oriented Programming&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-98-scott-wlaschin/&quot;&gt;Scott Wlaschin&lt;/a&gt;, state, behavior, immutable data.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-83-rahul-muttineni/&quot;&gt;Rahul Muttineni&lt;/a&gt;, Haskell on the JVM and the Eta language.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-40-david-nolen/&quot;&gt;David Nolen&lt;/a&gt;, Clojure, ClojureScript, Om, React, GraphQL and Falcor.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-26-phil-freeman/&quot;&gt;Phil Freeman&lt;/a&gt;, PureScript creator.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-86-adelbert-chang/&quot;&gt;Adelbert Chang&lt;/a&gt;, scala implicits vs type classes.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-105-david-koontz/&quot;&gt;David Koontz&lt;/a&gt;, F#, Haskell, LambdaCast.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-111-stian-veum-mollersen/&quot;&gt;Stian Veum Møllersen&lt;/a&gt;, Perlis Languages, concatenative languages.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-47-john-de-goes/&quot;&gt;John de Goes&lt;/a&gt;, LambdaConf, PureScript.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://www.functionalgeekery.com/episode-48-matthias-felleisen/&quot;&gt;Matthias Felleisen&lt;/a&gt;, Scheme, Typed Racket.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are several other incredibly interesting episodes. These ones above are just a few ones that came to mind when I scrolled through the episode list again quickly. Also don’t miss the show notes, they are often a treasure trove of
links to good papers and videos.&lt;/p&gt;

&lt;p&gt;Huge thanks to Steven Proctor for creating this amazing podcast!&lt;/p&gt;
</description>
        <pubDate>Thu, 30 Nov 2017 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2017/11/30/functional-geekery-good-episodes.html</link>
        <guid isPermaLink="true">http://mo.github.io/2017/11/30/functional-geekery-good-episodes.html</guid>
        
        <category>programming</category>
        
        
      </item>
    
      <item>
        <title>AbortController polyfill for cancelling fetch()</title>
        <description>&lt;p&gt;While writing tests for a React app I ran into the following warning:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Warning: setState(...): Can only update a mounted or mounting component. This usually
means you called setState() on an unmounted component. This is a no-op. Please check
the code for the BoatyMcBoatface component.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This warning is emitted, for example, when a component starts a REST call (or timer etc) and then
gets unmounted before the call completes. Once the call does complete, it typically tries to do
setState() on the component, which is not legal after it has been unmounted.&lt;/p&gt;

&lt;p&gt;So the correct fix is to abort the REST call when the component unmounts, which is easy if you’re
using axios or some other &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XMLHttpRequest&lt;/code&gt; wrapper. The problem was that I was using native
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch()&lt;/code&gt; which doesn’t have support for aborting the request (yet). I googled around for the
status on cancellation and it turns out that just a few days ago a new API was been
added&lt;sup&gt;&lt;a href=&quot;#footnote1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; to the DOM specification for this. This new API is a smaller scope
compared to cancellation of promises for which discussions stalled a bit early this year.&lt;/p&gt;

&lt;p&gt;This &lt;a href=&quot;https://dom.spec.whatwg.org/#aborting-ongoing-activities&quot;&gt;new DOM API&lt;/a&gt; allows you to create an
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbortController&lt;/code&gt; that in turn allows you to pass an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbortSignal&lt;/code&gt; into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch()&lt;/code&gt;
call. Later on you can call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.abort()&lt;/code&gt; on the controller to cancel the request. If you used the
new API from a React application, it would look like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;componentDidMount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AbortController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;signal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`/api/thing?id=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;thingId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;actualThing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;actualThing&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;actualThing&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;AbortError&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// It&apos;s important to rethrow all other errors so you don&apos;t silence them!&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// For example, any error thrown by setState(), will pass through here.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;componentWillUnmount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I wanted to try this new API in my application, so I created a polyfill for it. The polyfill does
not actually close the TCP connection on abort, it just throws the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbortError&lt;/code&gt; as seen above
and then when the response arrives it is dropped silently (the real implementation will close the
TCP socket though of course). This allows you to target the new API today and once the API is
implemented in browsers you can just drop the polyfill and not modify your code. The polyfill is
available here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/abortcontroller-polyfill&quot;&gt;abortcontroller-polyfill on NPM&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mo/abortcontroller-polyfill&quot;&gt;mo/abortcontroller-polyfill on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re using webpack, you simply &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install --save abortcontroller-polyfill&lt;/code&gt; and then
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import &apos;abortcontroller-polyfill&apos;;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require(&apos;abortcontroller-polyfill&apos;);&lt;/code&gt; near the top
of your client entrypoint .js or just include &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;abortcontroller-polyfill&apos;&lt;/code&gt; as an entrypoint
itself (before your own entrypoint).&lt;/p&gt;

&lt;p&gt;When I was googling around for the status on cancellation, I also noticed that Ron Buckton is
presenting a very ambitious &lt;a href=&quot;https://github.com/tc39/proposal-cancellation#readme&quot;&gt;proposal&lt;/a&gt;
(&lt;a href=&quot;https://tc39.github.io/proposal-cancellation/CancellationPrimitives-tc39.pptx&quot;&gt;slides&lt;/a&gt;) at a &lt;a href=&quot;https://github.com/tc39/agendas/blob/master/2017/07.md&quot;&gt;TC39
meeting&lt;/a&gt; (which happens to be today!) that
aims to unify cancellation, not just for DOM but for asynchronous functions, asynchronous iterators,
webworkers, animation etc in such a way that they can be both synchronous and asynchronously
observed (i.e. asking “was it cancelled?” verus “tell me if it cancels”). This work is based on
cancellation in managed threads in the .NET Framework.&lt;/p&gt;

&lt;p class=&quot;footnote&quot;&gt;&lt;a name=&quot;footnote1&quot;&gt;1&lt;/a&gt;: The final version of &lt;a href=&quot;https://dom.spec.whatwg.org/#aborting-ongoing-activities&quot;&gt;AbortController&lt;/a&gt; has been added
to the DOM specification. The corresponding &lt;a href=&quot;https://github.com/whatwg/fetch/pull/523&quot;&gt;PR
for the fetch specification&lt;/a&gt; is essentially done but not technically merged yet, waiting for
testcases to be merged into web-platform-tests via &lt;a href=&quot;https://github.com/w3c/web-platform-tests/pull/6484&quot;&gt;PR #6484&lt;/a&gt;. Browser bugs tracking the implementation of AbortController is available here: Firefox: &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1378342&quot;&gt;#1378342&lt;/a&gt;, Chromium: &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=750599&quot;&gt;#750599&lt;/a&gt;, WebKit: &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=174980&quot;&gt;#174980&lt;/a&gt;, Edge: &lt;a href=&quot;https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/13009916/&quot;&gt;#13009916&lt;/a&gt;.

&lt;/p&gt;
</description>
        <pubDate>Mon, 24 Jul 2017 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2017/07/24/abort-fetch-abortcontroller-polyfill.html</link>
        <guid isPermaLink="true">http://mo.github.io/2017/07/24/abort-fetch-abortcontroller-polyfill.html</guid>
        
        <category>javascript</category>
        
        <category>web</category>
        
        
      </item>
    
      <item>
        <title>Javascript tools for end-to-end testing web applications</title>
        <description>&lt;p&gt;Earlier, I compared&lt;sup&gt;&lt;a href=&quot;#footnote1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; a few &lt;a href=&quot;/2017/06/05/javascript-unit-testing.html&quot;&gt;different tools for unit testing Javascript
code&lt;/a&gt;. This post reviews available tools for end-to-end
testing web applications using Javascript (i.e. tests that click buttons, type text etc). Many UI
frameworks comes with a tool for this, but I’m looking specifically for a framework neutral tool so
I’m not considering for example &lt;a href=&quot;http://www.protractortest.org&quot;&gt;Protractor&lt;/a&gt; (Angular),
&lt;a href=&quot;https://teamcapybara.github.io/capybara/&quot;&gt;Capybara&lt;/a&gt; (Ruby on Rails),
&lt;a href=&quot;http://www.ember-cli-mirage.com/&quot;&gt;Mirage&lt;/a&gt; (Ember) etc.&lt;/p&gt;

&lt;p&gt;I want my E2E tests to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;smoke test all layers of the application running together (frontend/backend)&lt;/li&gt;
  &lt;li&gt;catch browser specific issues (even though browser bugs per se are getting increasingly rare, you
typically need to verify that your polyfills are complete and working in each browser for example)&lt;/li&gt;
  &lt;li&gt;click through parts of the application that are not current being changed/developed to guard against regressions when shared components changes&lt;/li&gt;
  &lt;li&gt;always report any JS exceptions (if I have assertions verifying correctness for the most important part of a page/screen, I still want a systematic check that no JS exceptions happened for example when some less important “below the fold” part of the page loaded. I don’t want full coverage with assertions in E2E tests, I prefer to push as much as possible to component level tests)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;why-should-you-implement-e2e-tests-in-javascript&quot;&gt;Why should you implement E2E tests in Javascript?&lt;/h1&gt;

&lt;p&gt;A web application end-to-end testcase will send commands to a real browser and wait for it to
complete before sending the next command. Commands are often (but not always) sent to the browser
via Selenium or at least the &lt;a href=&quot;https://www.w3.org/TR/webdriver/&quot;&gt;Webdriver API&lt;/a&gt;. This means that the
testcases are, by their nature, a long string of asynchronous operations. Given the “sort of
single-thread” nature of Javascript, there is a potential for “callback hell” issues. Java on the
other hand can execute many testcases in parallel using OS threads and block for each asynchronous
operation, thus providing a very easy to use API. On top of that Selenium itself is written in Java,
so it’s fair to ask why not just write E2E tests in Java?&lt;/p&gt;

&lt;p&gt;If your backend is not JVM-based (e.g. Node/Python/golang/PHP), it’s inconvenient to force all
developers to install a JRE/JDK, IDEA or Maven/Gradle solely for E2E tests (if you target the
webdriver binary directly, you don’t even need a JRE installed, although some cloud testing
providers still require you to go via a Selenium server). Even if you have a JVM-based backend
already, it’s much faster to iterate on E2E tests written in Javascript (you certainly don’t want to
wait for a slow Maven multi-module reactor build) and your testcases doesn’t need HotSpot JIT
anyway. On top of that, if your E2E tests execute in Node rather than in a browser, it’s possible to
use coroutines implemented by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-fibers&lt;/code&gt; package to create a API that looks and feels
synchronous. This allows you to put breakpoints half way into your testcase etc, which is a bit
trickier with &lt;a href=&quot;/2017/07/18/fluent-chained-api-asynchronous-functions-async-methods-Javascript.html&quot;&gt;APIs with chained asynchronous
methods&lt;/a&gt; (which
is also used quite frequently among E2E test frameworks implemented in Javascript).&lt;/p&gt;

&lt;h1 id=&quot;what-are-the-available-optionstools-for-ui-automation-in-js&quot;&gt;What are the available options/tools for UI automation in JS?&lt;/h1&gt;

&lt;p&gt;Among the tools/frameworks that comes up when you google for “end to end testing”, I immediately
ruled out a few, like &lt;a href=&quot;http://phantomjs.org/&quot;&gt;PhantomJS&lt;/a&gt;/&lt;a href=&quot;http://casperjs.org/&quot;&gt;CasperJS&lt;/a&gt; (not a
real browser, about to be replaced by headless Chrome anyway). &lt;a href=&quot;http://zombie.js.org/&quot;&gt;Zombie.js&lt;/a&gt;
does not test in a real browser and while impressive it looks a lot like a one man project that went
into maintenance mode. However, Zombie.js is based on a really good idea (getting rid of slowness
and flakiness by targetting jsdom). It would be nice if this approach was combined with full browser
testing so that the testcases first ran really fast using jsdom (to make it quick to iterate and
develop new testcases) and once they passed there, all testcases would run again but inside all the
target browsers. This was attempted in &lt;a href=&quot;https://github.com/ForbesLindesay/taxi-rank&quot;&gt;taxi-rank&lt;/a&gt;,
which is based on &lt;a href=&quot;https://cabbiejs.org/&quot;&gt;cabbie&lt;/a&gt;. However, there wasn’t a lot of good
articles/examples/documentation about these and NPM downloads are super low so I didn’t spend any
time on them, but I like the idea.&lt;/p&gt;

&lt;p&gt;Another really nice project is &lt;a href=&quot;https://github.com/segmentio/nightmare&quot;&gt;NightmareJS&lt;/a&gt; that runs UI
automation inside Electron (so it’s much closer to Chromium compared to PhantomJS). It’s much faster
than Selenium and avoids a lot of the messy configuration and flakiness that comes with trying to
launch a real browser. I’ve used it for a lot of things, but for the purposes of this comparison I’m
not including it since it cannot run testcases inside a real browser (and I explicitly want to test
in Firefox etc). &lt;a href=&quot;http://dalekjs.com/&quot;&gt;Dalek.js&lt;/a&gt; can test in a browser but the project was never
finished and is not maintained anymore. &lt;a href=&quot;http://docs.busterjs.org/en/latest/#&quot;&gt;Buster.js&lt;/a&gt; and
&lt;a href=&quot;https://github.com/totorojs/totoro&quot;&gt;TotoroJS&lt;/a&gt; were also abandoned/unmaintained and didn’t pass a 5
second sniff test.&lt;/p&gt;

&lt;p&gt;I saw a few people tweeting about Cypress.io so I included it although it’s in closed beta (not
technically open source right now but they claim that will change fairly soon).&lt;/p&gt;

&lt;h1 id=&quot;which-e2e-toolsframeworks-were-compared&quot;&gt;Which E2E tools/frameworks were compared?&lt;/h1&gt;

&lt;p&gt;The full list of tools that I ended up trying out was:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;License&lt;/th&gt;
      &lt;th&gt;API style&lt;/th&gt;
      &lt;th&gt;Issue Latency &lt;sup&gt;&lt;a href=&quot;#footnote2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
      &lt;th&gt;Open / All Issues &lt;sup&gt;&lt;a href=&quot;#footnote2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://seleniumhq.github.io/selenium/docs/api/javascript/index.html&quot;&gt;selenium-webdriver&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Apache-2.0&lt;/td&gt;
      &lt;td&gt;fake sync or async/await&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/admc/wd&quot;&gt;wd.js&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Apache-2.0&lt;/td&gt;
      &lt;td&gt;multiple&lt;/td&gt;
      &lt;td&gt;1 day&lt;/td&gt;
      &lt;td&gt;7%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;http://nightwatchjs.org/&quot;&gt;Nightwatch.js&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;async chain&lt;/td&gt;
      &lt;td&gt;1 days&lt;/td&gt;
      &lt;td&gt;12%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;http://webdriver.io/&quot;&gt;Webdriver.io&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;coroutine sync&lt;/td&gt;
      &lt;td&gt;12 hours&lt;/td&gt;
      &lt;td&gt;4%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://chimp.readme.io&quot;&gt;Chimp&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;coroutine sync&lt;/td&gt;
      &lt;td&gt;75 days&lt;/td&gt;
      &lt;td&gt;19%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;http://nemo.js.org/&quot;&gt;Nemo&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Apache-2.0&lt;/td&gt;
      &lt;td&gt;fake sync&lt;/td&gt;
      &lt;td&gt;10 days&lt;/td&gt;
      &lt;td&gt;16%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;http://codecept.io&quot;&gt;CodeceptJS&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;coroutine sync&lt;/td&gt;
      &lt;td&gt;30 days&lt;/td&gt;
      &lt;td&gt;19%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://theintern.github.io/&quot;&gt;Intern&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;New BSD&lt;/td&gt;
      &lt;td&gt;async chained&lt;/td&gt;
      &lt;td&gt;26 days&lt;/td&gt;
      &lt;td&gt;10%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://devexpress.github.io/testcafe/&quot;&gt;TestCafe&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;await single chain&lt;/td&gt;
      &lt;td&gt;7 days&lt;/td&gt;
      &lt;td&gt;16%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://cypress.io&quot;&gt;Cypress&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;async chained&lt;/td&gt;
      &lt;td&gt;49 days&lt;/td&gt;
      &lt;td&gt;32%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Don’t read to much into the issue latency numbers without checking the issues yourself, for example
I opened a &lt;a href=&quot;https://github.com/cypress-io/cypress/issues/563&quot;&gt;bug&lt;/a&gt; with Cypress recently and they
triaged it and assigned it to a developer the same day and it was fixed 24 hours later.&lt;/p&gt;

&lt;p&gt;If you render the monthly downloads for each of these NPM packages (and protractor as well for
reference), you get this plot:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/javascript-e2e-integration-testing-monthly-npm-downloads.png&quot;&gt;
  &lt;img src=&quot;/assets/javascript-e2e-integration-testing-monthly-npm-downloads.png&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The protractor package depends on selenium-webdriver, so that drives selenium-webdriver downloads
quite a bit. In general, NPM download statistics should be taken with a grain of salt due to mirrors
and download bots, downloads by CI systems and cached installs. And you cannot tell if a package has
high download numbers because IT is popular or if it’s because it has a popular package depending on
it. Note that nemo.js depends on selenium-webdriver, Chimp depends on WebdriverIO and
CodeceptJS depends on protractor, webdriverio and selenium-webdriver (it can execute using multiple
backends).&lt;/p&gt;

&lt;p&gt;If we zoom into the weeds, we see that during 2017 alone CodeceptJS and TestCafe both enjoyed &amp;gt;100%
increase in downloads whereas Chimp and Intern are largely constant. Just for fun, I also included
“nightmare” in the chart, it certainly has been gaining a lot popularity during 2017 (currently at
4x of where it was in october last year).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/javascript-e2e-integration-testing-monthly-npm-downloads-zoomed.png&quot;&gt;
  &lt;img src=&quot;/assets/javascript-e2e-integration-testing-monthly-npm-downloads-zoomed.png&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another interesting thing is the sharp drop in downloads for nemo; but this is partly due to
Paypal setting up an internal NPM mirror (they still use nemo).&lt;/p&gt;

&lt;p&gt;Another interesting thing to look at is the number of commits in the git repository for each of
these test tools. Cypress isn’t open source yet so I couldn’t include it. selenium-webdriver lives
in the main selenium repository inside a directory called “javascript” so I did a quick git
filter-branch on that directory and measured only commits that touched that directory.&lt;/p&gt;

&lt;p&gt;Anyway, if you plot the commit count over time you get this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/javascript-e2e-integration-testing-commit-count.png&quot;&gt;
  &lt;img src=&quot;/assets/javascript-e2e-integration-testing-commit-count.png&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The git data goes back a lot longer than the NPM downloads data, maybe wd.js had more NPM downloads
during the period when it also had a lot of commits landing. A lot of work was done on wd.js from
2012 to 2014 (FWIW; the main author of wd.js is Adam Christian, who worked for Sauce Labs 2010 to
2015). Webdriver.io started to pick up speed in 2013, and unlike wd.js, is still going strong. It’s
headed by Christian Bromann (who works for, you guessed it, Sauce Labs).&lt;/p&gt;

&lt;p&gt;Another notable thing in the commit count chart above, is the steep increase for Intern starting in
April; this is Jason Cheatham kicking off development of Intern v4 (more on that later).&lt;/p&gt;

&lt;p&gt;Below is another plot of the same data; showing commits per year for the three major webdriver
implementations and I’ve also added in leadfoot which is an independent webdriver implementation
used by Intern (they used wd.js in v1 before developing leadfoot):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/javascript-e2e-integration-testing-webdriver-commits-per-year.png&quot;&gt;
  &lt;img src=&quot;/assets/javascript-e2e-integration-testing-webdriver-commits-per-year.png&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another great way to estimate how many people are invested in a certain technology is to analyze how
the number of active developers changes over time. To visualize this, I’ve plotted the number of
unique developers seen in the git log during a rolling 365 day window:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/javascript-e2e-integration-testing-active-devs.png&quot;&gt;
  &lt;img src=&quot;/assets/javascript-e2e-integration-testing-active-devs.png&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This shows strong community momentum for webdriver.io and to some extent CodeceptJS.&lt;/p&gt;

&lt;p&gt;I also tried to write a few testcases using each framework. I created a small test page that had a
button which started a timer and then eventually made a div visible with some text, and also some
other easy scenarios. The test page could also be configured to be buggy, and then various bad
things would happen like the element not being added to DOM, some JS error would occur even though
the page appeared otherwise to be working, or the element was added to the DOM but inside another
div that had overflow:hidden and the child div was then placed outside of the cliprect of the parent
making it invisible while still not having &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display: none&lt;/code&gt;. I wanted to see how each framework
responded to these “bugs” and how easy it was to understand the error messages / test results etc.&lt;/p&gt;

&lt;p&gt;Below are some code examples (in reality you should of course not be hardcoding selectors like this,
but instead use the &lt;a href=&quot;https://martinfowler.com/bliki/PageObject.html&quot;&gt;page object pattern&lt;/a&gt; or
something similar). I’ve also included a few notes for each framework.&lt;/p&gt;

&lt;h1 id=&quot;selenium-webdriver&quot;&gt;selenium-webdriver&lt;/h1&gt;

&lt;p&gt;For selenium-webdriver, I used regular MochaJS as the runner. I disabled the &lt;a href=&quot;https://github.com/SeleniumHQ/selenium/wiki/WebDriverJs&quot;&gt;global promise
manager&lt;/a&gt; that is used to provide the older
“fake sync” API, and I used async/await instead (without transpilation! Yay, Node v8!). The
testcases looked sort of like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scenario 3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;file://&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../../test-pages/index.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;//*[text()=&quot;button3&quot;]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elementIsVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)));&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;actualElement&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ELEMENT_TIMEOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;actualElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden3 pass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.get()&lt;/code&gt; call blocks until the page has loaded fully so normally you can just do for example
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;driver.findElement(By.name(&apos;q&apos;)).sendKeys(&apos;webdriver&apos;)&lt;/code&gt; but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#hidden3&lt;/code&gt; above was added by a
timer after the button was pressed so I needed to add the explicit wait.&lt;/p&gt;

&lt;p&gt;Selenium and WebDriver in general doesn’t have protocol support for streaming JS errors via the
driver, which is a shame because it would be so useful! However, there is support in the protocol
for retrieving the JS console log at least, so I was able to assert that there were no JS errors, by
using this clunky hack:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scenario 2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;file://&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../../test-pages/index.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.button2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hidden2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elementIsVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)));&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hidden2actual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hidden2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ELEMENT_TIMEOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hidden2actual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden2 pass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;browserLog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;manage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;BROWSER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errors&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;browserLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;SEVERE&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;[%s] %s&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;JS errors in browser log!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re going to use selenium-webdriver, then you should be using this modern async/await API that
I showed above, but there is also an older “fake sync” API that looked like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;http://www.google.com/ncr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sendKeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;webdriver&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;btnG&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;titleIs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;webdriver - Google Search&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;quit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;all done&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This looks synchronous but it is not, instead each of the methods will append their action to a
chain of promises that won’t even start until the next runslice. This means that the code will first
print “all done” and then it will begin the navigation to google.com. It also means that you cannot
stop the testcase halfway through by inserting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debugger&lt;/code&gt; between two lines. You can call it
from inside a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;then&lt;/code&gt;, chained after another line, like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;http://www.google.com/ncr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;debugger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In general, my impression of selenium-webdriver was quite bad because not even the “hello world”
example would run due to &lt;a href=&quot;https://github.com/SeleniumHQ/selenium/issues/4041&quot;&gt;#4041&lt;/a&gt; and while this
fix bug was fixed by Lucas Tierney (kudos!) unfortunately no new release has been pushed to NPM for
this critical bug (it’s been &amp;gt;2 months now).&lt;/p&gt;

&lt;p&gt;Another drawback for “selenium-webdriver” is the generic name it has. It’s very hard to google for
examples and help for this tool because you keep hitting articles about selenium webdriver in C#,
Java, Ruby etc etc. This was actually surprisingly frustrating.&lt;/p&gt;

&lt;h1 id=&quot;wdjs&quot;&gt;wd.js&lt;/h1&gt;

&lt;p&gt;wd.js offers multiple APIs; one pure async API for those who are really into indentation, one
non-chained API based on Q promises for people who enjoy writing .then() a lot, one based on
generators and of course one API based on chained async calls (also using Q promises). Their readme
doesn’t mention any examples using async/await, though it’s probably not hard to setup if you want
it. I thought it was quite interesting to look at all of these API examples in their readme.&lt;/p&gt;

&lt;p&gt;Code using the chained Q API looks like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;browserName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;http://admc.io/wd/test-pages/guinea-pig.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;WD Tests&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;i am a link&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;window.location.href&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventually&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;guinea-pig2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elementByCss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#comments&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Bonjour!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;become&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Bonjour!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;quit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The commits per year plot earlier suggests that webdriverio has much stronger momentum compared to
wd.js so I didn’t bother to write my own testcase for wd.&lt;/p&gt;

&lt;h1 id=&quot;nightwatchjs&quot;&gt;nightwatch.js&lt;/h1&gt;

&lt;p&gt;Nightwatch offers a async chained API and runs on top of a built-in (forked) mocha version. A test
could look like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scenario3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;file://&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../../../test-pages/index.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;useXpath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;//*[text()=&quot;button3&quot;]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;useCss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitForElementVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ELEMENT_TIMEOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;containsText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden3 pass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The chain executes asynchronously so you can’t break it apart and put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debugger&lt;/code&gt; in the middle,
instead you have to inject your code into the chain; by using for example: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.perform(() =&amp;gt;
{debugger})&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can run tests using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nightwatch -c nightwatch.json&lt;/code&gt; and use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--env chrome&lt;/code&gt; if you want
to run tests in chrome instead of Firefox etc. In the config file you can set the paths to your
geckodriver and to the selenium server jar, and then just set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;selenium.start_process&lt;/code&gt; to true.
I also wanted all testcases to finish even if the first one failed so I set
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skip_testcases_on_fail&lt;/code&gt; to false. I was able to retrieve the JS log using the same clunky way I
used for selenium-webdriver. The config, command line output and command line switches all just
works the way you’d expect them to.&lt;/p&gt;

&lt;p&gt;I didn’t try it, but it seems possible to run your nightwatch tests via Sauce Labs quite easily.
There is also another cloud testing service called &lt;a href=&quot;nightcloud.io&quot;&gt;nightcloud.io&lt;/a&gt; being developed.&lt;/p&gt;

&lt;p&gt;In general, Nightwatch feels like a very competent tool. The only drawback I can think of is that
it seems that nightwatch was largely written by a single guy, so it has a low bus factor.&lt;/p&gt;

&lt;h1 id=&quot;webdriverio&quot;&gt;webdriver.io&lt;/h1&gt;

&lt;p&gt;If you disgard selenium-webdriver downloads (and protractor drives a lot of those), Webdriver.io is
the most downloaded E2E package on NPM. Webdriver.io is also the framework with the most active
developers working on it, in the last year alone over 130 different developers sent at least one
patch to this project.&lt;/p&gt;

&lt;p&gt;The code looks like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scenario 3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://www.whatever.com/something/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;//*[text()=&quot;button3&quot;]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitForVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ELEMENT_TIMEOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;strictEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden3 pass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code looks superficially similar to the “fake sync” API of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;selenium-webdriver&lt;/code&gt;, but it is
really not the same. If you drop &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execArgv: [&apos;--inspect-brk&apos;],&lt;/code&gt; into your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wdio.conf.js&lt;/code&gt; and
then add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debugger&lt;/code&gt; somewhere in the middle of the testcase, like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scenario 3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://www.whatever.com/something/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;//*[text()=&quot;button3&quot;]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;debugger&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitForVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ELEMENT_TIMEOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;strictEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden3 pass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;..then the debugger will actually execute the first part of the testcase, open the URL, wait for it
to finish loading and then click the button. Only after those things are finished executing will it
hit the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debugger&lt;/code&gt; breakpoint. This is possible because webdriverio uses proper coroutines
implemented in native code and supplied by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-fibers&lt;/code&gt; package. This means that for
example the, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.url()&lt;/code&gt; method can send a message to the browser saying that it should navigate to
a particular url and then execution context suspends itself in such a way that it’s not resumed
until the browser has finished loading the URL. From the outside, it appears like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.url()&lt;/code&gt; is
synchronous even though node is still “single threaded” and we havn’t used any additional OS
threads.&lt;/p&gt;

&lt;p&gt;Another neat feature for webdriverio is that it has a really good REPL that fires up a browser and
then you can experiment with commands and selectors from there. Even better, you can insert
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;browser.debug();&lt;/code&gt; into an existing testcase and have it drop into a REPL at that particular
place in the testcase.&lt;/p&gt;

&lt;p&gt;The current &lt;a href=&quot;http://webdriver.io/guide.html&quot;&gt;getting started&lt;/a&gt; documentation on the webdriver.io site
is not ideal imo. It has lots of steps in the beginning asking you to download specific versions of
the selenium server jar, geckodriver, chromedriver etc which is both fiddly and error prone. A bit
later on in the guide it mentions wdio, and much later I learned about
wdio-selenium-standalone-service which automatically sets everything up. Also, at first I couldn’t
find a way to override the configured browser from the command line, but then I realized I could set
the config to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;browserName: process.env.SELENIUM_BROWSER || &apos;firefox&apos;&lt;/code&gt; and override it with an
environment variable. The config can also list multiple browsers if you want to test locally in
Chrome and Firefox simultaneously, e.g. put this at the end of your wdio.conf.js:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SELENIUM_BROWSER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;capabilities&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;browserName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SELENIUM_BROWSER&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;capabilities&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;browserName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;browserName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;firefox&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I also ended up setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timeout: 30000&lt;/code&gt; inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mochaOpts&lt;/code&gt;. Running with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--logLevel
verbose&lt;/code&gt; was useful for debugging. The watch mode is also very useful.&lt;/p&gt;

&lt;p&gt;To assert that no JS errors were encountered, I had to explicitly download the browser log in the
end of the testcase, just like I did for selenium-webdriver. Sad!&lt;/p&gt;

&lt;h1 id=&quot;chimp&quot;&gt;chimp&lt;/h1&gt;

&lt;p&gt;chimp is a framework built on top of webdriver.io, an example testcase looks like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://www.whatever.com/something/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;//*[text()=&quot;button3&quot;]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitForVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ELEMENT_TIMEOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;strictEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden3 pass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There was a few things I didn’t like about chimp. It talks a lot about Meteor/DDP and Also, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chimp
--help&lt;/code&gt; doesn’t print anything and for every test run it prints “Master Chimp and become a testing
Ninja! Check out our course: http://…”&lt;/p&gt;

&lt;p&gt;In the end I didn’t think chimp added enough on top of webdriver.io to make it worth the additional
dependency.&lt;/p&gt;

&lt;h1 id=&quot;nemojs&quot;&gt;nemo.js&lt;/h1&gt;

&lt;p&gt;This E2E framework was developed by PayPal. An example testcase looks like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/*global describe:true, it:true */&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;basedir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Nemo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;nemo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;nemo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;suite using @generic@ nemo-view methods&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;nemo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Nemo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;basedir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;nemo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;quit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;should automate the browser&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//login&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;nemo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nemo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;baseUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;nemo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_waitVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id:signup-button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;nemo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_waitVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id:cta-btn&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;nemo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_waitVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id:email&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sendKeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;mynewpaypalaccount@geemail.paypal&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;nemo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nemo starts up with selenium-webdriver 2.53.3 which is incompatible with Firefox v48 and all later
versions of Firefox (i.e. all versions of Firefox released since mid-2016). Their homepage mentions
this and says users should downgrade Firefox or upgrade to a new major version of selenium-webdriver
that hasn’t been tested with nemo. It also prints a few other east to fix deprecation warnings like
os.tmpDir vs os.tmpdir etc.&lt;/p&gt;

&lt;h1 id=&quot;codeceptjs&quot;&gt;CodeceptJS&lt;/h1&gt;

&lt;p&gt;This framework allows you to run your testcases on top of either webdriverIO, selenium-webdriver,
protractor or NightmareJS. Their API uses generators to deal with the asynchronicity:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;Feature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Test1.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Scenario&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;test something&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;amOnPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fillField&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;[name=q]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitForElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;h3.r&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;see&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;Hello, World!&quot; program - Wikipedia&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;titles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grabTextFrom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;h3.r&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;titles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When I first tried to install the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codeceptjs-webdriverio&lt;/code&gt; package it failed to install
properly, so I filed &lt;a href=&quot;https://github.com/DavertMik/codeceptjs-webdriverio/issues/1&quot;&gt;issue #1&lt;/a&gt; (yay!)
and 25 days later a fix was pushed.&lt;/p&gt;

&lt;p&gt;From what I can tell, CodeceptJS doesn’t even have an API to fetch the browser JS log, so I could
not implement a check that verifies that no JS error happened on the page.&lt;/p&gt;

&lt;h1 id=&quot;testcafe&quot;&gt;TestCafe&lt;/h1&gt;

&lt;p&gt;TestCafe is developed by a company called DevExpress who open sourced it in October 2016. They
continue to work on it quite intensely (they are landing about 480 commits per year which is higher
than all other frameworks except for webdriverio at &amp;gt;700 commits per year). The most interesting
thing about this framework is that &lt;a href=&quot;https://testcafe-discuss.devexpress.com/t/why-not-use-selenium/47/2&quot;&gt;it does NOT use selenium or
webdriver&lt;/a&gt; at all. Instead
parts of the test framework itself executes inside the browser.&lt;/p&gt;

&lt;p&gt;A typical testcase looks like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;fixture&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`example page`&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;page&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testpageUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scenario 1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#button1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden1 pass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re not familar with the syntax near “fixture”; it’s an ES6 tagged template literal.&lt;/p&gt;

&lt;p&gt;One really awesome thing about testcafe is that it automatically detects JS errors that happened
when the test page was executing. I suppose this is the kind of awesome you can attain when you
reach outside of the Selenium box! I also noted that the terminal output when tests failed where
quite readable/clean.&lt;/p&gt;

&lt;p&gt;TestCafe doesn’t support XPath selectors
(&lt;a href=&quot;https://github.com/DevExpress/testcafe/issues/1178&quot;&gt;#1178&lt;/a&gt;) but it has a special API for locating
elements by text: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Selector(&apos;form input&apos;).withText(&apos;Submit&apos;)&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;intern&quot;&gt;Intern&lt;/h1&gt;

&lt;p&gt;Next up is Intern; a typical testcase looks like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;login works&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;remote&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;index.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scroob&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findByTagName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getVisibleText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Welcome!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As mentioned in conjunction with the “commit count over time” plot earlier, Jason Cheatham is
currently working on Intern v4 (the example above is from v4). There are only alpha releases
available so far, but the idea is that the final release will be “mostly API compatible” with the
latest alpha. Intern v4 will be used to test the new version of the Dojo framework.&lt;/p&gt;

&lt;p&gt;While earlier versions required you to declare your test suites as AMD modules, now with Intern v4
you can just babel-register and use require (and the built-in code coverage works even for
transpiled code!). Under the hood, Intern itself has been rewritten in TypeScript.&lt;/p&gt;

&lt;p&gt;My impression of Intern is that it’s by far the most difficult framework to setup, this will
probably change once v4 is finished though (both in terms of reduced quirks and better
documentation).&lt;/p&gt;

&lt;h1 id=&quot;cypress&quot;&gt;Cypress&lt;/h1&gt;

&lt;p&gt;This framework is currently in closed beta. Luckily I was able to get an early invite (thanks
Jennifer!). Cypress runs via Mocha and its API uses async chaining:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scenario 2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;cy&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testpageUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.button2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#hidden2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;be.visible&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;have.text&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hidden2 pass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It does not use selenium or webdriver, instead parts of the test framework is injected into the
browser directly. If the test runner needs to do privileged actions like read a file from disk, it
makes a request to a local webserver that performs the action for it (or in some cases a browser
extension is used as well). For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alert&lt;/code&gt; popups, Cypress currently auto accepts them but that
doesn’t work if they are coming from an iframe
(&lt;a href=&quot;https://github.com/cypress-io/cypress/issues/376&quot;&gt;#376&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Cypress also comes with the ability to optionally intercept XHR requests from the browser to
immediately return a static result, speeding up testcases where such a trade-off is appropriate. It
comes with bundled versions of chai, sinon, moment, lodash and jQuery. The documentation is really
good. Finally, Cypress ships with a GUI application based on Electron where you can see your tests
execute. For each step in the testcase, it snapshots the browser so you can go back and look at what
the DOM looked like at that step.&lt;/p&gt;

&lt;p&gt;Since Cypress isn’t finished yet you cannot really compare it to the other frameworks, but it feels
really sophisticated and polished. There are major things left to implement though, for example you
can only run tests in Chrome and Electron right now; Firefox, Edge and IE is not available.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;It’s amazing that there are so many good tools out there to chose from and so many talented people
working on them.&lt;/p&gt;

&lt;p&gt;In the end I decided to not use wd.js because it’s not maintained actively enough, same goes from
nemo (which also isn’t used enough outside of PayPal). Intern was too difficult to setup and in
transition between v3 and v4 and I didn’t really see the value in Chimp over WebdriverIO. I also
decided to skip Cypress because it’s not finished and cannot test Firefox etc, and even when v1 is
out it will require some time to mature. Also it’s risky that there are so few developers working on
it (right now), I prefer tools with broad community participation (no doubt they will try to build
such a community).&lt;/p&gt;

&lt;p&gt;If I rank the remaining frameworks, I get something like this (best up top):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;webdriver.io&lt;/li&gt;
  &lt;li&gt;TestCafe&lt;/li&gt;
  &lt;li&gt;NightwatchJS&lt;/li&gt;
  &lt;li&gt;CodeceptJS&lt;/li&gt;
  &lt;li&gt;selenium-webdriver&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are many things factoring into this ranking; for example on a technical level, NighwatchJS is
really good but the community momentum around WebdriverIO seems so much stronger. I’m still pretty
interested in seeing what the limitations are of non-selenium E2E testing. Because of this, my plan
is to write a slightly larger test suite in both webdriver.io and TestCafe, and have them run in
parallel for a while so I can get some more experience of how they work. I will also be checking
back with Cypress after they release a stable version that can test Firefox, Edge and IE.&lt;/p&gt;

&lt;p&gt;If you have to pick a single test E2E framework today, I recommend &lt;a href=&quot;http://webdriver.io/&quot;&gt;webdriver.io&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;epilogue&quot;&gt;Epilogue&lt;/h1&gt;

&lt;p&gt;Did you like this post? &lt;a href=&quot;http://twitter.com/home?status=Comprehensive%20review%20of%20Javascript%20tools%20for%20end-to-end%20testing%20web%20applications%20https%3A%2F%2Fgit.io%2Fv7v8E%20by%20%40mo_molsson&quot;&gt;Tweet about it&lt;/a&gt; or post it on whatever other network / thing you’re on.&lt;/p&gt;

&lt;p&gt;Did I miss some important feature / aspect of some framework? Did I miss an entire framework? Tweet
at &lt;a href=&quot;http://twitter.com/home?status=@mo_molsson&quot;&gt;@mo_molsson&lt;/a&gt; or drop a comment below. Thanks for
reading!&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p class=&quot;footnote&quot;&gt;&lt;a name=&quot;footnote1&quot;&gt;1&lt;/a&gt;:
&lt;a href=&quot;https://en.wikipedia.org/wiki/Buridan%27s_ass&quot;&gt;Buridan&apos;s ass&lt;/a&gt;.
&lt;/p&gt;

&lt;p class=&quot;footnote&quot;&gt;&lt;a name=&quot;footnote2&quot;&gt;2&lt;/a&gt;:
The median &quot;Issue Latency&quot; and the fraction of open issues among all issues, are both computed using the
excellent service &lt;a href=&quot;https://isitmaintained.com/&quot;&gt;Is it maintained?&lt;/a&gt;.
&lt;/p&gt;
</description>
        <pubDate>Thu, 20 Jul 2017 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2017/07/20/javascript-e2e-integration-testing.html</link>
        <guid isPermaLink="true">http://mo.github.io/2017/07/20/javascript-e2e-integration-testing.html</guid>
        
        <category>javascript</category>
        
        <category>web</category>
        
        
      </item>
    
      <item>
        <title>Chained APIs with asynchronous functions in Javascript</title>
        <description>&lt;p&gt;Quite a few of the web application E2E test frameworks implemented in Javascript have APIs based on
method chaining that sort of hide the asynchronous nature of Selenium testing (send command to
browser, wait for reply, send command, wait for reply etc). Examples include NightwatchJS, wd.js
when it is being used via its chained Q-promises API and also Cypress. Variants of this chaining is
sometimes referred to as &lt;a href=&quot;https://en.wikipedia.org/wiki/Fluent_interface&quot;&gt;Fluent Interface&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, here is a NightwatchJS testcase:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;http://launchpad.kp/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#big-red-button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitForElementVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.confirmation-popup&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;containsText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.confirmation-popup&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;fire ze missiles?&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#cancel-button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These chained APIs make asynchronous code look sort of synchronous even though it’s not. Often
people can get by without knowing how it works, until for example they have to step through the code
in a debugger; then it really helps a lot to understand how these APIs are built.&lt;/p&gt;

&lt;h1 id=&quot;why-chained-async-methods-can-be-unintuitive-at-first&quot;&gt;Why chained async methods can be unintuitive at first&lt;/h1&gt;

&lt;p&gt;If you break the chain into two parts and add some breakpoints:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;http://launchpad.kp/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#big-red-button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitForElementVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.confirmation-popup&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;debugger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;browser&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;containsText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.confirmation-popup&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;fire ze missiles?&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#cancel-button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;debugger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When the debugger stops at the first breakpoint, nothing has happened yet. The browser hasn’t
received the URL yet, in fact, Nightwatch hasn’t even started the browser yet. However, the same is
also true at the second breakpoint. This is because the code doesn’t really perform the actions,
instead it just builds a “list of actions”, the first of which the Javascript engine will begin to
execute at the next runslice. Technically, this list is stored as a chain of promises.&lt;/p&gt;

&lt;p&gt;Actually, there are also non-chaining variants of this where seemingly synchronous code just chains
up promises that gets executed later on. For example, early versions of the npm package
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;selenium-webdriver&lt;/code&gt; (the “official” Javascript bindings for Webdriver shipped by the Selenium
project themselves) had a &lt;a href=&quot;https://github.com/SeleniumHQ/selenium/wiki/WebDriverJs&quot;&gt;global promise
manager&lt;/a&gt; that allowed you to write code
like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webdriver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;selenium-webdriver&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webdriver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;until&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webdriver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;webdriver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forBrowser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;chrome&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;http://www.google.com/ncr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sendKeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;webdriver&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;By&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;btnG&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;titleIs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;webdriver - Google Search&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;quit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;program finished&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But just like the “debugger” breakpoints in the nightwatch example, this snippet would first run to
completion (and print &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;program finished&lt;/code&gt;) and then after that it would start the actual testing.&lt;/p&gt;

&lt;p&gt;Even though I think a more attractive approach (on Node) is proper coroutines via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-fiber&lt;/code&gt;,
I still think it’s quite interesting to understand what different approaches have been tried,
including chaining APIs. And in browsers we only have chaining APIs and async/await to pick from; so
in extreme cases like E2E testing maybe chaining is the cleaner option instead of writing await on
every API call (for example, Cypress executes E2E tests from the browser itself and uses chaining).&lt;/p&gt;

&lt;h1 id=&quot;how-to-implement-a-chained-api-with-async-methods&quot;&gt;How to implement a chained API with async methods&lt;/h1&gt;

&lt;p&gt;When you implement a chaining API for synchronous functions, you just put all the functions on an
object and you make sure that each function first does its thing and then returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt;.
Notably the thing that the function “does” can be either A) something that modifies state on
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt;, or B) something that creates a copy of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt; where the state is slightly different
(we don’t have to return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt;, we are only required to return something that implements all
the methods in our API so that the chaining can continue).&lt;/p&gt;

&lt;p&gt;For example, in jQuery you might do:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;elem&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;blue&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When the above API chain is evaluated the element accumulates the text, the CSS and the class.&lt;/p&gt;

&lt;p&gt;To implement chaining for asynchronous functions, we do the exact same thing, but the “state” we
keep inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt; is the chain of promises. We keep appending to this chain of promises as we
evaluate the chain in the API.&lt;/p&gt;

&lt;p&gt;To implement such a chained API in vanilla JS, one could do:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;someAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;someAsyncFunc() called&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;otherAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;otherAsyncFunc() called&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;otherAsyncFunc() result&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;someFunc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;someAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;otherFunc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;otherAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyApi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;previousActions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;someFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;previousActions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;someFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;otherFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;previousActions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;otherFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;api&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;api&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;before chain&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;api&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;someFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;someFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;otherFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;someFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;otherFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;after chain&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you run the above example, it outputs:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chain&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;after&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chain&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;someAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;called&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;someAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;called&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;otherAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;called&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;otherAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;someAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;called&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;otherAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;called&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;otherAsyncFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 18 Jul 2017 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2017/07/18/fluent-chained-api-asynchronous-functions-async-methods-Javascript.html</link>
        <guid isPermaLink="true">http://mo.github.io/2017/07/18/fluent-chained-api-asynchronous-functions-async-methods-Javascript.html</guid>
        
        <category>javascript</category>
        
        <category>web</category>
        
        
      </item>
    
      <item>
        <title>Javascript unit testing tools</title>
        <description>&lt;p&gt;I have compared&lt;sup&gt;&lt;a href=&quot;#footnote1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; few different tools for unit testing Javascript code. The frameworks I looked into
were QUnit, Mocha JS, tape, AVA, Jasmine, Karma and Jest. I wrote a few testcases and then ported
these to each framework. The testcases I wrote were meant to detect defects in various kinds of
functions, for example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;regular function that returned an incorrect value&lt;/li&gt;
  &lt;li&gt;regular function that unexpectedly throws an error&lt;/li&gt;
  &lt;li&gt;callback function that calls back immediately (same tick) and returns the wrong value&lt;/li&gt;
  &lt;li&gt;asynchronous callback function that delivers the wrong value&lt;/li&gt;
  &lt;li&gt;asynchronous callback function that forgets to callback&lt;/li&gt;
  &lt;li&gt;asynchronous callback function that accidentally calls back twice&lt;/li&gt;
  &lt;li&gt;asynchronous function returning a promise and resolving with an incorrect value&lt;/li&gt;
  &lt;li&gt;asynchronous function returning a promise that is then unexpectedly rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we dig into the implementation of these test scenarios in each test framework, here is a
table summarizes a few basic facts:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;QUnit&lt;/th&gt;
      &lt;th&gt;Mocha&lt;/th&gt;
      &lt;th&gt;tape&lt;/th&gt;
      &lt;th&gt;AVA&lt;/th&gt;
      &lt;th&gt;Jasmine&lt;/th&gt;
      &lt;th&gt;Karma&lt;/th&gt;
      &lt;th&gt;Jest&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Total Commits&lt;/td&gt;
      &lt;td&gt;1,157&lt;/td&gt;
      &lt;td&gt;2,263&lt;/td&gt;
      &lt;td&gt;453&lt;/td&gt;
      &lt;td&gt;1,062&lt;/td&gt;
      &lt;td&gt;1,584&lt;/td&gt;
      &lt;td&gt;2,274&lt;/td&gt;
      &lt;td&gt;2,224&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub Stars&lt;/td&gt;
      &lt;td&gt;3,630&lt;/td&gt;
      &lt;td&gt;12,480&lt;/td&gt;
      &lt;td&gt;3,889&lt;/td&gt;
      &lt;td&gt;10,335&lt;/td&gt;
      &lt;td&gt;12,502&lt;/td&gt;
      &lt;td&gt;8,623&lt;/td&gt;
      &lt;td&gt;10,455&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Issue Resolution Time &lt;sup&gt;&lt;a href=&quot;#footnote3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
      &lt;td&gt;3 days&lt;/td&gt;
      &lt;td&gt;11 days&lt;/td&gt;
      &lt;td&gt;1 day&lt;/td&gt;
      &lt;td&gt;5 days&lt;/td&gt;
      &lt;td&gt;7 days&lt;/td&gt;
      &lt;td&gt;58 days&lt;/td&gt;
      &lt;td&gt;22 hours&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Open Issues / All Issues &lt;sup&gt;&lt;a href=&quot;#footnote3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
      &lt;td&gt;4%&lt;/td&gt;
      &lt;td&gt;17%&lt;/td&gt;
      &lt;td&gt;27%&lt;/td&gt;
      &lt;td&gt;17%&lt;/td&gt;
      &lt;td&gt;4%&lt;/td&gt;
      &lt;td&gt;20%&lt;/td&gt;
      &lt;td&gt;5%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;License&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;MIT&lt;/td&gt;
      &lt;td&gt;BSD-3-Clause &lt;sup&gt;&lt;a href=&quot;#footnote4&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;If you plot the commit count of each project over time it’s clear that QUnit and Jasmine are the
oldest projects, and also that there is a very large amount of commits landing in the Jest
repository right now (and have been for quite some time now) compared to all the other frameworks:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/javascript-unit-testing-commit-count.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Another interesting thing to look at is the weekly download count on npm for each framework over
time; Mocha is the most commonly used framework by a very wide margin. And if we dig into recent
growth numbers (looking at from 2017-01-15 up until today) we see that Mocha weekly downloads
increased by 206K (up 19%) while for example Jest added 195K new weekly downloads in the same period (which for them was an impressive 145% increase in less than 5 months!):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/javascript-unit-testing-weekly-npm-downloads.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Below are some things I noted while writing sample testcases using these frameworks.&lt;/p&gt;

&lt;h1 id=&quot;qunit&quot;&gt;QUnit&lt;/h1&gt;

&lt;p&gt;The first framework that I looked at in-depth was QUnit. This is one of the oldest unit testing
frameworks for Javascript. It was originally developed to test jQuery and jQuery UI, but was later
extracted into a separate project that does not even depend on jQuery. Unfortunately while writing
my first few testcases for QUnit, I ran into a bug! If you have a testcase that calls a function
with an asynchronous callback, but the function forgets the invoke the callback; then QUnit
terminates with exit code 0 and doesn’t even print the passed/failed summary. I bet many CI systems
would interpret that as a PASS, so it’s a show stopper bug imo. The testcase illustrates the
problem:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;asyncForgetCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isBuggy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// accidentally nothing&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;QUnit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;only&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;util test async callback function that forgets to callback&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;doneFunc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;asyncForgetCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;   Foo   &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;actual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;doneFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Later I found that this bug was discovered and filed on Github in March, but nobody has had time to
fix it yet: &lt;a href=&quot;https://github.com/qunitjs/qunit/issues/1132&quot;&gt;#1132&lt;/a&gt;.
A workaround for the bug is to set a global default timeout using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;QUnit.config.testTimeout = 2000&lt;/code&gt; or similar.&lt;/p&gt;

&lt;p&gt;On the plus side, QUnit was able to correctly handle the “accidental double callback” scenario (and
doing this also without me having to declare the expected number of assertions) by failing the
testcase due to assert + doneFunc being called after the original call to doneFunc(). It should be
noted that QUnit does offer an “expected assertion count” feature available though, useful for more
advanced test scenarios.&lt;/p&gt;

&lt;p&gt;There was also some minor nitpicking issues that I noted while writing the rest of the testcases for
QUnit:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Normally when you do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert.equal(actual, &quot;expected&quot;)&lt;/code&gt; QUnit will print both the “actual” and
the “expected” value whenever the assertion fails. However, if “actual” happens to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;undefined&lt;/code&gt;
then only the expected value is printed. This is a minor issue, but I filed it as bug &lt;a href=&quot;https://github.com/qunitjs/qunit/issues/1177&quot;&gt;#1177&lt;/a&gt;. The maintainer asked if I wanted to fix it
myself and I figured what the hell, so I &lt;a href=&quot;https://github.com/js-reporters/js-reporters/pull/102&quot;&gt;did&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;In the QUnit documentation, the introduction starts with examples of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert.equal()&lt;/code&gt;, but
note that the assertion &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert.equal(1, &quot;1&quot;)&lt;/code&gt; holds so (imo) it’s much better to
use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert.strictEqual()&lt;/code&gt; by default and only resort to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert.equal()&lt;/code&gt; when you need it.
Imo it would be better if QUnit had a “default assertion” that checked for strict (===) equality.
This behavior comes with the default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert&lt;/code&gt; module in NodeJS (and also the “CommonJS Unit
Testing” specification), so you get the same thing for example in Mocha JS if you choose to use
those asserts there; but QUnit doesn’t allow you to use any other type of assertions. Pretty much
all modern Javascript testing frameworks default to === and lets you opt-in for == when you need it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;From a UX perspective, I don’t like that QUnit prints its default TAP output as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;# fail 0&lt;/code&gt;
with red color when all tests are passing, while it prints for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;# fail 1&lt;/code&gt; as the summary
line when one testcase fails. Nothing on screen should be red when all tests are passing. You can
use an alternative reporter by passing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--reporter&lt;/code&gt; but I like good defaults.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Another UX issue is that npm packages &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qunit&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qunitjs&lt;/code&gt; both install a binary called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qunit&lt;/code&gt; but only the latter can be used to run tests using the method described in the
documentation. Confusing for newbies.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;mocha-js&quot;&gt;Mocha JS&lt;/h1&gt;

&lt;p&gt;Mocha was started in 2011 by Tj Holowaychuk, the same guy that founded ExpressJS, co, commander.js,
koa and a bunch of other highly successful NodeJS projects, and who then &lt;a href=&quot;https://medium.com/@tjholowaychuk/farewell-node-js-4ba9e7f3e52b&quot;&gt;switched to
golang&lt;/a&gt; three years ago. After its
inception, the project quickly grew into a comprehensive test framework that covers a lot of
different testing needs, and a large number of people have contributed to its success over the years
(roughly 15 unique authors shows up in the git log each 60 day period). Lately though, the Mocha JS
project has been having some problems finding developers to keep up with maintenance, their GitHub
page current says “we’re currently unable to merge most pull requests due to lack of maintenance
resources.”&lt;/p&gt;

&lt;p&gt;That said, this test framework is quite mature. It allows you to customize which type of assertions
you use and which test result reporter you use etc. There is also plugins for IDEs/editors like
emacs and IDEA and so on.&lt;/p&gt;

&lt;p&gt;When I tried porting my testcases to Mocha JS I wasn’t really satisfied with how it handled the
“accidental double callback”. For example, if I did:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// someCode.js&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;asyncDoubleCallback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isBuggy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// myTests.js&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#asyncDoubleCallback()&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Mocha JS passes context object to it() via &quot;this&quot;.&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;only&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;is working&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;asyncDoubleCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;   Foo   &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;actual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then the default reporter (from Mocha 3.3.0) shows this result:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/javascript-unit-testing-mocha-double-callback.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note that Mocha does not report the number of passed/failed assertions, it reports the number of
passing and failing &lt;em&gt;testcases&lt;/em&gt;. In the above example I’m running a single testcase
using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it.only()&lt;/code&gt;, but Mocha reports 1 testcase passed and 1 testcase failed?!&lt;/p&gt;

&lt;h1 id=&quot;tape&quot;&gt;tape&lt;/h1&gt;

&lt;p&gt;The tape GitHub page starts with testling-ci badge that hasn’t run since 2014, proudly claiming to
support “Chrome 29”. There also hasn’t been a lot of commits made in the tape git repository lately.
It was slightly annoying having to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t.end()&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t.plan()&lt;/code&gt; all testcases. Also I didn’t like
that tape aborts the entire test suite if a single testcase throws, but it was easy to “fix” by
switching the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require(&apos;tape&apos;)&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require(&apos;tape-catch&apos;)&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Here is how tape handles the accidental double callback bug scenario btw:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/javascript-unit-testing-tape-double-callback.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note that tape, unlike Mocha, prints the number of assert passes/fails in the summary so everything
is technically correct here in some sense.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update 2017-07-12: As mentioned by Stefan Maric in the comments below; there is also a
test framework called &lt;a href=&quot;http://www.node-tap.org/&quot;&gt;node-tap&lt;/a&gt; which is similar to tape in a lot of
ways but with a few key issues fixed. Even though node-tap has fewer NPM downloads, npm uses
node-tap for testing. If you like tape you should also have a look at node-tap before you decide
which framework to use.&lt;/em&gt;&lt;/p&gt;

&lt;h1 id=&quot;ava&quot;&gt;AVA&lt;/h1&gt;

&lt;p&gt;AVA describes itself as “futuristic” and unlike the other frameworks it runs tests in parallel,
supports (and promotes) testing using ES2017 (and even post-ES2017 features like Observables).
It also has extremely good assertion output, much better than the previous test frameworks. For
equality of strings and deep equality of objects it shows diffs to highlight the difference between
actual and expected. When my buggy callback function delivers the wrong value causing
a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t.is(actual, &apos;foo&apos;)&lt;/code&gt; assertion to fail, then it immediately highlights the source line where
that bad value came from:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/javascript-unit-testing-ava-async-callback-with-wrong-value.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;While porting my testcases to AVA, I did hit one bug (filed as &lt;a href=&quot;https://github.com/avajs/ava/issues/1377&quot;&gt;#1377&lt;/a&gt;) in AVA related to timeouts (for the test
scenario where a function forgets to call its callback).&lt;/p&gt;

&lt;h1 id=&quot;jasmine&quot;&gt;Jasmine&lt;/h1&gt;

&lt;p&gt;Jasmine is a very old framework (first commit is from 2008). It was influenced to some extent by
Ruby “RSpec”. Offers test doubles using spies built-in (in other frameworks you need to import
something like &lt;a href=&quot;http://sinonjs.org/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sinon.js&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;When I ported over the testcases to Jasmine, I found that, unlike the four previous test frameworks,
it happily reports PASS in the “accidental double callback” scenario even if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;done()&lt;/code&gt; is called
twice:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#asyncDoubleCallback() naive&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;is working&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;asyncDoubleCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;   Foo   &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;actual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Jasmine also does not offer any assertion planning feature. At some point in 2014, somebody opened a GitHub issue (&lt;a href=&quot;https://github.com/jasmine/jasmine/issues/626&quot;&gt;#626&lt;/a&gt;) asking for this to be added but
the issue was closed by the maintainers because “they’ve not heard about this request before” which
is unfortunate. They offer call counting for test double spies with callThrough, but using those for
this scenario would be cumbersome and inefficient.&lt;/p&gt;

&lt;h1 id=&quot;karma&quot;&gt;Karma&lt;/h1&gt;

&lt;p&gt;Karma is not a test framework per se, but rather a test runner that can take a test suite written in
Mocha, QUnit or Jasmine and then run that suite in parallel on multiple browsers (desktop or
mobile, and also including JSDom, PhantomJS and headless Chrome). All browsers report back results
that are then presented as terminal output. It can also watch your test directory and automatically
rerun the tests each time you update a testcase. Karma is the test runner used by Angular JS and was
developed by the Angular team at Google. If you want to run your tests in browsers, then you should
probably be using Karma regardless of which of the frameworks you pick (except for Jest, more on
that below).&lt;/p&gt;

&lt;h1 id=&quot;jest&quot;&gt;Jest&lt;/h1&gt;

&lt;p&gt;Jest is the test framework and test runner that is used by the React project and consequently by
many libraries, tools and web apps implemented using React. The fact that it’s used by and backed by
Facebook lends it both credibility and likely resources. Among the top 10 contributors to Jest,
everyone except two works at Facebook&lt;sup&gt;&lt;a href=&quot;#footnote2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. It seems like a large part of the
work is done by Chris Pojer tbh.&lt;/p&gt;

&lt;p&gt;Jest started as a test runner wrapped around Jasmine, and the assertions still looks
like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(foo).toBe(&apos;bar&apos;)&lt;/code&gt;, but now there are few traces of Jasmine left. Jest doesn’t have
support for running tests inside a real browser, cpojer shared some thoughts around this
&lt;a href=&quot;https://github.com/facebook/jest/issues/139#issuecomment-229277654&quot;&gt;here&lt;/a&gt; and in issue
&lt;a href=&quot;https://github.com/facebook/jest/issues/848&quot;&gt;#848&lt;/a&gt;. One really cool feature of Jest is snapshot
testing, which helps you detect regressions in your output (either React component trees serialized to text, or for example an AST serialized to text if you’re working on a source transformation tool).&lt;/p&gt;

&lt;p&gt;When I tried to port my testcases to Jest, I quickly ran into a Jest bug where assertion failures
inside callbacks were suppressed (I was left with a test that timed out for no apparent reason and I
had to do some digging before I even found the suppressed errors). I originally filed this as &lt;a href=&quot;https://github.com/facebook/jest/issues/3519&quot;&gt;#3519&lt;/a&gt; but it turned out to be a duplicate of
&lt;a href=&quot;https://github.com/facebook/jest/issues/2059&quot;&gt;#2059&lt;/a&gt;.
If your async code uses promises instead of callbacks, this issue won’t affect you. If it’s
possible, a nice way to escape the problem is simply to refactor to your old callback based
functions to use promises instead. However, if you absolutely need to test functions with callbacks
(e.g. maybe you just inherited a big legacy code base and you want to write testcases before you
refactor, or maybe the callback API is used by third-party code) then the status quo for Jest seems
to be that for any assertion inside an asynchronous callback you have to wrap the
assert inside a try/catch that explicitly calls fail instead (like they do in lerna unit tests  &lt;a href=&quot;https://github.com/lerna/lerna/commit/8c3f2df6da71cb17454cf94647ca00990927d18a&quot;&gt;here&lt;/a&gt;). Another
workaround would be to temporarily wrap the callback in a promise inside the testcase:&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;testedFunc calls its callback with the correct value&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;assertions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;testedFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;some input&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;actual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As seen in the commit count chart earlier, there seems to be a lot of momentum behind Jest right
now. This is also reflected if you plot the number of active developers in each project (i.e. for
every date X, the Y-value is defined as the number of unique e-mails seen in git log from X - 365 days up until X). That plot looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/javascript-unit-testing-jest-active-dev-count.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The number of developers involved in Jest increasing very quickly, they must be doing a lot things
right when it comes to community building. I also looked at “the number of people which more than
50 commits” for each framework and Jest comes out highest there too with seven such developers so
this is not all “one-off drive-by patching” either.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;I was surprised that I hit so many little bugs / issues when trying to use these testing frameworks.
Maybe the best choice is to do like TJ and switch to Go? :-P Staying with Javascript, I think
it’s hard to ignore the momentum behind Jest. It would be awesome if someone fixed &lt;a href=&quot;https://github.com/facebook/jest/issues/2059&quot;&gt;#2059&lt;/a&gt; though. Then again, Mocha seems to work well
enough for a lot of people.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p class=&quot;footnote&quot;&gt;&lt;a name=&quot;footnote1&quot;&gt;1&lt;/a&gt;:
&lt;a href=&quot;https://en.wikipedia.org/wiki/Analysis_paralysis&quot;&gt;https://en.wikipedia.org/wiki/Analysis_paralysis&lt;/a&gt;.
&lt;/p&gt;

&lt;p class=&quot;footnote&quot;&gt;&lt;a name=&quot;footnote2&quot;&gt;2&lt;/a&gt;:
Technically, wtgtybhertgeghgtwtg is anonymous but &quot;their contribution history smells like they work for Facebook&quot; and amasad has left Facebook.
&lt;/p&gt;

&lt;p class=&quot;footnote&quot;&gt;&lt;a name=&quot;footnote3&quot;&gt;3&lt;/a&gt;:
The median &quot;Issue Resolution Time&quot; and the fraction of open issues among all issues, are both computed using the
excellent service &lt;a href=&quot;https://isitmaintained.com/&quot;&gt;Is it maintained?&lt;/a&gt;.
&lt;/p&gt;

&lt;p class=&quot;footnote&quot;&gt;&lt;a name=&quot;footnote4&quot;&gt;4&lt;/a&gt;:
Contributors need to sign a Contributor License Agreement (CLA) before patches are merged into Jest,
and along with the BSD-3-Clause there is a patent grant which can be revoked by Facebook under
some circumstances, see details on
&lt;a href=&quot;https://en.m.wikipedia.org/wiki/React_(JavaScript_library)#Licensing&quot;&gt;Wikipedia&lt;/a&gt;.
&lt;/p&gt;
</description>
        <pubDate>Mon, 05 Jun 2017 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2017/06/05/javascript-unit-testing.html</link>
        <guid isPermaLink="true">http://mo.github.io/2017/06/05/javascript-unit-testing.html</guid>
        
        <category>javascript</category>
        
        <category>web</category>
        
        
      </item>
    
      <item>
        <title>AWS S3: Introduction</title>
        <description>&lt;p&gt;Amazon S3 (Simple Storage Service) is a file storage web service offered as part
of Amazon Web Services (AWS). Files are uploaded using HTTP REST, e.g. via a
super basic web UI hosted by AWS S3 or via third-party upload tools. It’s of
course also accessible programatically via countless of SDKs available for all
languages/platforms. If the files are marked “public” they can optionally be
served as a static website. It’s possible to setup logging to track how often
each file/page is loaded, custom 404 pages and you can configure custom HTTP
response headers including cache control and CORS etc. It should be noted that
while the REST endpoint offers https, the static website endpoint is http only.
S3 can also act as a BitTorrent seeder, just append &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?torrent&lt;/code&gt; to any file
URL.&lt;/p&gt;

&lt;p&gt;S3 first launched in 2006 and since then many competitors has sprung up (often
API compatible with S3), for example Google Cloud Storage and Azure Storage. S3
has a 99.9% SLA (i.e it can be down up to 60 * 24 * 30 * (1 - 0.999) == 43.2
minutes per month) but this only means that you get a 10-25% discount the next
month if the service has been down more than that. S3 is billed both based on
how much you store and how much that content is downloaded. It does not offer
any automated cost control (hard quota based on pre-paid amount), but you can
configure AWS to send you an alert if your bill exceeds a limit you set. There
is a billing dashboard that gives you a quick overview, and if you’re on the
free tier it will tell you how many free S3 PUTs / GETs you have left each month
etc. It’s also possible to create “Requester Pays” buckets where the person
downloading pays for the request, or optionally pays for the request plus some
price that is paid out to the bucket owner via AWS DevPay.&lt;/p&gt;

&lt;p&gt;Another basic way to interact with S3 buckets are via the AWS CLI utility. If
you’re using Ubuntu you can usually just do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt-get install awscli&lt;/code&gt; to
get it, or if you for some reasons need the absolute latest version you might
want to do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt-get install python-pip&lt;/code&gt; followed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo pip install
awscli&lt;/code&gt; instead. Once you have that installed, you can list the contents of
public S3 buckets. For example, there is a public S3 bucket called “flaws.cloud”
and you can list the contents in it by running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws --no-sign-request s3 ls flaws.cloud
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By default the AWS CLI utility will sign all requests using your credentials,
but you can access public buckets without even having your own credentials if
you pass the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-sign-request&lt;/code&gt; parameter like above.&lt;/p&gt;

&lt;p&gt;To create your own S3 buckets you need to &lt;a href=&quot;https://aws.amazon.com/&quot;&gt;register an AWS
account&lt;/a&gt; (there is a free tier, that you can use for up
to a year, that includes basic variants of both S3 and EC2 and bunch of other
stuff). Once you have registered, go to the AWS management console and click
your name in the top-right corner. From that dropdown menu, select “My Security
Credentials”, and then click “Continue to Security Credentials”. You really
don’t want AWS credentials stolen, so before doing anything else, it’s a good
idea to enable two-factor authentication (you can use any TOTP device like a
smartphone running Google authenticator). To do that, expand “Multi-Factor
Authentication” and click “Activate MFA”, select “A virtual MFA device”, scan
the barcode etc.&lt;/p&gt;

&lt;p&gt;To access AWS programmatically or via the AWS CLI utility you need to create an
access key, you can do this directly from the “Security Credentials” screen on
your account but such a key would have complete access to your entire account so
instead it’s recommended that you create separate sub-accounts using Amazon IAM
and that each such account is given only the specific access that it needs. To
do this, click the “Services” link in the top left corner and select “IAM”. Then
click “Users”, then “Add User”, check “Programmatic Access” and in the
“Permissions” step, use “Attach existing policies directly” to give the user
“AmazonS3FullAccess”. For a production system you’d probably want to attach the
permissions to a group instead of course. Once the user has been created you get
an “access key id” (starting with “AKIA…”) and an “Secret access key”.&lt;/p&gt;

&lt;p&gt;Once you have an “access key id” and the corresponding “secret” you can
configure these in the AWS CLI utility by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aws configure&lt;/code&gt;. This will
make the specified key the default key, so all commands submitted to AWS using
the CLI utility will be signed by this key.&lt;/p&gt;

&lt;p&gt;Once you have a default key setup, you can find S3 commands described in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aws
s3 help&lt;/code&gt; and more advanced S3 API calls described in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aws s3api help&lt;/code&gt;. For
example:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Try to create a bucket called &quot;test&quot; (hits BucketAlreadyExists since name is taken)&lt;/span&gt;
aws s3 mb s3://test

&lt;span class=&quot;c&quot;&gt;# List all your buckets&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Upload a file to a bucket&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;file.txt s3://bucket-name-here/

&lt;span class=&quot;c&quot;&gt;# Upload a file to a bucket ... and also make it world readable&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;file.txt s3://bucket-name-here/ &lt;span class=&quot;nt&quot;&gt;--acl&lt;/span&gt; public-read

&lt;span class=&quot;c&quot;&gt;# Show the contents of a text file stored in a bucket&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;s3://some-bucket-name/object -

&lt;span class=&quot;c&quot;&gt;# Remove a file from a bucket&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;rm &lt;/span&gt;s3://my-bucket-name/file.txt

&lt;span class=&quot;c&quot;&gt;# Remove a bucket (only works if bucket is empty)&lt;/span&gt;
aws s3 rb s3://bucket-name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above commands all operate on the default region you specified when you ran
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aws configure&lt;/code&gt; but you can pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--region foo&lt;/code&gt; to use another region.
Also, if you want, you can configure several keys in the AWS CLI tool, just use
a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--profile profile-name-here&lt;/code&gt; parameter to select the key you want to use
(both when you run “aws configure” and then every time you invoke a regular
command).&lt;/p&gt;

&lt;p&gt;When interacting with the S3 REST endpoint, you can two different URL styles,
called “Virtual Hosted-style” or “Path-style”. In the former, the bucket name is
part of the domain, and in the latter it is the first part of the path. For
example, if you have uploaded a file to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stuff/items/file.txt&lt;/code&gt; in the bucket
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;situla-deadbeef&lt;/code&gt; in region &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eu-west-1&lt;/code&gt;, it can be referred to using
either one of the curl command URLs below:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws s3 &lt;span class=&quot;nt&quot;&gt;--region&lt;/span&gt; eu-west-1 mb s3://situla-123
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;hello | aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; - s3://situla-123/stuff/items/file.txt &lt;span class=&quot;nt&quot;&gt;--acl&lt;/span&gt; public-read
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws s3 &lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;s3://situla-123/stuff/items/file2.txt -
hello

&lt;span class=&quot;c&quot;&gt;# First curl the file using &quot;virtual hosted-style URL&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl https://situla-123.s3-eu-west-1.amazonaws.com/stuff/items/file.txt
hello

&lt;span class=&quot;c&quot;&gt;# And now curl the file using &quot;path-style URL&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl https://s3-eu-west-1.amazonaws.com/situla-123/stuff/items/file4.txt
hello
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One thing to keep in mind is that you might run into SSL certificate name
mismatch when using “virtual hosted-style URLs” for buckets that have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;
in their name.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws s3 &lt;span class=&quot;nt&quot;&gt;--region&lt;/span&gt; eu-west-1 mb s3://fail.pail
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;hello | aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; - s3://fail.pail/file.txt &lt;span class=&quot;nt&quot;&gt;--acl&lt;/span&gt; public-read

&lt;span class=&quot;c&quot;&gt;# Beware that bucket names that contain &quot;.&quot; gives you a SSL name mismatch error&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl https://fail.pail.s3-eu-west-1.amazonaws.com/file.txt
curl: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;51&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; SSL: certificate subject name &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.s3-eu-west-1.amazonaws.com&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; does
      not match target host name &lt;span class=&quot;s1&quot;&gt;&apos;fail.pail.s3-eu-west-1.amazonaws.com&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# But loading the same file over HTTPS using &quot;Path-style URL&quot; works fine&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl https://s3-eu-west-1.amazonaws.com/fail.pail/file.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And you are not allowed to create buckets with names that cannot be used in a
hostname, for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foo..bar&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foobar.&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.foobar&lt;/code&gt; are all
illegal bucket names. Bucket names may consist of 3-63 characters.&lt;/p&gt;

&lt;p&gt;It’s also possible to make an S3 bucket listable even for anonymous users (for
people that knows the bucket name). However, just like it’s a good idea to keep
directory listing turned off on webservers, most of the time it’s best to not
allow anonymous users to list your bucket contents; this is true even if many or
all objects (files) in the bucket are marked with ACL “public-read”. If you want
to make a bucket listable for anonymous users anyway, you can do it like this:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Try to list bucket contents anonymously&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws &lt;span class=&quot;nt&quot;&gt;--no-sign-request&lt;/span&gt; s3 &lt;span class=&quot;nb&quot;&gt;ls &lt;/span&gt;s3://situla-123

&lt;span class=&quot;c&quot;&gt;# Make the bucket contents listable for anonymous users (previous command now works)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws s3api put-bucket-acl &lt;span class=&quot;nt&quot;&gt;--bucket&lt;/span&gt; situla-123 &lt;span class=&quot;nt&quot;&gt;--acl&lt;/span&gt; public-read
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Other useful things you might need to do are:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Upload (or update if it already exists) a whole directory to S3&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;sync&lt;/span&gt; ~/mydir s3://my-bucket/

&lt;span class=&quot;c&quot;&gt;# List files inside a bucket recursively&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--recursive&lt;/span&gt; s3://situla-123

&lt;span class=&quot;c&quot;&gt;# Make a public file private again&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws s3api put-object-acl &lt;span class=&quot;nt&quot;&gt;--bucket&lt;/span&gt; my-bucket &lt;span class=&quot;nt&quot;&gt;--key&lt;/span&gt; stuff/hello.txt &lt;span class=&quot;nt&quot;&gt;--acl&lt;/span&gt; private

&lt;span class=&quot;c&quot;&gt;# Create a presigned URL for public access (valid for 30 seconds then stops working)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws s3 presign s3://situla-42/private.txt &lt;span class=&quot;nt&quot;&gt;--expires-in&lt;/span&gt; 30
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To access your bucket as a static website you need to enable “Static website
hosting” for it. The easiest way to do this is via the AWS console web UI, just
select the bucket and switch to the “Properties” tab and you’ll find it there.
The main difference compared to HTTP GET operations issued to the API REST
endpoint is that the static website endpoint will serve a HTTP 403 Forbidden
that contains HTML code instead of an XML error message. For URLs that point to
files that doesn’t exists, the website endpoint returns a 404 document that you
can configure when you enable “Static website hosting”. Unlike the REST
endpoint, the website endpoint can also return redirects. You can access the
website endpoint via:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http://foo-bucket.s3-website-REGION.amazonaws.com/prefix1/prefix2/file.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When talking about the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s3://my-bucket/subdir/blah/file.txt&lt;/code&gt;, the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subdir/blah&lt;/code&gt; is a prefix to the full key name which is
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subdir/blah/file.txt&lt;/code&gt;. There is no concept of folders or subdirectories etc
even though the simple web UI that S3 hosts kind of makes it look like S3 has
folders. The full key name is a string of Unicode characters for which the UTF-8
encoding is at most 1024 bytes. While all Unicode characters are allowed,
certain characters like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; needs URL encoding so they
are a bit messy (especially, if your application has a bug and you need to do
some trouble shooting using curl etc). The AWS S3 documentation has a &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#object-keys&quot;&gt;list of
characters&lt;/a&gt;
that you should avoid in key names if possible.&lt;/p&gt;

&lt;p&gt;In the properties page of the bucket inside the AWS web UI you can also enable
versioning of all objects in that bucket. Once you’ve done this, the change
history of each file is saved. You can then do:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# List all versions of the object specified by --prefix foo&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws s3api list-object-versions &lt;span class=&quot;nt&quot;&gt;--bucket&lt;/span&gt; bkt &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;--prefix&lt;/span&gt; private.txt | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.Versions[] | .LastModified + &quot; &quot; + .VersionId&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Use .versionId printed above to retrieve the old version of the file&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws s3api get-object &lt;span class=&quot;nt&quot;&gt;--bucket&lt;/span&gt; bkt &lt;span class=&quot;nt&quot;&gt;--key&lt;/span&gt; private.txt &lt;span class=&quot;nt&quot;&gt;--version-id&lt;/span&gt; VERSION_ID foo.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, I would like to recommend the &lt;a href=&quot;http://flaws.cloud/&quot;&gt;flAWS challenge&lt;/a&gt;,
it’s a fun little challenge where you need to fiddle around with AWS CLI tool a
bit to gain access to the next “level”.&lt;/p&gt;
</description>
        <pubDate>Mon, 06 Mar 2017 00:00:00 +0000</pubDate>
        <link>http://mo.github.io/2017/03/06/aws-s3-intro.html</link>
        <guid isPermaLink="true">http://mo.github.io/2017/03/06/aws-s3-intro.html</guid>
        
        <category>backend</category>
        
        <category>cloud</category>
        
        
      </item>
    
  </channel>
</rss>
