<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Blog about testing &amp; documentation for KnapsackPro.com</title>
    <description>Speed up your tests with optimal test suite parallelisation on your CI. Blog &amp; documentation for Knapsack Pro.</description>
    <link>https://docs.knapsackpro.com/</link>
    <atom:link href="https://docs.knapsackpro.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 02 Mar 2026 10:07:14 +0000</pubDate>
    <lastBuildDate>Mon, 02 Mar 2026 10:07:14 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>Have you ever used Ruby&apos;s Object#method?</title>
        <description>&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object#method&lt;/code&gt; is a little-known, yet very interesting Ruby method. Let’s see how it works and how you can use it in your code.&lt;/p&gt;

&lt;h2 id=&quot;the-basics&quot;&gt;The basics&lt;/h2&gt;

&lt;p&gt;Let’s imagine we have some Ruby class, e.g.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Animal&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;speak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;I say &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sound&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Calling the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#method&lt;/code&gt; on the instance of this class, would return a &lt;a href=&quot;https://ruby-doc.org/core-3.0.1/Method.html&quot; target=&quot;_blank&quot;&gt;Method&lt;/a&gt; object. This &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Method&lt;/code&gt; object acts as a closure in the context of the associated object.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;animal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Animal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;met&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:speak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;met&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Method&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;met&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inspect&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#&amp;lt;Method: Animal#speak(sound)&amp;gt;&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;met&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# we forget to pass the argument&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; ArgumentError (wrong number of arguments (given 0, expected 1))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;met&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;meow&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# I say meow!&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; nil&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;One thing that’s interesting in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Method&lt;/code&gt; objects, is the fact they implement the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#to_proc&lt;/code&gt; method. To see how this could be useful, let’s first remind ourselves of a widely-used Ruby shortcut.&lt;/p&gt;

&lt;h2 id=&quot;the-ampersand-operator&quot;&gt;The ampersand operator&lt;/h2&gt;

&lt;p&gt;Consider this common Ruby shortcut:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&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;2&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;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;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&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;num&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;even?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [2, 4, 6]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# commonly simplified with:&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;2&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;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;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:even?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [2, 4, 6]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The reason this works is two-fold. First, there is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt; (ampersand) operator. When used at the beginning of a method argument, it transforms its operant into a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Proc&lt;/code&gt; object (by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#to_proc&lt;/code&gt; on it), and passes it to the method as if it was a block. Secondly, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Symbol&lt;/code&gt; class implements &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#to_proc&lt;/code&gt;. The way it’s implemented effectively allows us to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send&lt;/code&gt; a given symbol to the provided argument. See &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Symbol#to_proc&lt;/code&gt; in action below:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;is_even&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:even?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_proc&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;is_even&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&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;c1&quot;&gt;#=&amp;gt; false&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;is_even&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&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;c1&quot;&gt;#=&amp;gt; true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This proc could also be passed to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#select&lt;/code&gt; method from the previous example. We still need to use the ampersand operator (so that Ruby knows to treat it like a block).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&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;2&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;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;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_even&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [2, 4, 6]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;ampersand-with-the-method-object&quot;&gt;Ampersand with the Method object&lt;/h3&gt;

&lt;p&gt;Given the fact that the Method object implements the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#to_proc&lt;/code&gt; method as well, we can use it much the same way as we did the symbol. Let’s circle back to our previous example to see this in action.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Animal&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;speak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;I say &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sound&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;met&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Animal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:speak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;animal_sounds&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;s1&quot;&gt;&apos;meow&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;woof&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;moo&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;oink&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;hee-haw&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;animal_sounds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;met&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# I say meow!&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# I say woof!&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# I say moo!&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# I say oink!&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# I say hee-haw!&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; [&quot;meow&quot;, &quot;woof&quot;, &quot;moo&quot;, &quot;oink&quot;, &quot;hee-haw&quot;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;so-when-is-this-useful&quot;&gt;So when is this useful?&lt;/h2&gt;

&lt;p&gt;One reason I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#method&lt;/code&gt; combined with the ampersand operator in the past was to get rid of repetitive, one-dimensional blocks. This is especially valuable, when you are chaining multiple methods together. Let’s say we have multiple filters with the same interface (all returning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;) that we want to use on a collection:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AboveMinThresholdChecker&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;MIN_THRESHOLD&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MIN_THRESHOLD&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BelowMaxThresholdChecker&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PrimeNumberChecker&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Using explicit blocks, we would then do something like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;numbers&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;mi&quot;&gt;1&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;nf&quot;&gt;to_a&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&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;num&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AboveMinThresholdChecker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&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;nf&quot;&gt;select&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;num&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BelowMaxThresholdChecker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&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;nf&quot;&gt;select&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;num&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PrimeNumberChecker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&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;/figure&gt;

&lt;p&gt;With our new knowledge, we can simplify the above in the following way using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#method&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AboveMinThresholdChecker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BelowMaxThresholdChecker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PrimeNumberChecker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Have you used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#method&lt;/code&gt; in Ruby before? Can you think of other interesting situations it could prove handy? Please share in the comments below!&lt;/p&gt;

&lt;p&gt;If your project suffers from long CI builds, you can address this with &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=have-you-ever-used-rubys-object-method&quot;&gt;Knapsack Pro&lt;/a&gt;. Be the hero in your team by streamlining your CI process and &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=using-ruby-object-method&quot;&gt;improving the developer productivity&lt;/a&gt;!&lt;/p&gt;
</description>
        <pubDate>Tue, 27 Apr 2021 08:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/have-you-ever-used-rubys-object-method</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/have-you-ever-used-rubys-object-method</guid>
        
        
        <category>techtips</category>
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>Estimate database connections pool size for Rails application</title>
        <description>&lt;p&gt;Configuring the database connections pool for the Rails app might not be a straightforward task for many programmers. There is a constraint of max opened connections on a database level. Your server environment configuration can change in time and affect the number of connections to the database required. For instance number of servers you use can change when you autoscale it based on the web traffic. It means that the number of web processes/threads running for Puma or Unicorn servers could change. All this adds additional complexity. When you use two databases (e.g. Postgres + Redis), everything gets more complex. In this article, we will address that. You will learn how to estimate needed database connections for your Ruby on Rails production application.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/estimate-database-connections-pool-size-for-rails-application/rails-db-pool.jpeg&quot; style=&quot;width:400px;margin-left: 15px;float:right;&quot; alt=&quot;Rails, RoR, DB, database, pool&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-do-available-database-connections-matter&quot;&gt;Why do available database connections matter?&lt;/h2&gt;

&lt;p&gt;The first question is, why do you need to care about available database connections? The answer is simple. Suppose you configured your Ruby application to open too many DB connections. In that case, it could happen that you will get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord::ConnectionTimeoutError&lt;/code&gt; exceptions from the application when the database cannot handle more new connections from your Rails app. It can result in 500 errors visible for your web app users.&lt;/p&gt;

&lt;p&gt;This problem might not be apparent immediately. Often you will find out about it in production. Your application might work just fine until specific circumstances cause the Rails app to need more DB connections, which can trigger exception flood. Let’s see how to avoid it.&lt;/p&gt;

&lt;h2 id=&quot;ror-application-configuration-step-by-step&quot;&gt;RoR application configuration step by step&lt;/h2&gt;

&lt;p&gt;Let’s break a typical Ruby on Rails application down into smaller components that use databases.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We have a Rails application that uses the Postgres database for ActiveRecord usage.&lt;/li&gt;
  &lt;li&gt;We also use the Redis database for background workers like Sidekiq.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It looks simple, isn’t it? Let’s start with that, and later on, we will add more complexity to the mix :)&lt;/p&gt;

&lt;h2 id=&quot;postgres-database-connections---how-to-check-the-limit&quot;&gt;Postgres database connections - how to check the limit?&lt;/h2&gt;

&lt;p&gt;How to check how many available connections do you have for Postgres?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If you use a dedicated server with Postgres installed, then most likely you have a default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_connections&lt;/code&gt; which is typically 100 connections.&lt;/li&gt;
  &lt;li&gt;If you use a Postgres instance on the AWS, then you need to check the AWS documentation to find out what’s the max allowed connections to your database instance (it depends on if you use Amazon RDS or Aurora and what is server instance class)&lt;/li&gt;
  &lt;li&gt;If you use Heroku, you can check the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection Limit&lt;/code&gt; for the &lt;a href=&quot;https://elements.heroku.com/addons/heroku-postgresql#pricing&quot;&gt;Postgres Heroku add-on&lt;/a&gt; to check max acceptable connections.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;activerecord-connection-pool&quot;&gt;ActiveRecord connection pool&lt;/h2&gt;

&lt;p&gt;In your Rails application, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/database.yml&lt;/code&gt; file contains the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pool&lt;/code&gt; option. As explained in the &lt;a href=&quot;https://edgeguides.rubyonrails.org/configuring.html#database-pooling&quot;&gt;Rails docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Active Record database connections are managed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord::ConnectionAdapters::ConnectionPool&lt;/code&gt;, which ensures that a connection pool synchronizes the amount of thread access to a limited number of database connections.&lt;/p&gt;

  &lt;p&gt;Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, Puma, Unicorn, etc.) should behave the same. The database connection pool is initially empty. As demand for connections increases, it will create them until it reaches the connection pool limit.&lt;/p&gt;

  &lt;p&gt;Any one request will check out a connection the first time it requires access to the database. At the end of the request, it will check the connection back in. This means that the additional connection slot will be available again for the next request in the queue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pool&lt;/code&gt; can be defined this way:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;blog_production&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;or as a part of a URL to the database:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;development&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql://localhost/blog_production?pool=5&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The URL option is popular when you host a database on an external server like Amazon RDS. Then you could define the URL this way:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres://blog_production:PASSWORD@blog-production.abcdefgh.eu-west-1.rds.amazonaws.com/blog_production?sslca=config/rds-combined-ca-bundle.pem&amp;amp;pool=5&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Please note that for the production, you should not commit credentials in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/database.yml&lt;/code&gt; file. Instead, store it in environment variables and then read the value at your Rails app’s runtime.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;%= ENV[&apos;DB_URL&apos;] %&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;how-does-activerecord-connection-pool-affects-postgres-max-connections&quot;&gt;How does ActiveRecord connection pool affects Postgres max connections?&lt;/h2&gt;

&lt;p&gt;Let’s start with a simple example. Your application may use one of the application servers like Puma or Unicorn. Let’s focus on Puma because it’s more complex as it has a separate configuration for several processes (known as workers in Puma terms) and threads. Unicorn runs in a single thread only. It works exactly like Puma with a single thread setting.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/estimate-database-connections-pool-size-for-rails-application/puma.jpeg&quot; style=&quot;width:400px;margin-left: 15px;float:right;&quot; alt=&quot;Rails, RoR, DB, database, pool&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;puma-config-1-process-and-1-thread&quot;&gt;Puma config: 1 process and 1 thread&lt;/h3&gt;

&lt;p&gt;Let’s say you use the Puma server to run the Rails application. The Puma is configured to run 1 process (worker) and it has only 1 thread.&lt;/p&gt;

&lt;p&gt;The puma process can open up to 5 connections to the database because the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pool&lt;/code&gt; option is defined as 5 in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/database.yml&lt;/code&gt;. Typically, there are fewer connections than that because when you run 1 process and only 1 thread, only 1 connection to the Postgres database will be needed to make a database query.&lt;/p&gt;

&lt;p&gt;Sometimes the database connection might be dead. In such a case, ActiveRecord can open a new connection, and then you may end up with 2 active connections. In the worst-case scenario when 4 connections would be dead, then Rails can open 5 connections max.&lt;/p&gt;

&lt;h3 id=&quot;puma-config-1-process-and-2-threads&quot;&gt;Puma config: 1 process and 2 threads&lt;/h3&gt;

&lt;p&gt;If you use 2 threads in a single Puma process (worker) then it means those 2 threads can use the same pool of DB connections within the Puma process.&lt;/p&gt;

&lt;p&gt;It means that 2 DB connections will be open out of 5 possible. If any connection is dead, then more connections can be opened until the 5 connection pool limit is reached.&lt;/p&gt;

&lt;h3 id=&quot;puma-config-2-processes-and-2-threads-per-process&quot;&gt;Puma config: 2 processes and 2 threads per process&lt;/h3&gt;

&lt;p&gt;If you run 2 Puma processes (workers) and each process has 2 threads then it means that each single process will open 2 DB connections because you have 2 threads per process. You have 2 processes so it means at the start of your application, there might be 4 DB connections open. Each process has its pool, so you have 2 pools. Each pool can open up to 5 DB connections. It means that in the worst-case scenario, there can be even 10 connections created to the database.&lt;/p&gt;

&lt;p&gt;Assuming you use 2 threads per Puma process, it’s good to have the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pool&lt;/code&gt; option set to 2 + some spare connections. It allows ActiveRecord to open a new connection if one of the DB connections is dead.&lt;/p&gt;

&lt;h3 id=&quot;puma-config-2-processes-and-2-threads-and-2-web-dynos-on-heroku&quot;&gt;Puma config: 2 processes and 2 threads, and 2 web dynos on Heroku&lt;/h3&gt;

&lt;p&gt;If you use Heroku to host your application, it allows scaling your web application horizontally by adding more servers (dynos). Assume you run your application on 2 servers (2 Heroku dynos), each dyno is running 2 Puma processes, and each process has 2 threads. It means at the start, your application may open 6 connections to the database. Here is why:&lt;/p&gt;

&lt;p&gt;2 dynos X 2 Puma processes X 2 Puma threads = 6 DB connections&lt;/p&gt;

&lt;p&gt;2 dynos X 2 Puma process X Pool size (5) = Total pool size 20&lt;/p&gt;

&lt;p&gt;It means that in the worst-case your application may open 20 DB connections.&lt;/p&gt;

&lt;h4 id=&quot;autoscaling-web-application&quot;&gt;Autoscaling web application&lt;/h4&gt;

&lt;p&gt;If you autoscale your web servers by adding more servers during the peak web traffic, you need to be careful. Ensure your application stays within the Postgres max connections limit. The above example shows you how to calculate expected opened DB connections and the worst-case scenario.  Please adjust your pool size to ensure that you will be below the max connections limit for your database engine in the worst-case scenario.&lt;/p&gt;

&lt;h2 id=&quot;what-else-can-open-db-connections&quot;&gt;What else can open DB connections?&lt;/h2&gt;

&lt;p&gt;We just talked about a webserver like Puma that can open connections and consume your max DB connections limit. But other non-web processes can do it as well:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You run Rails console on production in a Heroku dyno &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku run bin/rails console --app=my-app-name&lt;/code&gt;. It runs an instance of your Rails app, and 1 DB connection will be open. In the worst-case scenario, the number of connections defined in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pool&lt;/code&gt; can be opened. But it’s unlikely that your DB connections would go dead. So the whole pool limit shouldn’t be used.&lt;/li&gt;
  &lt;li&gt;You run scheduled rake tasks via Heroku Scheduler (cron-like tool). If the rake tasks are performed periodically, they need to open a connection to the DB so that at least 1 DB connection is used from the pool per rake task. Imagine you have 10 rake tasks that are started every hour. It means you need 10 available DB connections every hour. It can be easy to miss this if you base your estimation on just the web connections.&lt;/li&gt;
  &lt;li&gt;You use background workers like Sidekiq to perform async jobs. Your jobs may open DB connections. We will talk about it later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;background-worker---sidekiq-and-activerecord-pool&quot;&gt;Background worker - Sidekiq and ActiveRecord pool&lt;/h2&gt;

&lt;p&gt;Sidekiq process will use the pool defined in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/database.yml&lt;/code&gt; similarly as Puma. All Sidekiq threads in a Sidekiq process can use a common pool of connections.&lt;/p&gt;

&lt;p&gt;If you run multiple servers (Heroku dynos), then it works similarly to the Puma example.&lt;/p&gt;

&lt;p&gt;2 servers (dynos) X 1 Sidekiq process X 10 Sidekiq threads = 20 DB connections will be open.&lt;/p&gt;

&lt;p&gt;You need to have a pool size of at least 10 in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/database.yml&lt;/code&gt; because Sidekiq by default, uses 10 threads.&lt;/p&gt;

&lt;p&gt;If you use a pool size smaller than 10, Sidekiq threads will contend the access to the limited connections in the pool. It may work out fine, but be aware this can increase your job’s processing time. It can also lead to &lt;a href=&quot;https://github.com/sidekiq/sidekiq/wiki/Problems-and-Troubleshooting#cannot-get-database-connection-within-500-seconds&quot;&gt;this problem&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;sidekiq-and-redis-database-connections&quot;&gt;Sidekiq and Redis database connections&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/estimate-database-connections-pool-size-for-rails-application/redis.jpeg&quot; style=&quot;width:200px;margin-left: 15px;float:right;&quot; alt=&quot;Rails, RoR, DB, database, pool&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Sidekiq uses the Redis database to store async jobs. It would be best if you calculate DB connections to Redis as well as Postgres connections. A Sidekiq server process requires at least (concurrency + 5) connections. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;concurrency&lt;/code&gt; option is the number of Sidekiq threads per Sidekiq process.&lt;/p&gt;

&lt;p&gt;Using the previous example:&lt;/p&gt;

&lt;p&gt;2 servers (dynos) X 1 Sidekiq process X 10 Sidekiq threads = 2 servers (dynos) X 1 Sidekiq process X (10 + 5) = 30 Redis connections required.&lt;/p&gt;

&lt;p&gt;More on the &lt;a href=&quot;https://github.com/sidekiq/sidekiq/wiki/Using-Redis&quot;&gt;Sidekiq docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;redis-database-connections&quot;&gt;Redis database connections&lt;/h2&gt;

&lt;p&gt;If you use Redis for processing background jobs, then it’s not just the Sidekiq process that is using Redis connections. Your Puma process and threads can use Redis to add new jobs to the Sidekiq queue as well. Typically you will have 1 Redis connection per 1 Puma thread.&lt;/p&gt;

&lt;p&gt;If you explicitly open a new Redis connection with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Redis.new&lt;/code&gt;, this can create a new connection per the Puma thread as well.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;We covered a few examples of Postgres and Redis on calculating DB connections needed by your Rails application. I hope this will give you a better understanding of how to estimate how many DB connections you need on your database level to serve your application’s demands properly.&lt;/p&gt;

&lt;p&gt;If you are looking to improve your Rails application workflow please consider checking how to &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;run automated tests in parallel on your CI server&lt;/a&gt; with &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=estimate-database-connections-pool-size-for-rails-application&quot;&gt;Knapsack Pro&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 26 Apr 2021 20:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/estimate-database-connections-pool-size-for-rails-application</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/estimate-database-connections-pool-size-for-rails-application</guid>
        
        
        <category>techtips</category>
        
      </item>
    
      <item>
        <title>Querying single columns in Rails Active Record using the #pluck method</title>
        <description>&lt;p&gt;Have you heard of the &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pluck&quot; target=&quot;_blank&quot;&gt;#pluck&lt;/a&gt; method from Rails’ Active Record? Read on to see what it does and how it can be used to easily query single columns from your database!&lt;/p&gt;

&lt;h2 id=&quot;the-use-case&quot;&gt;The use case&lt;/h2&gt;

&lt;p&gt;Sometimes when retrieving data from the database, we are only interested in certain attributes (e.g., just emails of all the users, instead of data residing in all columns of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt; table).&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;naïve&lt;/em&gt; approach would be to load all the data into the memory and then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; it so that only the subset of the data is left, e.g.:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# SELECT &quot;users&quot;.* FROM &quot;users&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [&quot;kate@example.com&quot;, &quot;john@example.org&quot;, &quot;janet@example.com&quot;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is quite inefficient.&lt;/p&gt;

&lt;p&gt;First of all, the SQL query is retrieving data from all columns from the table.
Secondly, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecrodCollection&lt;/code&gt; object is created in the memory. Its members are initialized with all the data retrieved from the DB. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; at the end returns a regular Ruby &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; object, making this intermediate step unnecessary.&lt;/p&gt;

&lt;p&gt;Knowing we only really need to load the data from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;email&lt;/code&gt; column, we could improve the above by chaining the Active Record’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#select&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# SELECT &quot;users&quot;.&quot;email&quot; FROM &quot;users&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [&quot;kate@example.com&quot;, &quot;john@example.org&quot;, &quot;janet@example.com&quot;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The SQL query is limited to one column now, but the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt; objects are still being created in the intermediate step before &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;. Granted, they contain less data now (as the attributes we didn’t &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select&lt;/code&gt; are omitted), but it’s still quite unnecessary in this use case.&lt;/p&gt;

&lt;h2 id=&quot;using-the-pluck&quot;&gt;Using the #pluck&lt;/h2&gt;

&lt;p&gt;The good news is, we can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pluck&lt;/code&gt; instead to solve this issue. It’s as simple as:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pluck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# SELECT &quot;users&quot;.&quot;email&quot; FROM &quot;users&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [&quot;kate@example.com&quot;, &quot;john@example.org&quot;, &quot;janet@example.com&quot;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The SQL is still retrieving just the data we need. The data is then returned in the &lt;em&gt;Plain Old Ruby&lt;/em&gt; Array, and no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt; object is being created in between!&lt;/p&gt;

&lt;h3 id=&quot;retrieving-data-from-multiple-columns&quot;&gt;Retrieving data from multiple columns&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pluck&lt;/code&gt; method works with multiple DB columns, too. Just pass their names as arguments.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&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;user&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;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;email&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;# SELECT &quot;users&quot;.&quot;id&quot;, &quot;users&quot;.&quot;email&quot; FROM &quot;users&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [[1, &quot;kate@example.com&quot;], [4, &quot;john@example.org&quot;], [5, &quot;janet@example.com&quot;]]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Just do this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pluck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# SELECT &quot;users&quot;.&quot;id&quot;, &quot;users&quot;.&quot;email&quot; FROM &quot;users&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [[1, &quot;kate@example.com&quot;], [4, &quot;john@example.org&quot;], [5, &quot;janet@example.com&quot;]]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;When querying the database, it’s important to be mindful about what data we retrieve and what is being done with it. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pluck&lt;/code&gt; method both keeps the SQL query to the minimum and avoids the ActiveRecord object creation overhead when it’s not needed in a given scenario.&lt;/p&gt;

&lt;p&gt;Have you used the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#pluck&lt;/code&gt; method in your project? Please share in the comments.&lt;/p&gt;

&lt;p&gt;And if your project could benefit from faster CI builds, consider using &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=querying-single-columns-in-rails-active-record-using-pluck-method&quot;&gt;Knapsack Pro&lt;/a&gt; to achieve that!&lt;/p&gt;
</description>
        <pubDate>Sat, 17 Apr 2021 08:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/querying-single-columns-in-rails-active-record-using-pluck-method</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/querying-single-columns-in-rails-active-record-using-pluck-method</guid>
        
        
        <category>techtips</category>
        
        <category>ruby</category>
        
        <category>rails</category>
        
      </item>
    
      <item>
        <title>How BitBucket Pipeline with parallel Cypress tests can speed up CI build</title>
        <description>&lt;p&gt;Do you use BitBucket Pipeline as your CI server? Are you struggling with slow E2E tests in Cypress? Did you know BitBucket Pipeline can run parallel steps? You can use it to distribute your browser tests across several parallel steps to execute end-to-end Cypress tests in a short amount of time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-bitbucket-pipeline-with-parallel-cypress-tests-can-speed-up-ci-build/bitbucket-cypress.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;BitBucket, Pipeline, CI, Cypress&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-to-run-tests-in-parallel&quot;&gt;How to run tests in parallel&lt;/h2&gt;

&lt;p&gt;Distributing tests across parallel steps to spread the workload and run tests faster might be more challenging than you think. The question is how to divide Cypress test files across the parallel jobs in order to ensure the work is distributed evenly? But… is distributing work evenly what you actually want?&lt;/p&gt;

&lt;p&gt;To get the shortest CI build time you want to utilize the available CI resources to the fullest. You want to avoid wasting time. This means that you want to ensure the parallel steps will finish work at a similar time as this would mean there are no bottlenecks in CI machines utilization.&lt;/p&gt;

&lt;p&gt;Many things are unknown and unpredictable. This can affect how long it will take to execute tests on BitBucket Pipeline. There are things like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;boot time - time spent on loading your CI docker container&lt;/li&gt;
  &lt;li&gt;loading npm/yarn dependencies from cache&lt;/li&gt;
  &lt;li&gt;running Cypress tests&lt;/li&gt;
  &lt;li&gt;tests can run against different browsers and this can affect how long the tests are executed&lt;/li&gt;
  &lt;li&gt;sometimes tests can fail and their execution time is different&lt;/li&gt;
  &lt;li&gt;other times you may have &lt;a href=&quot;/2021/fix-intermittently-failing-ci-builds-flaky-tests-rspec&quot;&gt;flaky tests randomly failing&lt;/a&gt; and you could use Test Retries in Cypress to automatically rerun failed test cases. This results in running a test file for longer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the above contribute to the uncertainty around execution time. It’s hard to know how best to divide test files across the parallel steps to ensure the steps complete work at a similar time. But there is a solution to that - a dynamic test suite split during runtime.&lt;/p&gt;

&lt;h2 id=&quot;queue-mode---a-dynamic-tests-split&quot;&gt;Queue Mode - a dynamic tests split&lt;/h2&gt;

&lt;p&gt;To distribute tests work across BitBucket Pipeline parallel steps you can use &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-bitbucket-pipeline-with-parallel-cypress-tests-can-speed-up-ci-build&quot;&gt;Knapsack Pro&lt;/a&gt; with a Queue Mode. You can use &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress#knapsack-procypress&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt; npm package&lt;/a&gt; that will generate a Queue with a list of test files on the Knapsack Pro API side and then all parallel steps can connect to the queue to consume test files and execute them. This way parallel steps ask for more tests only after they finish executing a set of tests previously fetched from the Knapsack Pro API. You can learn about the &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;details of Queue Mode from the article&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;bitbucket-pipeline-yml-config&quot;&gt;BitBucket Pipeline YML config&lt;/h2&gt;

&lt;p&gt;Here is an example of a BitBucket Pipeline config in YML. As you can see, there are 3 parallel steps to run Cypress tests via &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-bitbucket-pipeline-with-parallel-cypress-tests-can-speed-up-ci-build&quot;&gt;Knapsack Pro&lt;/a&gt;. If you would like to run your tests on more parallel jobs you simply need to add more steps.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress/base:10&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;max-time&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;30&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# job definition for running E2E tests in parallel with KnapsackPro.com&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;e2e&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;e2e&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run E2E tests with @knapsack-pro/cypress&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;node&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# run web application in the background&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm run start:ci &amp;amp;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# env vars from https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;export KNAPSACK_PRO_CI_NODE_BUILD_ID=$BITBUCKET_BUILD_NUMBER&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;export KNAPSACK_PRO_COMMIT_HASH=$BITBUCKET_COMMIT&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;export KNAPSACK_PRO_BRANCH=$BITBUCKET_BRANCH&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;export KNAPSACK_PRO_CI_NODE_TOTAL=$BITBUCKET_PARALLEL_STEP&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;export KNAPSACK_PRO_CI_NODE_INDEX=$BITBUCKET_PARALLEL_STEP_COUNT&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# https://docs.knapsackpro.com/cypress/guide/#configuration-steps&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;export KNAPSACK_PRO_FIXED_QUEUE_SPLIT=true&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npx @knapsack-pro/cypress&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# store any generated images and videos as artifacts&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress/screenshots/**&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress/videos/**&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;pipelines&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Install dependencies&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;node&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm ci&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# run N steps in parallel&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*e2e&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*e2e&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*e2e&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;definitions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$HOME/.npm&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;cypress&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$HOME/.cache/Cypress&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you are looking for an example with a custom docker container for a parallel step please see &lt;a href=&quot;https://gist.github.com/ArturT/90b7ec869e3827b580664beb086a8cd6&quot;&gt;this one&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please remember to add your API token in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS&lt;/code&gt; environment variable as &lt;a href=&quot;https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/&quot;&gt;secure variable&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;BitBucket Pipeline is a CI server that allows running scripts in parallel. You can use parallel steps to distribute your Cypress tests with &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-bitbucket-pipeline-with-parallel-cypress-tests-can-speed-up-ci-build&quot;&gt;Knapsack Pro&lt;/a&gt; to save time and run CI build as fast as possible.&lt;/p&gt;
</description>
        <pubDate>Tue, 13 Apr 2021 18:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/how-bitbucket-pipeline-with-parallel-cypress-tests-can-speed-up-ci-build</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/how-bitbucket-pipeline-with-parallel-cypress-tests-can-speed-up-ci-build</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Using Ruby&apos;s 2.7 new #tally method</title>
        <description>&lt;p&gt;Ruby 2.7 introduced the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumberable#tally&lt;/code&gt; method. It allows to easily count elements’ occurrences in a given collection. In other words, it literally &lt;em&gt;tallies them up&lt;/em&gt;. :)&lt;/p&gt;

&lt;h2 id=&quot;the-use-case&quot;&gt;The use case&lt;/h2&gt;

&lt;p&gt;Say we have a collection of words, and we’d like to count the occurrences of each word within it.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;words&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w(the be to of and a in that have I it for not on with he as you do at
           the not for it be we her so up and a to for on with)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# some of the most common English words based on https://en.wikipedia.org/wiki/Most_common_words_in_English&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;pre-ruby-27&quot;&gt;Pre Ruby 2.7&lt;/h3&gt;

&lt;p&gt;In previous versions of Ruby, whenever we encountered a logic counting occurrences of collection elements (here: words in the array), we would most likely see a code similar to this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;word_counts&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;n&quot;&gt;words&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;word_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&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;mi&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;word_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&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;mi&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;word_counts&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; {&quot;the&quot;=&amp;gt;2, &quot;be&quot;=&amp;gt;2, &quot;to&quot;=&amp;gt;2, &quot;of&quot;=&amp;gt;1, &quot;and&quot;=&amp;gt;2, &quot;a&quot;=&amp;gt;2, &quot;in&quot;=&amp;gt;1, &quot;that&quot;=&amp;gt;1, &quot;have&quot;=&amp;gt;1, &quot;I&quot;=&amp;gt;1, &quot;it&quot;=&amp;gt;2, &quot;for&quot;=&amp;gt;3, &quot;not&quot;=&amp;gt;2, &quot;on&quot;=&amp;gt;2, &quot;with&quot;=&amp;gt;2, &quot;he&quot;=&amp;gt;1, &quot;as&quot;=&amp;gt;1, &quot;you&quot;=&amp;gt;1, &quot;do&quot;=&amp;gt;1, &quot;at&quot;=&amp;gt;1, &quot;we&quot;=&amp;gt;1, &quot;her&quot;=&amp;gt;1, &quot;so&quot;=&amp;gt;1, &quot;up&quot;=&amp;gt;1}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This could be simplified by initializing the Hash with a default value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; (which makes total sense in this case):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;word_counts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&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;n&quot;&gt;words&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&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;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&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;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;word_counts&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; {&quot;the&quot;=&amp;gt;2, &quot;be&quot;=&amp;gt;2, &quot;to&quot;=&amp;gt;2, &quot;of&quot;=&amp;gt;1, &quot;and&quot;=&amp;gt;2, &quot;a&quot;=&amp;gt;2, &quot;in&quot;=&amp;gt;1, &quot;that&quot;=&amp;gt;1, &quot;have&quot;=&amp;gt;1, &quot;I&quot;=&amp;gt;1, &quot;it&quot;=&amp;gt;2, &quot;for&quot;=&amp;gt;3, &quot;not&quot;=&amp;gt;2, &quot;on&quot;=&amp;gt;2, &quot;with&quot;=&amp;gt;2, &quot;he&quot;=&amp;gt;1, &quot;as&quot;=&amp;gt;1, &quot;you&quot;=&amp;gt;1, &quot;do&quot;=&amp;gt;1, &quot;at&quot;=&amp;gt;1, &quot;we&quot;=&amp;gt;1, &quot;her&quot;=&amp;gt;1, &quot;so&quot;=&amp;gt;1, &quot;up&quot;=&amp;gt;1}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We could even transform it further to a one-liner using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#each_with_object&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;words&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each_with_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&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;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_counts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word_counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&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;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; {&quot;the&quot;=&amp;gt;2, &quot;be&quot;=&amp;gt;2, &quot;to&quot;=&amp;gt;2, &quot;of&quot;=&amp;gt;1, &quot;and&quot;=&amp;gt;2, &quot;a&quot;=&amp;gt;2, &quot;in&quot;=&amp;gt;1, &quot;that&quot;=&amp;gt;1, &quot;have&quot;=&amp;gt;1, &quot;I&quot;=&amp;gt;1, &quot;it&quot;=&amp;gt;2, &quot;for&quot;=&amp;gt;3, &quot;not&quot;=&amp;gt;2, &quot;on&quot;=&amp;gt;2, &quot;with&quot;=&amp;gt;2, &quot;he&quot;=&amp;gt;1, &quot;as&quot;=&amp;gt;1, &quot;you&quot;=&amp;gt;1, &quot;do&quot;=&amp;gt;1, &quot;at&quot;=&amp;gt;1, &quot;we&quot;=&amp;gt;1, &quot;her&quot;=&amp;gt;1, &quot;so&quot;=&amp;gt;1, &quot;up&quot;=&amp;gt;1}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I personally like the second solution the most. I think it strikes the best balance between simplicity and ease of understanding.&lt;/p&gt;

&lt;p&gt;As we can see, all of the above approaches rely on iterating through the collection to tally up its members.&lt;/p&gt;

&lt;p&gt;There is also another approach that could be chosen: relying on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#group_by&lt;/code&gt;. If I were to use this one, the code I would write would probably look something like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;words&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:itself&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;c1&quot;&gt;# this produces a structure like: {&quot;the&quot;=&amp;gt;[&quot;the&quot;, &quot;the&quot;], &quot;be&quot;=&amp;gt;[&quot;be&quot;, &quot;be&quot;], ... }&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;map&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;word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;occurrences&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;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;occurrences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&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;nf&quot;&gt;to_h&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; {&quot;the&quot;=&amp;gt;2, &quot;be&quot;=&amp;gt;2, &quot;to&quot;=&amp;gt;2, &quot;of&quot;=&amp;gt;1, &quot;and&quot;=&amp;gt;2, &quot;a&quot;=&amp;gt;2, &quot;in&quot;=&amp;gt;1, &quot;that&quot;=&amp;gt;1, &quot;have&quot;=&amp;gt;1, &quot;I&quot;=&amp;gt;1, &quot;it&quot;=&amp;gt;2, &quot;for&quot;=&amp;gt;3, &quot;not&quot;=&amp;gt;2, &quot;on&quot;=&amp;gt;2, &quot;with&quot;=&amp;gt;2, &quot;he&quot;=&amp;gt;1, &quot;as&quot;=&amp;gt;1, &quot;you&quot;=&amp;gt;1, &quot;do&quot;=&amp;gt;1, &quot;at&quot;=&amp;gt;1, &quot;we&quot;=&amp;gt;1, &quot;her&quot;=&amp;gt;1, &quot;so&quot;=&amp;gt;1, &quot;up&quot;=&amp;gt;1}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;the-new-way&quot;&gt;The new way&lt;/h3&gt;

&lt;p&gt;Since Ruby 2.7, we could get the same result (a Hash containing numbers of occurrences of each element) by simply invoking the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#tally&lt;/code&gt; method on the collection.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;words&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tally&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; {&quot;the&quot;=&amp;gt;2, &quot;be&quot;=&amp;gt;2, &quot;to&quot;=&amp;gt;2, &quot;of&quot;=&amp;gt;1, &quot;and&quot;=&amp;gt;2, &quot;a&quot;=&amp;gt;2, &quot;in&quot;=&amp;gt;1, &quot;that&quot;=&amp;gt;1, &quot;have&quot;=&amp;gt;1, &quot;I&quot;=&amp;gt;1, &quot;it&quot;=&amp;gt;2, &quot;for&quot;=&amp;gt;3, &quot;not&quot;=&amp;gt;2, &quot;on&quot;=&amp;gt;2, &quot;with&quot;=&amp;gt;2, &quot;he&quot;=&amp;gt;1, &quot;as&quot;=&amp;gt;1, &quot;you&quot;=&amp;gt;1, &quot;do&quot;=&amp;gt;1, &quot;at&quot;=&amp;gt;1, &quot;we&quot;=&amp;gt;1, &quot;her&quot;=&amp;gt;1, &quot;so&quot;=&amp;gt;1, &quot;up&quot;=&amp;gt;1}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It’s not only a nice shortcut, it also expresses the intent in a clear way - something many of us love in Ruby code.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Have you had a chance to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#tally&lt;/code&gt; method in your production code yet? Please share in the comments below!&lt;/p&gt;

&lt;p&gt;Do you use continuous integration in your Ruby project? Consider using &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=using-ruby-2-7-new-tally-method&quot;&gt;Knapsack Pro&lt;/a&gt; to increase your team’s productivity by shortening build times on your CI server!&lt;/p&gt;
</description>
        <pubDate>Sat, 10 Apr 2021 08:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/using-ruby-2-7-new-tally-method</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/using-ruby-2-7-new-tally-method</guid>
        
        
        <category>techtips</category>
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>How to run Ruby on Rails tests on Github Actions using RSpec</title>
        <description>&lt;p&gt;Are you thinking about migrating a Ruby on Rails project CI pipeline to Github Actions? You will learn how to configure the Rails app to run RSpec tests using Github Actions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-ruby-on-rails-tests-on-github-actions-using-rspec/github-ror.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Github, Github Actions, RoR, Ruby on Rails, Ruby&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This article covers a few things for Github Actions YAML config:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;how to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby/setup-ruby&lt;/code&gt; action to install Ruby gems with bundler and automatically cache gems. This way you can load Ruby gems for your project from the cache and run CI build fast.&lt;/li&gt;
  &lt;li&gt;how to use Postgres on Github Actions&lt;/li&gt;
  &lt;li&gt;how to use Redis on Github Actions&lt;/li&gt;
  &lt;li&gt;how to use Github Actions build matrix to run parallel jobs and execute RSpec tests spread across multiple jobs to save time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;github-actions-yml-config-for-rails-application&quot;&gt;Github Actions YML config for Rails application&lt;/h2&gt;

&lt;h3 id=&quot;rubysetup-ruby-action&quot;&gt;ruby/setup-ruby action&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/ruby/setup-ruby&quot;&gt;ruby/setup-ruby&lt;/a&gt; is an action that you can use to install a particular Ruby programming language version. It allows you to cache Ruby gems based on your Gemfile.lock out of the box.&lt;/p&gt;

&lt;p&gt;It’s recommended to &lt;a href=&quot;/2021/how-to-load-ruby-gems-from-cache-on-github-actions&quot;&gt;use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby/setup-ruby&lt;/code&gt; instead of outdated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actions/setup-ruby&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set up Ruby&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby/setup-ruby@v1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Not needed with a .ruby-version file&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ruby-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2.7&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# runs &apos;bundle install&apos; and caches installed gems automatically&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;bundler-cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;how-to-configure-postgres-on-github-actions&quot;&gt;How to configure Postgres on Github Actions&lt;/h3&gt;

&lt;p&gt;To use Postgres on Github Actions you need to set up a service for Postgres. I recommend using additional options that will configure Postgres to use RAM instead of disk. This way your database can run faster in a testing environment.&lt;/p&gt;

&lt;p&gt;In the config below, we also pass the settings for doing a health check to ensure the database is up and running before you start running tests.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# If you need DB like PostgreSQL, Redis then define service below.&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# https://github.com/actions/example-services/tree/main/.github/workflows&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:10.8&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5432:5432&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# needed because the postgres container does not provide a healthcheck&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# tmpfs makes DB faster by using RAM&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;&amp;gt;-&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;--mount type=tmpfs,destination=/var/lib/postgresql/data&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;--health-cmd pg_isready&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;--health-interval 10s&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;--health-timeout 5s&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;--health-retries 5&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;how-to-configure-redis-on-github-actions&quot;&gt;How to configure Redis on Github Actions&lt;/h3&gt;

&lt;p&gt;You can use Redis Docker container to start Redis server on Github Actions. See how simple it is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;redis&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;6379:6379&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--entrypoint redis-server&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;how-to-use-github-actions-build-matrix-to-run-tests-with-parallel-jobs&quot;&gt;How to use Github Actions build matrix to run tests with parallel jobs&lt;/h3&gt;

&lt;p&gt;You can use the &lt;a href=&quot;https://docs.github.com/en/actions/writing-workflows/about-workflows#using-a-matrix&quot;&gt;build matrix&lt;/a&gt; in Github Actions to run multiple jobs at the same time.&lt;/p&gt;

&lt;p&gt;You will need to split test files between these parallel jobs. For that, you can use Knapsack Pro with &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;Queue Mode to distribute tests evenly between the jobs&lt;/a&gt;. This way you can ensure the proper amount of tests is executed on each job and the workload is well balanced between the jobs. Simply speaking this way you can make sure the CI build is as fast as possible - it has optimal execution time.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC }}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_total }}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_index }}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_LOG_LEVEL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;info&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can see that for RSpec we also use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; Ruby gem flag &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES&lt;/code&gt;. It allows to automatically &lt;a href=&quot;https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-ruby-on-rails-tests-on-github-actions-using-rspec&quot;&gt;detect slow test files and split them between parallel jobs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can learn more about it in a separate article explaining &lt;a href=&quot;/2020/how-to-run-slow-rspec-files-on-github-actions-with-parallel-jobs-by-doing-an-auto-split-of-the-spec-file-by-test-examples&quot;&gt;how to run slow RSpec files on Github Actions with parallel jobs by doing an auto split of the spec file by test examples&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;full-yml-config-for-github-actions-and-ruby-on-rails-project&quot;&gt;Full YML config for Github Actions and Ruby on Rails project&lt;/h2&gt;

&lt;p&gt;Here is the full configuration of the CI pipeline for Github Actions. You can use it to run tests for your Rails project.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# If you need DB like PostgreSQL, Redis then define service below.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# https://github.com/actions/example-services/tree/main/.github/workflows&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:10.8&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5432:5432&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# needed because the postgres container does not provide a healthcheck&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# tmpfs makes DB faster by using RAM&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;&amp;gt;-&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--mount type=tmpfs,destination=/var/lib/postgresql/data&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-cmd pg_isready&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-interval 10s&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-timeout 5s&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-retries 5&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;redis&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;6379:6379&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--entrypoint redis-server&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# [n] - where the n is a number of parallel jobs you want to run your tests on.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Use a higher number if you have slow tests to split them between more parallel jobs.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Remember to update the value of the `ci_node_index` below to (0..n-1).&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Indexes for parallel jobs (starting from zero).&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;GEMFILE_RUBY_VERSION&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2.7.2&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Rails verifies the time zone in DB is the same as the time zone of the Rails app&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;TZ&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Europe/Warsaw&quot;&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set up Ruby&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Not needed with a .ruby-version file&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ruby-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2.7&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# runs &apos;bundle install&apos; and caches installed gems automatically&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;bundler-cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create DB&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bin/rails db:prepare&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_total }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_index }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_LOG_LEVEL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;info&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# if you use Knapsack Pro Queue Mode you must set below env variable&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# to be able to retry CI build and run previously recorded tests&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# https://docs.knapsackpro.com/ruby/queue-mode/#dynamic-split-vs-fixed-split&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# RSpec split test files by test examples feature - it&apos;s optional&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;You’ve just learned how to set up your Rails application on Github Actions. I hope this will help you if you migrate your project from a different CI server to Github Actions.&lt;/p&gt;

&lt;p&gt;You can learn more about &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-ruby-on-rails-tests-on-github-actions-using-rspec&quot;&gt;Knapsack Pro&lt;/a&gt; and how it can help you run tests fast using parallel jobs on CI. It works with RSpec, Cucumber, Minitest, and other Ruby test runners. &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-ruby-on-rails-tests-on-github-actions-using-rspec&quot;&gt;Knapsack Pro&lt;/a&gt; can also work with JavaScript test runners and has a native API integration.&lt;/p&gt;
</description>
        <pubDate>Thu, 08 Apr 2021 06:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/how-to-run-ruby-on-rails-tests-on-github-actions-using-rspec</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/how-to-run-ruby-on-rails-tests-on-github-actions-using-rspec</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Run Minitest on Github Actions with parallel jobs using build matrix</title>
        <description>&lt;p&gt;How to run Ruby on Rails tests in Minitest on Github Actions? What to do if tests are slow? How to manage complex workflows? You can use Github Actions build matrices to divide Minitest files between jobs and run the test suite much faster.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/run-minitest-on-github-actions-with-parallel-jobs-using-build-matrix/github-minitest.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Minitest, Ruby, Github, Github Actions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If your Minitest tests are taking dozens of minutes and you would like to save some time for your Ruby engineering team then you could use tests parallelization on your CI server.&lt;/p&gt;

&lt;p&gt;To run tests as fast as possible you need to split them into equal buckets (into parallel jobs). But how to do it? Some of the test files can be super fast to execute, other Minitest files can take minutes if they run system tests (E2E tests).&lt;/p&gt;

&lt;p&gt;There is also an aspect of preparing the test environment for each parallel job. By preparing I mean you need to clone a repository, install ruby gems or load them from a cache, maybe you need to load some docker container, etc. This can take various amounts of time on each parallel job. Random network errors happen like network delay to &lt;a href=&quot;/2021/how-to-load-ruby-gems-from-cache-on-github-actions&quot;&gt;load cached gems&lt;/a&gt;, or maybe Github Actions from time to time will start one of your jobs late compared to others. It’s an inevitable issue in the network environment and can cause your tests to run for a different amount of time on each parallel job. This is visible on the graph below and it causes the CI build to be slower.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/not-optimal-tests-split.png&quot; style=&quot;width:100%;&quot; alt=&quot;not optimal tests split on CI server, CI parallelism&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In a perfect scenario you would like to cover all these problems and no matter what still be able to split Minitest work in parallel jobs in a way that ensures the tests on each parallel job completes at a similar time. This guarantees no bottlenecks. The perfect tests split is on the below graph.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/optimal-tests-split.png&quot; style=&quot;width:100%;&quot; alt=&quot;optimal tests split on CI server, CI parallelism&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;split-tests-in-a-dynamic-way-with-queue-mode&quot;&gt;Split tests in a dynamic way with Queue Mode&lt;/h2&gt;

&lt;p&gt;You can use &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-minitest-on-github-actions-with-parallel-jobs-using-build-matrix&quot;&gt;Knapsack Pro&lt;/a&gt; Queue Mode to split tests in a dynamic way between parallel jobs. This way each job consumes tests from a queue until the queue is empty. Simply speaking this allows you to utilize your CI server resources efficiently and run tests in optimal time.&lt;/p&gt;

&lt;p&gt;I described &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;how Queue Mode splits Ruby and JavaScript tests in parallel with a dynamic test suite split&lt;/a&gt;. You can learn from that article about it.&lt;/p&gt;

&lt;h2 id=&quot;github-actions-build-matrix-to-run-parallel-tests&quot;&gt;Github Actions build matrix to run parallel tests&lt;/h2&gt;

&lt;p&gt;Github Actions has a &lt;a href=&quot;https://docs.github.com/en/actions/writing-workflows/about-workflows#using-a-matrix&quot;&gt;build matrix feature&lt;/a&gt; that allows running many jobs at the same time. You can use it to run your Minitest tests between parallel jobs.&lt;/p&gt;

&lt;p&gt;Below is a full Github Actions YML config for a Rails project and Minitest.
The tests are split with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; Ruby gem and Queue Mode.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# If you need DB like PostgreSQL, Redis then define service below.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# https://github.com/actions/example-services/tree/main/.github/workflows&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:10.8&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5432:5432&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# needed because the postgres container does not provide a healthcheck&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# tmpfs makes DB faster by using RAM&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;&amp;gt;-&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--mount type=tmpfs,destination=/var/lib/postgresql/data&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-cmd pg_isready&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-interval 10s&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-timeout 5s&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-retries 5&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;redis&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;6379:6379&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--entrypoint redis-server&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Set N number of parallel jobs you want to run tests on.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Use higher number if you have slow tests to split them on more parallel jobs.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Remember to update ci_node_index below to 0..N-1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# set N-1 indexes for parallel jobs&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# When you run 2 parallel jobs then first job will have index 0, the second job will have index 1 etc&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Rails verifies Time Zone in DB is the same as time zone of the Rails app&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;TZ&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Europe/Warsaw&quot;&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set up Ruby&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Not needed with a .ruby-version file&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ruby-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2.7&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# runs &apos;bundle install&apos; and caches installed gems automatically&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;bundler-cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create DB&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bin/rails db:prepare&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_MINITEST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_MINITEST }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_total }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_index }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_LOG_LEVEL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;info&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:queue:minitest&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;As you can see the slow Minitest test suite doesn’t need to be an issue for you. QA, Testers, or Automation Engineers could benefit from improving the CI build speed and allowing their software developers team to deliver products faster. You can learn more at &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-minitest-on-github-actions-with-parallel-jobs-using-build-matrix&quot;&gt;Knapsack Pro&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Sun, 04 Apr 2021 13:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/run-minitest-on-github-actions-with-parallel-jobs-using-build-matrix</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/run-minitest-on-github-actions-with-parallel-jobs-using-build-matrix</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Fix: &apos;warning: Using the last argument as keyword parameters is deprecated&apos;</title>
        <description>&lt;p&gt;Have you encountered the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;warning: Using the last argument as keyword parameters is deprecated&lt;/code&gt; message in your Ruby project? You probably started seeing this after upgrading your project to Ruby 2.7. This version introduced the deprecation as a means to prepare users for a breaking change in Ruby 3. Let’s learn how to fix this!&lt;/p&gt;

&lt;h2 id=&quot;the-explanation&quot;&gt;The explanation&lt;/h2&gt;

&lt;p&gt;In Ruby 2, you could pass a Hash as the last argument to the method that expects keyword arguments, and the key-value pairs from the hash would be treated as keyword arguments.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;options&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;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Ruby 2.6&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;Hello, John!&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ruby 2.7 still allows this, but now warns about this usage.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# Ruby 2.7&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;warning: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argument&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;keyword&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deprecated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maybe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;be&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;added&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;Hello, John!&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And Ruby 3 raises an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ArgumentError&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# Ruby 3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ArgumentError&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wrong&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arguments&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;given&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;n&quot;&gt;expected&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;n&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;keyword: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In the above example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;greeting&lt;/code&gt; is a “positional argument” of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#greet&lt;/code&gt; method, while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name:&lt;/code&gt; is a keyword argument. As we can see, in Ruby 2 we could invoke the method as if we were just passing two regular arguments to it, using a hash object as the last one. Ruby’s interpreter would then automagically “deconstruct” the hash as needed and match its key-value pairs to the keyword argument expected by the method.&lt;/p&gt;

&lt;p&gt;The Ruby team believed that this behavior is prone to be quite confusing and &lt;a href=&quot;https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/&quot; target=&quot;_blank&quot;&gt;decided to alter it in Ruby 3&lt;/a&gt;. That’s why you see these deprecation warnings in Ruby 2.7, and the usage is no longer valid since Ruby 3.&lt;/p&gt;

&lt;h2 id=&quot;so-how-to-fix-it&quot;&gt;So how to fix it?&lt;/h2&gt;

&lt;p&gt;There are two ways to resolve the warnings in Ruby 2.7 (and prepare your code for Ruby 3).&lt;/p&gt;

&lt;h3 id=&quot;the-simple-solution&quot;&gt;The simple solution&lt;/h3&gt;

&lt;p&gt;The most non-invasive way to fix the warning is to do what the warning itself suggests - just use the double splat (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;**&lt;/code&gt;) operator before the hash. This way, we explicitly say that the Hash’s contents are meant to be “exploded” to match the required keyword arguments.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;options&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;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Ruby 2.7&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello&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;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;Hello, John!&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Ruby 3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello&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;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;Hello, John!&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Works like a charm!&lt;/p&gt;

&lt;h3 id=&quot;not-using-the-hash&quot;&gt;Not using the Hash&lt;/h3&gt;

&lt;p&gt;Another option would obviously be to abstain from using the hash object in the first place. This may require a bigger refactor in your code or simply be undesired. Still, it might be something to consider, especially in simple cases.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Ruby 2.7&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;Hello, John!&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Ruby 3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;Hello, John!&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;avoiding-the-shortcuts&quot;&gt;Avoiding the shortcuts&lt;/h2&gt;

&lt;p&gt;As with every warning, we might get an idea to ignore or even suppress it. I advise against this. The warning is there for a reason, and it’s best to fix it and avoid troubles with future version upgrades. This is even more true given how straightforward this warning is to fix in most cases.&lt;/p&gt;

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

&lt;p&gt;Have you encountered this warning in your code? Please let us know in the comments!&lt;/p&gt;

&lt;p&gt;And if your Ruby project suffers from slow CI builds, consider using &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=fix-warning-using-the-last-argument-as-keyword-parameters-is-deprecated&quot;&gt;Knapsack Pro&lt;/a&gt; to improve the productivity of your team!&lt;/p&gt;
</description>
        <pubDate>Sat, 03 Apr 2021 07:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/fix-warning-using-the-last-argument-as-keyword-parameters-is-deprecated</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/fix-warning-using-the-last-argument-as-keyword-parameters-is-deprecated</guid>
        
        
        <category>techtips</category>
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>How and when to use Ruby case statements</title>
        <description>&lt;p&gt;Using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; expression in Ruby is a great way to write conditionals in a clear, and succinct way.
Despite that, it’s not hard to encounter convoluted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; constructs in places where refactoring to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; statement would result in huge improvements. Let’s learn more about the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; expression and when it’s best to use it.&lt;/p&gt;

&lt;h2 id=&quot;expressing-logic-simply&quot;&gt;Expressing logic simply&lt;/h2&gt;

&lt;p&gt;Consider this piece of code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;low value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;medium value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos; high value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;invalid value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It seems like quite a complicated way to express a pretty straightforward logic, doesn’t it? Still, I bet that you have seen a construct like that many times. I know I have.&lt;/p&gt;

&lt;p&gt;This logic could be greatly simplified with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; expression:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&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;o&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;s1&quot;&gt;&apos;low value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&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;o&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;s1&quot;&gt;&apos;medium value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;high value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;invalid value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We could also use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;then&lt;/code&gt; keyword to make this even more succinct:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&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;o&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;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;low value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&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;o&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;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;medium value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;high value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;              &lt;span class=&quot;s1&quot;&gt;&apos;invalid value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This code effectively works like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&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;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;low value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;elsif&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;o&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;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;medium value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&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;number&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;high value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;invalid value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; statements are most powerful when our logic is concerned with a value of a single object (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;number&lt;/code&gt; in the above examples), which is provided next to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; keyword.
What comes after each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;when&lt;/code&gt; acts as a pattern. It doesn’t have to be a Range necessarily - this would work with any object.
Similar to &lt;a href=&quot;/2021/understanding-and-using-rubys-powerful-grep-method&quot;&gt;how the #grep method behaves&lt;/a&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; expression results in invoking the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#===&lt;/code&gt; method on the given patterns.
In fact, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#===&lt;/code&gt; method in Ruby is often called the “case equality”, as it’s mostly known for being used in case statements.&lt;/p&gt;

&lt;p&gt;You can consult the Ruby documentation and look into how the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#===&lt;/code&gt; method is implement in various classes. On the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt; class, it works the same as the regular equality method, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#==&lt;/code&gt;. It’s then up to descending classes to implement their own interpretation of the “case equality”.&lt;/p&gt;

&lt;h2 id=&quot;patterns-in-the-case-statements&quot;&gt;Patterns in the case statements&lt;/h2&gt;

&lt;p&gt;Let’s look at some examples of objects being used as patterns in the case statements.&lt;/p&gt;

&lt;p&gt;The case statement with integers as patterns:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With strings:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Y&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;N&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And with regexes:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^a/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;starts with an A&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^z/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;starts with a Z&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can of course mix the types of patterns within one statement:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;       &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;zero&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&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;o&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;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;low value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&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;o&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;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;medium value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;high value&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;One last tip. You can provide multiple patterns for each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;when&lt;/code&gt;. This would effectively mimic the logical &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;||&lt;/code&gt; operator, with each pattern being compared against.
To illustrate this, let’s expand on a previous example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;y&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;yes&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;n&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;no&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Under the hood, this works just like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;y&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;yes&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;n&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;no&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;I hope that you find this quick recap of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;case&lt;/code&gt; expression useful. How often are you using case statements in your projects? Please share in the comments.&lt;/p&gt;

&lt;p&gt;If your Ruby project suffers from slow CI build times, consider using &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-and-when-to-use-ruby-case-statements&quot;&gt;Knapsack Pro&lt;/a&gt; to improve the productivity of your team.&lt;/p&gt;
</description>
        <pubDate>Thu, 01 Apr 2021 11:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/how-and-when-to-use-ruby-case-statements</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/how-and-when-to-use-ruby-case-statements</guid>
        
        
        <category>techtips</category>
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>How to load Ruby gems from cache on Github Actions</title>
        <description>&lt;p&gt;How to start CI build faster by loading Ruby gems from cache on Github Actions? You can start running your tests for a Ruby on Rails project quicker if you manage to set up all dependencies in a short amount of time. Caching can be helpful with that. Ruby gems needed for your project can be cached by Github Actions and thanks to that they can be loaded much faster when you run a new CI build.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-load-ruby-gems-from-cache-on-github-actions/cache.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Buildkite, CI, RSpec, testing, Ruby&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You will learn how to configure Github Actions using:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/actions/cache&quot;&gt;actions/cache&lt;/a&gt; - it’s a popular solution to cache Ruby gems.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/ruby/setup-ruby&quot;&gt;ruby/setup-ruby&lt;/a&gt; - it’s a solution to install a specific Ruby version and cache Ruby gems with bundler. Two features in one action.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;actionscache---just-cache-dependencies&quot;&gt;actions/cache - just cache dependencies&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/actions/cache&quot;&gt;Actions/cache&lt;/a&gt; is a popular solution that can be used to save data into the cache and restore it during the next CI build. It’s often used for Ruby on Rails projects that also use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actions/setup-ruby&lt;/code&gt; for managing the Ruby version on Github Actions.&lt;/p&gt;

&lt;p&gt;Let’s look at the Github Actions caching config example using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actions/cache&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/main.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ runner.os }}-gems-${{ hashFiles(&apos;**/Gemfile.lock&apos;) }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;restore-keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;${{ runner.os }}-gems-&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Bundle install&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle config path vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle install --jobs 4 --retry 3&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;You need to specify a directory path that will be cached. It’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vendor/bundle&lt;/code&gt; in our case.&lt;/li&gt;
  &lt;li&gt;You also generate a unique cache &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key&lt;/code&gt; based on the OS version and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; file. When you change the operating system version or you install a new gem and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; changes then as a result the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key&lt;/code&gt; value will be generated.&lt;/li&gt;
  &lt;li&gt;You need to configure the bundler to install all your Ruby gems to the directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vendor/bundle&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;You can use bundler options:
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--jobs 4&lt;/code&gt; - install gems using parallel workers. This allows faster gems installation.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--retry 3&lt;/code&gt; - makes 3 attempts to connect to Rubygems if there is a network issue (for instance temporary downtime of Rubygems.org)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you would like to see the full YAML config for the Github Actions and Rails project you can take a look at some of our articles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2019/how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;How to run RSpec on GitHub Actions for Ruby on Rails app using parallel jobs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2019/github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;GitHub Actions CI config for Ruby on Rails project with MySQL, Redis, Elasticsearch - how to run parallel tests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2020/how-to-run-slow-rspec-files-on-github-actions-with-parallel-jobs-by-doing-an-auto-split-of-the-spec-file-by-test-examples&quot;&gt;How to run slow RSpec files on Github Actions with parallel jobs by doing an auto split of the spec file by test examples&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/cucumber-bdd-testing-using-github-actions-parallel-jobs-to-run-tests-quicker&quot;&gt;Cucumber BDD testing using Github Actions parallel jobs to run tests quicker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;rubysetup-ruby---install-ruby-and-cache-gems&quot;&gt;ruby/setup-ruby - install Ruby and cache gems&lt;/h2&gt;

&lt;p&gt;In the previous section, we mentioned the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actions/setup-ruby&lt;/code&gt; is often used with Ruby on Rails projects. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actions/setup-ruby&lt;/code&gt; has been deprecated so it’s recommended to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby/setup-ruby&lt;/code&gt; action nowadays. It already has caching feature that you could use. Let’s see how.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/main.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby/setup-ruby@v1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Not needed with a .ruby-version file&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ruby-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2.7&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# runs &apos;bundle install&apos; and caches installed gems automatically&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;bundler-cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# run RSpec tests&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rspec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby/setup-ruby&lt;/code&gt; for managing the Ruby version and gems caching is much simpler. You just add an option &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundler-cache: true&lt;/code&gt; and that’s it.&lt;/p&gt;

&lt;p&gt;You can read in &lt;a href=&quot;https://github.com/ruby/setup-ruby#caching-bundle-install-automatically&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby/setup-ruby&lt;/code&gt; documentation&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;“It is also possible to cache gems manually, but this is not recommended because it is verbose and very difficult to do correctly. There are many concerns which means using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actions/cache&lt;/code&gt; is never enough for caching gems (e.g., incomplete cache key, cleaning old gems when restoring from another key, correctly hashing the lockfile if not checked in, OS versions, ABI compatibility for ruby-head, etc). So, please use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundler-cache: true&lt;/code&gt; instead…”&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;You saw 2 ways of caching Ruby gems on Github Actions. There are also other ways to make your CI build faster like running tests in parallel. You can learn more about &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;test parallelisation here&lt;/a&gt; or simply check the &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-load-ruby-gems-from-cache-on-github-actions&quot;&gt;Knapsack Pro&lt;/a&gt; homepage.&lt;/p&gt;
</description>
        <pubDate>Thu, 25 Mar 2021 07:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/how-to-load-ruby-gems-from-cache-on-github-actions</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/how-to-load-ruby-gems-from-cache-on-github-actions</guid>
        
        
        <category>techtips</category>
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Faster Cypress + RSpec test suite for Rails apps on GitHub Actions using Knapsack Pro</title>
        <description>&lt;p&gt;Cypress is an amazing tool for end to end testing Rails applications, but large test suites can quickly take upwards of 20 minutes to run. That’s where Knapsack Pro comes in. Knapsack Pro &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;Queue Mode to intelligently split your test suite into jobs that can be run in parallel&lt;/a&gt;, reducing run time to only a few minutes. In this article we’ll show how to quickly implement Knapsack Pro Queue Mode to speed up both Cypress &amp;amp; RSpec test suites in a Ruby on Rails app on Github Actions&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/faster-cypress-rspec-test-suite-for-rails-apps-on-github-actions-using-knapsack-pro/cypress_rspec.jpeg&quot; style=&quot;width:450px;margin-left: 15px;float:right;&quot; alt=&quot;Cypress + RSpec&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;set-up-knapsack-pro-api-keys&quot;&gt;Set up Knapsack Pro API Keys&lt;/h2&gt;

&lt;p&gt;First step is to go to your Knapsack &lt;a href=&quot;https://knapsackpro.com/sessions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=faster-cypress-rspec-test-suite-for-rails-apps-on-github-actions-using-knapsack-pro&quot;&gt;dashboard&lt;/a&gt; and grab your API keys for both RSpec and Cypress. Once you have those, go to your Github Repo’s settings, for example:&lt;/p&gt;

&lt;p&gt;&lt;img width=&quot;1404&quot; alt=&quot;image&quot; src=&quot;https://user-images.githubusercontent.com/64985/111044967-80297880-8400-11eb-92b6-8a1e8aa2701e.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;set-up-your-github-actions-config-file&quot;&gt;Set up your GitHub Actions config file&lt;/h2&gt;

&lt;p&gt;Once you’ve added your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS&lt;/code&gt; secrets, the next step is setting up your GitHub Actions configuration file to use the Knapsack Runner in place of the normal commands to run RSpec and Cypress.&lt;/p&gt;

&lt;h3 id=&quot;existing-github-actions-config&quot;&gt;Existing GitHub Actions config&lt;/h3&gt;

&lt;p&gt;For those that already have a GH actions config file setup (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows/ci.yml&lt;/code&gt;), here’s all that you should need to change to get Knapsack Pro Queue Mode working for both Cypress and RSpec.&lt;/p&gt;

&lt;p&gt;Change your RSpec run command to use Knapsack:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;gi&quot;&gt;+      strategy:
+        fail-fast: false
+        matrix:
+          # Set N number of parallel jobs you want to run tests on.
+          # Use higher number if you have slow tests to split them on more parallel jobs.
+          # Remember to update ci_node_index below to 0..N-1
+          ci_node_total: [2]
+          # set N-1 indexes for parallel jobs
+          # When you run 2 parallel jobs then first job will have index 0, the second job will have index 1 etc
+          ci_node_index: [0, 1]
&lt;/span&gt;      - name: Run RSpec Tests
&lt;span class=&quot;gi&quot;&gt;+      env:
+        KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
+        KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
+        KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: ${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC }}
+        KNAPSACK_PRO_FIXED_QUEUE_SPLIT: true
+    run: bin/rake knapsack_pro:queue:rspec  # Run RSpec using Knapsack Pro Queue Mode
&lt;/span&gt;&lt;span class=&quot;gd&quot;&gt;-    run: bin/rspec spec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Change your cypress run command to use Knapsack as well:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;gi&quot;&gt;+      strategy:
+        fail-fast: false
+        matrix:
+          ci_node_total: [5]
+          # set N-1 indexes for parallel jobs
+          # When you run 2 parallel jobs then first job will have index 0, the second job will have index 1 etc
+          ci_node_index: [0, 1, 2, 3, 4]
&lt;/span&gt;      - name: Run cypress tests
&lt;span class=&quot;gi&quot;&gt;+        env:
+          KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS: ${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS }}
+          KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
+          KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
+          KNAPSACK_PRO_FIXED_QUEUE_SPLIT: true
+          KNAPSACK_PRO_TEST_FILE_PATTERN: &apos;{cypress/**/*,app/javascript/**/*.component}.spec.{js,ts,tsx}&apos;
+        run: yarn @knapsack-pro/cypress
&lt;/span&gt;&lt;span class=&quot;gd&quot;&gt;-        run: yarn cypress run&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;new-github-actions-config-file&quot;&gt;New Github Actions config file&lt;/h3&gt;

&lt;p&gt;For those starting from scratch, here’s a full example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows/ci.yaml&lt;/code&gt; for a Rails app with Cypress + RSpec with Knapsack tokens for RSpec and Cypress already added.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ci&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# OPTIONAL: Cancel any previous CI runs to save your GH Actions minutes&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Cancel&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Previous&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Runs&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;timeout-minutes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;styfle/cancel-workflow-action@0.8.0&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;workflow_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3553203&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;yarn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/setup-node@v2-beta&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;12&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;**/node_modules&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ runner.os }}-yarn-${{ hashFiles(&apos;yarn.lock&apos;) }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;restore-keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;${{ runner.os }}-yarn-&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Yarn install&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn install --frozen-lockfile&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;CYPRESS_INSTALL_BINARY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Prevent installing Cypress binary until later when it&apos;s needed&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;bundler-cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rspec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;timeout-minutes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Adjust as needed, just here to prevent accidentally using up all your minutes from a silly infinite loop of some kind&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Set N number of parallel jobs you want to run tests on.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Use higher number if you have slow tests to split them on more parallel jobs.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Remember to update ci_node_index below to 0..N-1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# set N-1 indexes for parallel jobs&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# When you run 2 parallel jobs then first job will have index 0, the second job will have index 1 etc&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;bundler-cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build DB&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bin/rails db:schema:load&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run RSpec Tests&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGPORT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ job.services.postgres.ports[5432] }}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# get randomly assigned published port&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_total }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_index }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bin/rake knapsack_pro:queue:rspec&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Run RSpec using Knapsack Pro Queue Mode&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;cypress&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;timeout-minutes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Adjust as needed, just here to prevent accidentally using up all your minutes from a silly infinite loop of some kind&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;RACK_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ github.token }}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;yarn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# set N-1 indexes for parallel jobs&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# When you run 5 parallel jobs then first job will have index 0, the second job will have index 1 etc&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/setup-node@v2-beta&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;12&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;**/node_modules&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ runner.os }}-yarn-${{ hashFiles(&apos;yarn.lock&apos;) }}&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;bundler-cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build DB&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bin/rails db:schema:load&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run Rails Server in background&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bin/rails server -p 3000 &amp;amp;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npx cypress -v &amp;gt; .cypress-version&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;~/.cache/Cypress&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress-cache-v3-${{ runner.os }}-${{ hashFiles(&apos;.cypress-version&apos;) }}&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn cypress install&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/setup-node@v2-beta&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;12&quot;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn wait-on &apos;http-get://localhost:3000&apos; -t &lt;/span&gt;&lt;span class=&quot;m&quot;&gt;30000&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_total }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_index }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_FILE_PATTERN&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{cypress/**/*,app/javascript/**/*.component}.spec.{js,ts,tsx}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn @knapsack-pro/cypress&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Run Cypress using Knapsack Pro Queue Mode&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Save screenshots and videos of failed tests and make them available as Github build artifacts&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;failure()&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress-screenshots&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress/screenshots&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;failure()&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress-videos&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress/videos&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;failure()&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress-logs&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress/logs&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;add-knapsack-pro-gem-and-npm-package&quot;&gt;Add Knapsack Pro gem and npm package&lt;/h2&gt;

&lt;p&gt;Add the Knapsack Pro gem to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:development&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#...&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;knapsack_pro&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Add the Knapsack Pro npm package with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn add --dev @knapsack-pro/cypress&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;run-your-tests--view-your-results&quot;&gt;Run your tests &amp;amp; view your results&lt;/h2&gt;

&lt;p&gt;Once you’ve completed the above steps, trigger a test run on your repo. You should see multiple jobs for both RSpec and Cypress like so:&lt;/p&gt;

&lt;p&gt;&lt;img width=&quot;345&quot; alt=&quot;Screen Shot 2021-03-23 at 10 13 30 AM&quot; src=&quot;https://user-images.githubusercontent.com/64985/112190195-017fc880-8bc2-11eb-85ea-00fa3469be43.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now check your &lt;a href=&quot;https://knapsackpro.com/sessions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=faster-cypress-rspec-test-suite-for-rails-apps-on-github-actions-using-knapsack-pro&quot;&gt;Knapsack Dashboard&lt;/a&gt; for the results 🚀&lt;/p&gt;

&lt;p&gt;The complete example Rails app can be found in &lt;a href=&quot;https://github.com/goodproblems/knapsack-example-rails-app&quot;&gt;this repository&lt;/a&gt;. Happy testing!&lt;/p&gt;

&lt;h2 id=&quot;related-resources&quot;&gt;Related resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/&quot;&gt;Installation guide for Knapsack Pro&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/setting-up-knapsack-pro-in-rspec-project&quot;&gt;Regular Mode vs Queue Mode&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2020/how-to-merge-cypress-test-reports-generated-by-mochawesome-on-github-actions&quot;&gt;How to merge Cypress test reports generated by Mochawesome on Github Actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 23 Mar 2021 18:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/faster-cypress-rspec-test-suite-for-rails-apps-on-github-actions-using-knapsack-pro</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/faster-cypress-rspec-test-suite-for-rails-apps-on-github-actions-using-knapsack-pro</guid>
        
        
        <category>continuous_integration</category>
        
        <category>cypress</category>
        
        <category>javascript</category>
        
        <category>parallelisation</category>
        
        <category>CI</category>
        
        <category>github</category>
        
        <category>actions</category>
        
      </item>
    
      <item>
        <title>Auto-scaling Buildkite CI build agents for RSpec (run parallel jobs in minutes instead of hours)</title>
        <description>&lt;p&gt;If your RSpec test suite runs for hours, you could shorten that to just minutes with parallel jobs using Buildkite agents. You will learn how to run parallel tests in optimal CI build time for your Ruby on Rails project. I will also show you a few useful things for Buildkite CI like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours/buildkite-rspec.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Buildkite, CI, RSpec, testing, Ruby&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A real RSpec test suite taking 13 hours and 32 minutes executed in only 5 minutes 20 seconds by using 151 parallel Buildkite agents with &lt;a href=&quot;/knapsack_pro-ruby/guide/&quot;&gt;knapsack_pro Ruby gem&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;How to distribute test files between parallel jobs using Queue Mode in &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours&quot;&gt;Knapsack Pro&lt;/a&gt; to utilize CI machines optimally.&lt;/li&gt;
  &lt;li&gt;A simple example of CI Buildkite parallelism config.&lt;/li&gt;
  &lt;li&gt;An advanced example of Buildkite config with Elastic CI Stack for AWS.&lt;/li&gt;
  &lt;li&gt;Why you might want to use AWS Spot Instances&lt;/li&gt;
  &lt;li&gt;How to automatically split slow RSpec test files by test examples (test cases) between parallel Buildkite agents&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;a-real-rspec-test-suite-taking-13-hours-and-executed-in-only-5-minutes&quot;&gt;A real RSpec test suite taking 13 hours and executed in only 5 minutes&lt;/h2&gt;

&lt;p&gt;I’d like to show you the results from a real project for running RSpec parallel tests. The project we are looking at here is huge and its RSpec tests run time is 13 hours and 32 minutes. It’s super slow. You can imagine creating a git commit and waiting 13 hours to find out the next day that your code breaks something else in the project. You can’t work like that!&lt;/p&gt;

&lt;p&gt;The solution for this is to run tests in parallel on many CI machines using Buildkite agents. Each CI machine has a Buildkite agent installed that will run a chunk of the RSpec test suite. Below you can see an example of running ~13 hours test suite across 151 parallel Buildkite agents.
This allows running the whole RSpec test suite in just 5 minutes 20 seconds!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours/151-parallel-nodes.png&quot; alt=&quot;parallel machines, parallel jobs, Buildkite, Knapsack Pro, tests, RSpec&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The above graph comes from the Knapsack Pro &lt;a href=&quot;https://knapsackpro.com/sessions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours&quot;&gt;user dashboard&lt;/a&gt;. 151 parallel jobs are a lot of machines. It would take the whole screen to show you 151 bars. You can only see the last few bars on the graph. The bars are showing how the RSpec test files were split between parallel machines.&lt;/p&gt;

&lt;p&gt;You can see that each parallel machine finishes work at a similar time. The right edges of all of the bars are pretty close to each other. This is the important part. You want to ensure the RSpec work is distributed evenly between parallel jobs. This way you can avoid a bottleneck - a slow job running too many test files. I’ll show you how to do it.&lt;/p&gt;

&lt;h2 id=&quot;how-to-distribute-test-files-between-parallel-jobs-using-queue-mode-in-knapsack-pro-to-utilize-ci-machines-optimally&quot;&gt;How to distribute test files between parallel jobs using Queue Mode in Knapsack Pro to utilize CI machines optimally&lt;/h2&gt;

&lt;p&gt;To run CI build as fast as possible we need to utilize our available resources as much as we can. This means the work of running RSpec tests should be split between parallel machines evenly.&lt;/p&gt;

&lt;p&gt;The bigger the test suite, the longer it takes to run it and more edge cases can happen when you split running tests among many machines in the network. Some of the possible edge cases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;some of the test files take longer than others to run (for instance E2E test files)&lt;/li&gt;
  &lt;li&gt;some of the test cases fail and run quicker, some don’t and run longer. This affects the overall time spent by the CI machine on running your tests.&lt;/li&gt;
  &lt;li&gt;some of the test cases take longer because they must connect with network/external API etc - this adds uncertainty to their execution time&lt;/li&gt;
  &lt;li&gt;some of the parallel machines spend more time on boot time
    &lt;ul&gt;
      &lt;li&gt;installing Ruby gems takes longer&lt;/li&gt;
      &lt;li&gt;loading Ruby gems from cache is slow&lt;/li&gt;
      &lt;li&gt;or simply the CI provider has not started your job yet&lt;/li&gt;
      &lt;li&gt;or maybe you have not enough available machines in the pool of available agents&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiple things can disrupt the spread of work between parallel nodes.&lt;/p&gt;

&lt;p&gt;Our ultimate goal is to ensure all machines finish work at a similar time because this means every machine received a workload that was suitable to its available capabilities. This means that, if a machine started work very late it will run only a small part of the tests. If another machine started work very early it will run more tests. This will even out the ending time between parallel machines. All this is possible thanks to Queue Mode in knapsack_pro Ruby gem, it will take care of running tests in parallel for you. &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;Queue Mode splits test files dynamically between parallel jobs to ensure the jobs completes at the same time&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can see an example of running a small RSpec test suite across 2 parallel Buildkite agents for the Ruby on Rails project.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/2Pp9icUJVIg&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;a-simple-example-of-ci-buildkite-parallelism-config&quot;&gt;A simple example of CI Buildkite parallelism config&lt;/h2&gt;

&lt;p&gt;Here is a very simple example of Buildkite config to run 2 parallel jobs as you can see on the screenshot.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours/buildkite-parallel-rspec.png&quot; alt=&quot;Buildkite parallel RSpec agents&quot; /&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .buildkite/pipeline.yml&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# You should hide you secrets like API token&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Please follow https://buildkite.com/docs/pipelines/security/secrets/managing&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;204abb31f698a6686120a40efeff31e5&quot;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# allow to run the same set of test files on job retry&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rake&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;knapsack_pro:queue:rspec&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parallelism&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Please note that you should hide your credentials like the Knapsack Pro API token and not commit it into your repository. You can refer to the &lt;a href=&quot;https://buildkite.com/docs/pipelines/security/secrets/managing&quot;&gt;Buildkite secrets documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;an-advanced-buildkite-config-with-elastic-ci-stack-for-aws&quot;&gt;An advanced Buildkite config with Elastic CI Stack for AWS&lt;/h2&gt;

&lt;p&gt;If you want to run your RSpec project on dozen or even hundreds of parallel machines, you need powerful resources. In such a case, you can follow the &lt;a href=&quot;https://buildkite.com/docs/agent/v3/elastic-ci-aws&quot;&gt;Buildkite tutorial about AWS setup&lt;/a&gt;. The Elastic CI Stack for AWS gives you a private, autoscaling Buildkite Agent cluster in your own AWS account.&lt;/p&gt;

&lt;h3 id=&quot;aws-spot-instances-can-save-you-money&quot;&gt;AWS Spot Instances can save you money&lt;/h3&gt;

&lt;p&gt;AWS offers Spot Instances. These machines are cheap but they can be withdrawn by AWS at any time. This means that you can run cheap machines for your CI but from time to time the AWS may kill one of your parallel machines. Such a scenario can be handled by the &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours&quot;&gt;Knapsack Pro&lt;/a&gt;. It remembers the set of test files allocated to the AWS machine that was running the tests. When the machine is withdrawn and later on retried by the Buildkite retry feature then the proper test files will be executed as you would expect.&lt;/p&gt;

&lt;h3 id=&quot;buildkite-retry-feature&quot;&gt;Buildkite retry feature&lt;/h3&gt;

&lt;p&gt;Buildkite config allows for &lt;a href=&quot;https://buildkite.com/docs/pipelines/command-step#automatic-retry-attributes&quot;&gt;automatic retry of your job&lt;/a&gt;. This can be helpful when you use AWS Spot Instances.
When AWS shuts down your machine during test runtime due to withdrawal then Buildkite can automatically run a new job on a new machine.&lt;/p&gt;

&lt;p&gt;Another use case for the automatic retry is when you have &lt;a href=&quot;/2021/fix-intermittently-failing-ci-builds-flaky-tests-rspec&quot;&gt;flaky Ruby tests&lt;/a&gt; that sometimes pass green or fail red. You can use Buildkite to retry the failing job in such a case.&lt;/p&gt;

&lt;p&gt;My recommendation is to use the &lt;a href=&quot;https://knapsackpro.com/faq/question/how-to-retry-failed-tests-flaky-tests&quot;&gt;rspec-retry gem&lt;/a&gt; as a first choice. RSpec-retry gem will retry only failing test cases instead of all test files assigned to the parallel machine.
The second option you can try is to rely on the &lt;a href=&quot;https://buildkite.com/docs/pipelines/command-step#automatic-retry-attributes&quot;&gt;Buildkite retry feature&lt;/a&gt;. It will retry the CI node and all tests assigned to it by Knapsack Pro API.&lt;/p&gt;

&lt;h2 id=&quot;how-to-automatically-split-large-slow-rspec-test-files-by-test-examples-test-cases-between-parallel-buildkite-agents&quot;&gt;How to automatically split large slow RSpec test files by test examples (test cases) between parallel Buildkite agents&lt;/h2&gt;

&lt;p&gt;Slow RSpec test files are often related to E2E tests, the browser tests like capybara feature specs. They can run for a few or sometimes even dozens of minutes. They could become a bottleneck if the parallel job has to run a single test file for 10 minutes while other parallel jobs complete a few smaller test files in 5 minutes.&lt;/p&gt;

&lt;p&gt;There is a solution for that! You can use Knapsack Pro with &lt;a href=&quot;https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it&quot;&gt;RSpec split by examples feature&lt;/a&gt; that will automatically detect slow RSpec test files in your project and split them between parallel Buildkite agents by test examples (test cases).&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example/buildkite.jpg&quot; style=&quot;width:250px;float:right;&quot; alt=&quot;Buildkite&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As you can see a combination of a few elements like Buildkite CI with cloud infrastructure solutions like AWS and an optimal split of test files using Knapsack Pro can improve significantly the work of your team.
With &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours&quot;&gt;Knapsack Pro&lt;/a&gt; you can achieve great results and super fast CI builds. Feel free to &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours&quot;&gt;try it&lt;/a&gt; and join other happy Buildkite users.&lt;/p&gt;

&lt;h3 id=&quot;related-articles&quot;&gt;Related articles&lt;/h3&gt;

&lt;p&gt;If you are looking for a Docker config you can also see repository examples at the end of the article: &lt;a href=&quot;/2017/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example&quot;&gt;Auto balancing 7 hours tests between 100 parallel jobs on Buildkite CI&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 19 Mar 2021 07:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/auto-scaling-buildkite-ci-build-agents-for-rspec-run-parallel-jobs-in-minutes-instead-of-hours</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Understanding and using Ruby&apos;s powerful #grep method</title>
        <description>&lt;p&gt;Ruby is known for its many handy methods. I’d like to take a look at one that is a little bit less known, but quite powerful nonetheless: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/understanding-and-using-rubys-powerful-grep-method/grep.png&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;#grep&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;about-the-method&quot;&gt;About the method&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://ruby-doc.org/core-3.0.0/Enumerable.html#method-i-grep&quot; target=&quot;_blank&quot;&gt;#grep&lt;/a&gt; is one of many instance methods defined on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable&lt;/code&gt; module. This essentially means that you can use it on all Ruby classes that include this module, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable&lt;/code&gt; module is the place where many other popular “collection” methods are defined - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#map&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#select&lt;/code&gt;, and the like. Whatever Ruby object you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#map&lt;/code&gt; on, you should be able to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep&lt;/code&gt; on as well.&lt;/p&gt;

&lt;h2 id=&quot;getting-a-grep&quot;&gt;“Getting a #grep”&lt;/h2&gt;

&lt;p&gt;Let’s see an example usage of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;array&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;s1&quot;&gt;&apos;foo&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;bar&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;baz&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^ba/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; [&quot;bar&quot;, &quot;baz&quot;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Seems pretty handy, right? And that’s just one way of using it. But before showing more examples, let’s deconstruct this to understand exactly how it works.&lt;/p&gt;

&lt;p&gt;What happens under the hood here could be mimicked with a code like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&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;element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/regexp/&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Or, more generally:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&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;element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notice a few things here:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The return value will always be an array. The length of this array will be between 0 (empty), and the size of the original collection (which is exactly how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#select&lt;/code&gt; behaves too).&lt;/li&gt;
  &lt;li&gt;We are using the &lt;em&gt;triple equals&lt;/em&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#===&lt;/code&gt;) method (“operator”).
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#===&lt;/code&gt; is probably worthy of a separate article. But for now, we can just recall that it’s implemented in classes as sort of a “lighter” (meaning: less strict) equality method (also called “case” equality due to its usage in case statements). We can illustrate the difference between regular and “case” equality with an example:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# the two objects are obviously not the same&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;o&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;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; false&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 2 falls within the range of 1..2&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;o&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;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;We don’t have to limit ourselves to regexes. Any object could be passed as a “pattern” to compare against.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The &lt;em&gt;triple equals&lt;/em&gt; method is invoked on the given &lt;em&gt;pattern&lt;/em&gt;, not the elements of the collection. (The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pattern === element&lt;/code&gt; bit is of course just a syntactical sugar for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pattern.=== element&lt;/code&gt; - stripping out the ‘sugar’ exposes the method invocation). Invoking the method on the pattern makes sense, as it’s this object that should “know” when something is passing its “light” equality requirements. Notice the difference here:&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foobar&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/foo/&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# invoking the `String#===` method&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; false&lt;/span&gt;

&lt;span class=&quot;sr&quot;&gt;/foo/&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;foobar&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# invoking the `Regexp#===` method&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;when-and-how-to-use-it&quot;&gt;When and how to use it&lt;/h2&gt;

&lt;p&gt;Enough theory. Let’s get down to it and look at some concrete situations where the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep&lt;/code&gt; method might come in very handy to you.&lt;/p&gt;

&lt;h3 id=&quot;grep-with-a-regexp&quot;&gt;#grep with a Regexp&lt;/h3&gt;

&lt;p&gt;You can easily match strings with a Regexp pattern.&lt;/p&gt;

&lt;p&gt;Just use:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;string_collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^pattern/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;instead of:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;string_collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&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;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/^pattern/&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Apart from handling collections of strings in the code, I find the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep&lt;/code&gt; method to be very handy for… recalling things (e.g. by performing a quick lookup in the terminal). These examples should illustrate the idea:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;constants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/RUBY/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt;[:RUBY_DESCRIPTION, :RUBY_VERSION, :RUBY_RELEASE_DATE, :RUBY_PLATFORM,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   :RUBY_PATCHLEVEL, :RUBY_REVISION, :RUBY_COPYRIGHT, :RUBY_ENGINE,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   :RUBY_ENGINE_VERSION, :RUBYGEMS_ACTIVATION_MONITOR]&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance_methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/gr/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [:grep, :grep_v, :group_by]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;(The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Regexp#===&lt;/code&gt; works with symbols just as it does with strings)&lt;/p&gt;

&lt;h3 id=&quot;grep-with-a-range&quot;&gt;#grep with a Range&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Range#===&lt;/code&gt; checks the inclusion of Numeric values in the given range. This means that we can pass a range to grep to quickly filter values falling within the range.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;numbers&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;mi&quot;&gt;1&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;3&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;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;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&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;o&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;c1&quot;&gt;#=&amp;gt; [3, 4, 5]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;grep-with-a-class-name-constant&quot;&gt;#grep with a class name Constant&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;all_sorts_of_things&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;mi&quot;&gt;1&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;s1&quot;&gt;&apos;apple&apos;&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;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;foo: &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;s1&quot;&gt;&apos;banana&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;all_sorts_of_things&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [{:foo=&amp;gt;4}]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;all_sorts_of_things&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [&quot;apple&quot;, &quot;banana&quot;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;grep-with-any-object&quot;&gt;#grep with any object&lt;/h3&gt;
&lt;p&gt;Since the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#===&lt;/code&gt; method in Ruby is implemented on an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;, you can pass any descendant of this class to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep&lt;/code&gt; method.
In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt; class, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#===&lt;/code&gt; method behaves the same way as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#==&lt;/code&gt;. It’s up to descending class to implement (or not) a different meaning between the two.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;all_sorts_of_things&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;mi&quot;&gt;1&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;s1&quot;&gt;&apos;apple&apos;&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;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;foo: &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;s1&quot;&gt;&apos;banana&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;all_sorts_of_things&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;apple&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [&apos;apple&apos;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;all_sorts_of_things&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [3]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;the-opposite-grep_v&quot;&gt;The opposite: #grep_v&lt;/h2&gt;

&lt;p&gt;Just like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#select&lt;/code&gt; has an opposite &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#reject&lt;/code&gt; method, there is also a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep_v&lt;/code&gt; method that behaves like you would imagine - it rejects the matched objects, similar to how this code would:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;string_collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reject&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;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Example usage:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;all_sorts_of_things&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;mi&quot;&gt;1&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;s1&quot;&gt;&apos;apple&apos;&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;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;foo: &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;s1&quot;&gt;&apos;banana&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;all_sorts_of_things&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep_v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [&quot;apple&quot;, {:foo=&amp;gt;4}, &quot;banana&quot;, nil]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;passing-a-block&quot;&gt;Passing a block&lt;/h2&gt;

&lt;p&gt;Both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep_v&lt;/code&gt; are utilizing a block if one is supplied. The block is used to transform the matched values before returning them in an array (just like chaining a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#map&lt;/code&gt; after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#select&lt;/code&gt; would). E.g.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;constants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/SEEK/&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;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cons&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;const_get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cons&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;#=&amp;gt; [1, 0, 3, 2, 4] # (values of IO::SEEK_CUR, IO::SEEK_SET, IO::SEEK_DATA, IO::SEEK_END, and IO::SEEK_HOLE)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The same operation without a #grep would probably look something like that:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;constants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&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;cons&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cons&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/SEEK/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;
             &lt;span class=&quot;nf&quot;&gt;map&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;cons&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;const_get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cons&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;#=&amp;gt; [1, 0, 3, 2, 4]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;As you can see, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep&lt;/code&gt; method is pretty powerful! If you haven’t already, add it to your developer’s arsenal and see where it can help you in your work.&lt;/p&gt;

&lt;p&gt;Are you using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#grep&lt;/code&gt; method in your project? Please share in the comments below!&lt;/p&gt;

&lt;p&gt;And if your Ruby project suffers from slow build times, consider using &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=understanding-and-using-rubys-grep-method&quot;&gt;Knapsack Pro&lt;/a&gt; to improve the productivity and delivery times of your team!&lt;/p&gt;
</description>
        <pubDate>Sat, 13 Mar 2021 11:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/understanding-and-using-rubys-powerful-grep-method</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/understanding-and-using-rubys-powerful-grep-method</guid>
        
        
        <category>techtips</category>
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>Run Jest tests on GitHub Actions with optimal parallelization</title>
        <description>&lt;p&gt;Jest is a powerful testing framework used in JavaScript projects. Besides vanilla JS, it’s often used for React, NodeJS, Angular or Vue.js projects, among others. I am going to help you configure running your Jest test suite on GitHub Actions. We are going to use CI parallelization with Knapsack Pro, for maximum effectiveness. Let’s begin.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/run-jest-on-github-actions-with-parallelization/jest.png&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Jest&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-parallelization&quot;&gt;Why parallelization&lt;/h2&gt;

&lt;p&gt;Splitting the test suites on multiple machines is a smart way to achieve short build times and increase your team’s productivity. A &lt;em&gt;naïve&lt;/em&gt; division of work between multiple machines might result in fewer gains than anticipated though, due to possible bottlenecks (please refer to &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot; target=&quot;_blank&quot;&gt;this article&lt;/a&gt; to learn more about the reasons behind it). This is where a &lt;em&gt;dynamic&lt;/em&gt; test distribution with &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-jest-on-github-actions-with-parallelization&quot;&gt;Knapsack Pro&lt;/a&gt; (&lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest&quot;&gt;@knapsack-pro/jest&lt;/a&gt;) comes into play. It ensures you utilize your parallel CI nodes (a.k.a. parallel jobs) in the optimal way.&lt;/p&gt;

&lt;h2 id=&quot;configuring-github-actions-build-matrix&quot;&gt;Configuring GitHub Actions: Build Matrix&lt;/h2&gt;

&lt;p&gt;GitHub Actions is configured through a yaml config file residing in your repository. It should be placed in the: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows&lt;/code&gt; directory, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows/main.yml&lt;/code&gt;. When properly configured, your GH Actions builds will be visible in the &lt;em&gt;Actions&lt;/em&gt; tab in your GitHub repository page.&lt;/p&gt;

&lt;p&gt;The Build Matrix is a powerful feature in GitHub Actions, which allows you to easily configure a combination of settings for running multiple jobs. Since we are concerned with simple parallelism for the purpose of this demonstration, our build matrix config is pretty straightforward:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci_node_total&lt;/code&gt; key, we always provide a single-element list. The value should be equal to the number of parallel nodes you are going to use.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci_node_index&lt;/code&gt; key should contain a list of indexes of your CI nodes. We are using zero-based numbering, so the list will essentially be a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0..n-1&lt;/code&gt; range, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; is the number of your parallel nodes (used as a value in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci_node_total&lt;/code&gt;). For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci_node_total: [2]&lt;/code&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci_node_index&lt;/code&gt; would be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0, 1]&lt;/code&gt;. For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci_node_total: [3]&lt;/code&gt;, it would be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0, 1, 2]&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;The above config would result in running 4 parallel nodes (jobs) on GitHub Actions. GitHub combines the provided lists into a matrix (hence the name) to determine this. As a result, the 4 parallel nodes would have these settings:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{matrix.ci_node_total: 4, matrix.ci_node_index: 0}&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{matrix.ci_node_total: 4, matrix.ci_node_index: 1}&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{matrix.ci_node_total: 4, matrix.ci_node_index: 2}&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{matrix.ci_node_total: 4, matrix.ci_node_index: 3}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly what we need to pass to the Knapsack Pro API to run the Queue correctly. Here’s the config step for running your Jest test suite on each node:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests with Knapsack Pro&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST }}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_total }}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_index }}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;npx @knapsack-pro/jest&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notice how we use the previously defined values of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matrix.ci_node_total&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matrix.ci_node_index&lt;/code&gt; to set the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt; variables, respectively.&lt;/p&gt;

&lt;p&gt;Apart from these two, there are two additional variables defined: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST&lt;/code&gt; is a token needed for Knapsack API authorization. Configure it using secrets (&lt;em&gt;Actions&lt;/em&gt; &amp;amp; &lt;em&gt;Dependabot&lt;/em&gt;) as described in &lt;a href=&quot;https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository&quot;&gt;GitHub Actions’ docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To view or generate Knapsack API token for your project, head over to &lt;a href=&quot;https://knapsackpro.com/sessions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-jest-on-github-actions-with-parallelization&quot;&gt;Knapsack User’s Dashboard&lt;/a&gt; (you may need to sign up for an account if you don’t have one). Save your API token in your GitHub Secrets as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The other variable, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/code&gt; needs to be set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;. This will ensure the retry functionality on GitHub Actions works correctly with Knapsack API.&lt;/p&gt;

&lt;p&gt;Here’s a full example config for Jest test suite on GitHub Actions:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/main.yaml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;8.x&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# [n] - where the n is a number of parallel jobs you want to run your tests on.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Use a higher number if you have slow tests to split them between more parallel jobs.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Remember to update the value of the `ci_node_index` below to (0..n-1).&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Indexes for parallel jobs (starting from zero).&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v1&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Use Node.js ${{ matrix.node-version }}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.node-version }}&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm install and build&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;npm install&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;npm run build --if-present&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests with Knapsack Pro&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_total }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_index }}&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# necessary for rerunning the same build to work correctly&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;npx @knapsack-pro/jest&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;running-your-builds&quot;&gt;Running your builds&lt;/h2&gt;

&lt;p&gt;When your GitHub Actions is properly set up, it’s going to run your build when you push new changes to the repository. You can now enjoy the optimal parallelization of your Jest tests.&lt;/p&gt;

&lt;p&gt;To see how Knapsack is splitting your test suite, visit the &lt;a href=&quot;https://knapsackpro.com/sessions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-jest-on-github-actions-with-parallelization&quot;&gt;User Dashboard&lt;/a&gt;. The GitHub Actions builds will also be visible under the &lt;em&gt;Actions&lt;/em&gt; tab in your project’s repository on GH.&lt;/p&gt;

&lt;p&gt;Anything unclear? Leave a comment below or go ahead and &lt;a href=&quot;https://knapsackpro.com/contact?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-jest-on-github-actions-with-parallelization&quot;&gt;contact us&lt;/a&gt; about your problem. We’ll be happy to help!&lt;/p&gt;
</description>
        <pubDate>Fri, 05 Mar 2021 08:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/run-jest-on-github-actions-with-parallelization</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/run-jest-on-github-actions-with-parallelization</guid>
        
        
        <category>continuous_integration</category>
        
        <category>jest</category>
        
      </item>
    
      <item>
        <title>Cucumber BDD testing using Github Actions parallel jobs to run tests quicker</title>
        <description>&lt;p&gt;Cucumber employs Behavior-Driven Development (BDD) for testing your application. This type of test is often time-consuming when running in the browser. You will learn how to run Cucumber tests on Github Actions using parallel jobs to execute the test suite much faster.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/cucumber-bdd-testing-using-github-actions-parallel-jobs-to-run-tests-quicker/cucumber-octocat-github.jpeg&quot; style=&quot;width:200px;margin-left: 15px;float:right;&quot; alt=&quot;cucumber, github, octocat&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;github-actions-matrix-strategy&quot;&gt;Github Actions matrix strategy&lt;/h2&gt;

&lt;p&gt;You can use the &lt;a href=&quot;https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix&quot;&gt;Github Actions matrix strategy&lt;/a&gt; to run parallel jobs. You will need to divide your Cucumber test files between the parallel jobs in a way that work will be balanced out between the jobs.&lt;/p&gt;

&lt;p&gt;It’s not that simple to do because often Cucumber tests can take a different amount of time. One test file can have many test cases, the other can have only a few but very complex ones, etc.&lt;/p&gt;

&lt;p&gt;There are often more steps in your CI pipeline like installing dependencies, loading data from the cache and each step can take a different amount of time per parallel job before even Cucumber tests are started. The steps affect the overall CI build speed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/not-optimal-tests-split.png&quot; style=&quot;width:100%;&quot; alt=&quot;not optimal tests split on CI server, CI parallelism&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What you would like to achieve is to run parallel jobs in a way that they always finish the execution of Cucumber tests at a similar time. Thanks to that you will avoid lagging jobs that could be a bottleneck in your CI build.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/optimal-tests-split.png&quot; style=&quot;width:100%;&quot; alt=&quot;optimal tests split on CI server, CI parallelism&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;dynamically-split-cucumber-tests-using-queue-mode&quot;&gt;Dynamically split Cucumber tests using Queue Mode&lt;/h2&gt;

&lt;p&gt;To get optimal CI build execution time you need to ensure the work between parallel jobs is split in such a way as to avoid bottleneck slow job.
To achieve that you can split Cucumber test files in a dynamic way between the parallel jobs using &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-bdd-testing-using-github-actions-parallel-jobs-to-run-tests-quicker&quot;&gt;Knapsack Pro&lt;/a&gt; Queue Mode and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; ruby gem.&lt;/p&gt;

&lt;p&gt;Knapsack Pro API will take care of coordinating how tests are divided between parallel jobs. On the API side, there is a Queue with a list of your test files and each parallel job on Github Actions is running Cucumber tests via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; Ruby gem. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; gem asks Queue API for a set of test files to run and after it gets executed then the gem asks for another set of test files until the Queue is consumed. This ensures that all parallel jobs finish running tests at a very similar time so that you can avoid bottleneck jobs.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/knapsack-pro-cloud-v2.png&quot; style=&quot;width:70%;display:block;margin:0 auto;&quot; alt=&quot;tests split on CI server with Knapsack Pro Queue Mode, CI parallelism&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can learn more about the &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;dynamic tests suite split in Queue Mode&lt;/a&gt; or check the video below.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;github-actions-parallel-jobs-config-for-cucumber&quot;&gt;Github Actions parallel jobs config for Cucumber&lt;/h2&gt;

&lt;p&gt;Here is the full Github Actions YAML config example for the Cucumber test suite in a Ruby on Rails project using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; gem to run Cucumber tests between parallel jobs.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/main.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# If you need DB like PostgreSQL, Redis then define service below.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# https://github.com/actions/example-services/tree/main/.github/workflows&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:10.8&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5432:5432&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# needed because the postgres container does not provide a healthcheck&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# tmpfs makes DB faster by using RAM&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;&amp;gt;-&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--mount type=tmpfs,destination=/var/lib/postgresql/data&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-cmd pg_isready&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-interval 10s&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-timeout 5s&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-retries 5&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;redis&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;6379:6379&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--entrypoint redis-server&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Set N number of parallel jobs you want to run tests on.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Use higher number if you have slow tests to split them on more parallel jobs.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Remember to update ci_node_index below to 0..N-1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# set N-1 indexes for parallel jobs&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# When you run 2 parallel jobs then first job will have index 0, the second job will have index 1 etc&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set up Ruby&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/setup-ruby@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ruby-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2.7&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ runner.os }}-gems-${{ hashFiles(&apos;**/Gemfile.lock&apos;) }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;restore-keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;${{ runner.os }}-gems-&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Bundle install&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle config path vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle install --jobs 4 --retry 3&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create DB&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# use localhost for the host here because we have specified a container for the job.&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# If we were running the job on the VM this would be postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bin/rails db:prepare&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CUCUMBER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_CUCUMBER }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_total }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_index }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_LOG_LEVEL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;info&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:queue:cucumber&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here is the view from Github Actions showing that we run 8 parallel jobs for the CI build.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/cucumber-bdd-testing-using-github-actions-parallel-jobs-to-run-tests-quicker/github-actions-parallel-jobs.png&quot; style=&quot;display:block;margin:0 auto;&quot; alt=&quot;Github Actions, parallel jobs, CI, testing&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;I hope you find this example useful. If you would like to learn more about &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-bdd-testing-using-github-actions-parallel-jobs-to-run-tests-quicker&quot;&gt;Knapsack Pro please check our homepage&lt;/a&gt; and see a list of &lt;a href=&quot;/&quot;&gt;supported test runners for parallel testing in Ruby, JavaScript, etc&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 03 Mar 2021 07:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/cucumber-bdd-testing-using-github-actions-parallel-jobs-to-run-tests-quicker</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/cucumber-bdd-testing-using-github-actions-parallel-jobs-to-run-tests-quicker</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Faster pipelines with Knapsack Pro by parallelizing Cypress tests on GitLab CI</title>
        <description>&lt;p&gt;When the runtime of our build pipeline had reached 20 minutes on average for each commit on each Merge Request, we knew we needed a solution to speed things up. We wanted to optimize everything we could within the pipeline, a way of doing this was to parallelize our tests with &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci&quot;&gt;Knapsack Pro&lt;/a&gt; to speed up our Cypress integration tests which would save us a sizable amount of time.&lt;/p&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/logo.png&quot;&gt;
  &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/logo.png&quot; class=&quot;img-large&quot; alt=&quot;Knapsack Pro, Gitlab, and Cypress logos flying around in a speed frenzy&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;style&gt;
.post a &gt; figure &gt; figcaption {
  color: #111;
  text-decoration: none;
}
.post a:hover &gt; figure &gt; figcaption,
.post a &gt; figure:hover &gt; figcaption,
.post a &gt; figure &gt; figcaption:hover {
  color: #1d7690;
  text-decoration: none;
}

.post p,
.post p &gt; a &gt; figure.
.post p &gt; a &gt; figure &gt; img {
  display: block;
  width: 75%;
  margin: 15px auto;
}

.post section,
.post article {
  box-sizing: border-box;
}
.post section {
  display: flex;
  flex-wrap: wrap;
}
.post article {
  width: 50%;
  text-align: center;
  padding: 15px;
}

.post section &gt; article &gt; a &gt; figure &gt; img {
  max-height: 400px;
  display: block;
  margin: 0 auto 15px;
}
&lt;/style&gt;

&lt;h2 id=&quot;what-is-our-setup&quot;&gt;What is our setup?&lt;/h2&gt;

&lt;p&gt;Our team is working on &lt;a href=&quot;https://www.kiwi.com/en/&quot;&gt;Kiwi.com&lt;/a&gt;, which is a travel company selling tickets for flights, trains, buses and any other kinds of transportation. More specifically, the team is responsible for the Help Center, where users can go to find articles and chat with support before, during and after their trips.&lt;/p&gt;

&lt;p&gt;Our application is deployed in two distinct ways: as an npm package integrated into other modules within Kiwi.com as a sidebar &lt;em&gt;(called &lt;strong&gt;sidebar&lt;/strong&gt;)&lt;/em&gt;, and as a standalone web application &lt;em&gt;(called &lt;strong&gt;full-page&lt;/strong&gt;)&lt;/em&gt; which is dockerized and then deployed on GKE (&lt;a href=&quot;https://cloud.google.com/kubernetes-engine&quot;&gt;Google Kubernetes Engine&lt;/a&gt;) for our production environment and using GCR (&lt;a href=&quot;https://cloud.google.com/run&quot;&gt;Google Cloud Run&lt;/a&gt;) for staging envs .&lt;/p&gt;

&lt;p&gt;Most of the code is shared between these units but there are some minor differences in design and major differences in flows/functionality, so they need to be tested separately.&lt;/p&gt;

&lt;p&gt;We use &lt;a href=&quot;https://about.gitlab.com/&quot;&gt;GitLab&lt;/a&gt; for code reviews and version control and GitLab CI to run our build pipelines.&lt;/p&gt;

&lt;p&gt;We use &lt;a href=&quot;https://www.cypress.io/&quot;&gt;Cypress&lt;/a&gt; for end-to-end and integration testing with separate tests for both types of deployment.&lt;/p&gt;

&lt;h2 id=&quot;what-problem-are-we-trying-to-solve&quot;&gt;What problem are we trying to solve?&lt;/h2&gt;

&lt;p&gt;We want our pipelines and tests to run faster but not spend too much time nor money on doing it, so running each test on its own server is probably not the best way to go, not to mention the overhead of starting up the runners and checking out the source code.&lt;/p&gt;

&lt;p&gt;If our pipelines are slow, developers could forget that they had something running in the background and jump between tasks or worse, somebody else merges code into the main branch and the pipeline has to be run again…&lt;/p&gt;

&lt;p&gt;If our pipelines are fast, then we have to wait less while they are running, after applying some of the changes suggested during code reviews and to get hotfixes out to production if necessary.&lt;/p&gt;

&lt;h2 id=&quot;manual-splitting-of-cypress-tests&quot;&gt;Manual splitting of Cypress tests&lt;/h2&gt;

&lt;p&gt;After seeing that running tests in series takes extremely long, we tried to split some areas up and run them on separate Cypress runners and this worked for a while. But, as the tests’ size grew asymmetrically, we had to shift around which suites should run together continuously.&lt;/p&gt;

&lt;section&gt;
  &lt;article&gt;
    &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/00-cypress-files-before-knapsack.png&quot;&gt;
      &lt;figure&gt;
        &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/00-cypress-files-before-knapsack.png&quot; class=&quot;img-large&quot; alt=&quot;List of Cypress test files, showing that tests are nested two levels: by module and by feature or area&quot; /&gt;
        &lt;figcaption&gt;List of Cypress test files, showing that tests are nested two levels: by module and by feature or area&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;/a&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/01-pipeline-jobs-before-knapsack.png&quot;&gt;
      &lt;figure&gt;
        &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/01-pipeline-jobs-before-knapsack.png&quot; class=&quot;img-list&quot; alt=&quot;List of Cypress test Gitlab CI jobs, showing that there are many separate jobs for each of the test subfolders&quot; /&gt;
        &lt;figcaption&gt;List of Cypress test Gitlab CI jobs, showing that there are many separate jobs for each of the test subfolders&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;/a&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/02-cypress-runtime-before-knapsack.png&quot;&gt;
      &lt;figure&gt;
        &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/02-cypress-runtime-before-knapsack.png&quot; class=&quot;img-list&quot; alt=&quot;List of Cypress test Gitlab CI jobs, showing that job runtimes vary from 53 seconds to 5 minutes&quot; /&gt;
        &lt;figcaption&gt;List of Cypress test Gitlab CI jobs, showing that job runtimes vary from 53 seconds to 5 minutes&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;/a&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/03-pipeline-runtime-before-knapsack.png&quot;&gt;
      &lt;figure&gt;
        &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/03-pipeline-runtime-before-knapsack.png&quot; class=&quot;img-list&quot; alt=&quot;Pipeline summary showing: 33 jobs for master in 19 minutes and 24 seconds&quot; /&gt;
        &lt;figcaption&gt;Pipeline summary&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;/a&gt;
  &lt;/article&gt;
&lt;/section&gt;

&lt;h2 id=&quot;what-is-knapsack-pro&quot;&gt;What is Knapsack Pro?&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci&quot;&gt;Knapsack Pro&lt;/a&gt; is a tool that helps split up test suites in a dynamic way across parallel CI runners to ensure that each runner finishes work simultaneously.&lt;/p&gt;

&lt;p&gt;Its name comes from the “&lt;a href=&quot;https://en.wikipedia.org/wiki/Knapsack_problem&quot;&gt;knapsack problem&lt;/a&gt;”, a problem in combinatorial optimization, to figure out how to take things (test suites) with certain attributes (number of tests, time it took on average to run, etc) and determine how best to divide them to get the most value (all CI nodes are running tests from boot until shutdown and they all finish at the same time).&lt;/p&gt;

&lt;p&gt;It comes with a dashboard to see the distribution of test subsets for each parallel run of CI runners, which is useful when splitting test suites or to see if something is going wrong with the runs. The dashboard also suggests if you’d benefit from less or more parallel runners as well.&lt;/p&gt;

&lt;p&gt;The founder of Knapsack Pro, &lt;a href=&quot;https://github.com/ArturT&quot;&gt;Artur Trzop&lt;/a&gt; has been helping us proactively, sometimes noticing and notifying us that something went wrong with our pipeline even before we did. Whenever we had an issue, we could just write to him and we would figure out a solution together.&lt;/p&gt;

&lt;h2 id=&quot;plugging-in-knapsack-pro&quot;&gt;Plugging in Knapsack Pro&lt;/h2&gt;

&lt;p&gt;To try out &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci&quot;&gt;Knapsack Pro&lt;/a&gt;, all we did was &lt;a href=&quot;https://docs.knapsackpro.com/cypress/guide/&quot;&gt;follow the docs&lt;/a&gt; and afterward merge all separate Cypress jobs into one and set up some &lt;a href=&quot;https://docs.cypress.io/app/tooling/reporters#Reporter-Options&quot;&gt;reporterOptions&lt;/a&gt; in Cypress to ensure that we’ll collect all of the test results as JUnit reports from all the parallel runs.&lt;/p&gt;

&lt;p&gt;Here’s a short example of our &lt;strong&gt;full-page&lt;/strong&gt; test jobs before the conversion:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;.cypress_fp&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;cypress_fp&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cypress/browsers:node14.15.0-chrome86-ff82&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;retry&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;before_script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn install--frozen-lockfile --production=false&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# save screenshots as an artifact&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fp-screenshots&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;expire_in&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;3 days&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;on_failure&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress/screenshots/&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;fp contact&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*cypress_fp&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn cypress:run-ci -s &quot;./cypress/tests/full-page/contact/*.js&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;fp login&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*cypress_fp&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn cypress:run-ci-s &quot;./cypress/tests/full-page/login/*.js&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;fp search&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*cypress_fp&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn cypress:run-ci -s &quot;./cypress/tests/full-page/search/*.js&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# etc ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here is the new &lt;strong&gt;full-page&lt;/strong&gt; test job after the conversion:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;.cypress_parallel_defaults&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;cypress_parallel_defaults&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;full-page&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cypress/browsers:node14.15.0-chrome86-ff82&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;retry&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;before_script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn install--frozen-lockfile --production=false&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yarn @knapsack-pro/cypress --record &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; --reporter junit --reporter-options &quot;mochaFile=cypress/results/junit-[hash].xml&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;after_script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# collect all artifacts&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mkdir -p cypress/screenshots/full-page &amp;amp;&amp;amp; cp -r cypress/screenshots/full-page $CI_PROJECT_DIR&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mkdir -p cypress/videos/full-page &amp;amp;&amp;amp; cp -r cypress/videos/full-page $CI_PROJECT_DIR&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mkdir -p cypress/results &amp;amp;&amp;amp; cp -r cypress/results $CI_PROJECT_DIR&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# all the copying above is done to make it easy to browse the artifacts in the UI (single folder)&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# save videos and screenshots as artifacts&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$CI_JOB_NAME_$CI_NODE_INDEX-of-$CI_NODE_TOTAL&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;on_failure&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;expire_in&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;3 days&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;full-page/&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;reports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;junit&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;results/junit-*.xml&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;expose_as&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress_full-page&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;CYPRESS_BASE_URL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://$CI_ENVIRONMENT_SLUG.$CLOUDRUN_CUSTOM_DOMAIN_SUFFIX/en/help&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS_FULL_PAGE&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_BUILD_ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;full-page-$CI_COMMIT_REF_SLUG-$CI_PIPELINE_ID&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_FILE_PATTERN&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cypress/full-page/*.js&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This was the result we saw on the Knapsack Pro Dashboard, showing that our current separation of suites needs to be worked on to gain all the benefits of parallelization:&lt;/p&gt;

&lt;p&gt;
  &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/04-cypress-suites-fullpage-before-split.png&quot;&gt;
    &lt;figure&gt;
      &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/04-cypress-suites-fullpage-before-split.png&quot; class=&quot;img-large&quot; alt=&quot;Knapsack Pro dashboard showing the slowest test files, slowest being almost four minutes while the second slowest is only 30 seconds&quot; /&gt;
      &lt;figcaption&gt;Knapsack Pro dashboard showing the slowest test files, slowest being almost four minutes while the second slowest is only 30 seconds&lt;/figcaption&gt;
    &lt;/figure&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;After some tweaking, splitting, and moving suites around, this is what we finally had:&lt;/p&gt;

&lt;section&gt;
  &lt;article&gt;
    &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/05-cypress-files-after-knapsack.png&quot;&gt;
      &lt;figure&gt;
        &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/05-cypress-files-after-knapsack.png&quot; alt=&quot;List of Cypress test files, showing that tests are a single level: by module&quot; /&gt;
        &lt;figcaption&gt;List of Cypress test files, showing that tests are a single level: by module&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;/a&gt;
      &lt;/article&gt;
      &lt;article&gt;
    &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/06-pipeline-jobs-after-knapsack.png&quot;&gt;
      &lt;figure&gt;
        &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/06-pipeline-jobs-after-knapsack.png&quot; alt=&quot;List of Cypress test Gitlab CI jobs, showing that there are only two separate jobs parallelized four times for each of the modules&quot; /&gt;
        &lt;figcaption&gt;List of Cypress test Gitlab CI jobs, showing that there are only two separate jobs parallelized four times for each of the modules&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;/a&gt;
      &lt;/article&gt;
      &lt;article&gt;
    &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/07-cypress-runtime-after-knapsack.png&quot;&gt;
      &lt;figure&gt;
        &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/07-cypress-runtime-after-knapsack.png&quot; alt=&quot;List of Cypress test Gitlab CI jobs, showing that job runtimes are almost equal for each batch of parallel jobs (3:18 - 3:24 and 2:07 - 2:17)&quot; /&gt;
        &lt;figcaption&gt;List of Cypress test Gitlab CI jobs, showing that job runtimes are almost equal for each batch of parallel jobs (3:18 - 3:24 and 2:07 - 2:17)&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;/a&gt;
      &lt;/article&gt;
      &lt;article&gt;
    &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/08-pipeline-runtime-after-knapsack.png&quot;&gt;
      &lt;figure&gt;
        &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/08-pipeline-runtime-after-knapsack.png&quot; alt=&quot;Pipeline summary showing: 33 jobs for master in 15 minutes and 21 seconds&quot; /&gt;
        &lt;figcaption&gt;Pipeline summary&lt;/figcaption&gt;
      &lt;/figure&gt;
    &lt;/a&gt;
  &lt;/article&gt;
&lt;/section&gt;

&lt;p&gt;It is still not perfectly balanced but with this granularity, we can utilize four runners at the same time and there isn’t too much idle time, so we’re not wasting CPU cycles.&lt;/p&gt;

&lt;p&gt;
  &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/09-after_cypress_new_master_fullpage.png&quot;&gt;
    &lt;figure&gt;
      &lt;img src=&quot;/images/blog/posts/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci/09-after_cypress_new_master_fullpage.png&quot; alt=&quot;Knapsack Pro dashboard showing the slowest test files, slowest being 21.803 seconds while the second slowest being 21.001 seconds (some other tests are only 1-6 seconds short)&quot; /&gt;
      &lt;figcaption&gt;Knapsack Pro dashboard showing the slowest test files, slowest being 21.803 seconds while the second slowest being 21.001 seconds (some other tests are only 1-6 seconds short)&lt;/figcaption&gt;
    &lt;/figure&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;h2 id=&quot;what-else-can-we-do&quot;&gt;What else can we do?&lt;/h2&gt;

&lt;p&gt;15 minutes still feels long, pipeline runtimes around 10 minutes are a good goal to reach but we have more optimization to do around build times and Docker compilation times, which are out of scope for this article. If you’re interested in reading more about how to do that, I can recommend the article &lt;a href=&quot;https://www.robincussol.com/docker-for-js-devs-how-to-containerise-nodejs-apps-efficiently/&quot;&gt;Docker for JavaScript Devs: How to Containerize Node.js Apps Efficiently&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2 id=&quot;results&quot;&gt;Results&lt;/h2&gt;

&lt;p&gt;The switch to &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci&quot;&gt;Knapsack Pro&lt;/a&gt; took less than a day. Most of the work afterward was to split up tasks and optimize other parts of GitLab CI. The whole process took two weeks for a single developer.&lt;/p&gt;

&lt;p&gt;By splitting up the test suites into smaller, equal sized chunks, running tests parallelized on four CI runners (for each of our target builds) and some other general pipeline fixes (better caching between jobs/steps, only running parts of the pipeline for actual changes), we’ve managed to decrease our pipeline runtime of ~20 minutes to ~15 minutes.&lt;/p&gt;

&lt;p&gt;When we add more test suites, Knapsack Pro will take care of sorting the tests to the proper CI runners to ensure that pipeline runtimes don’t increase if necessary.&lt;/p&gt;

&lt;p&gt;We sped up our pipeline by 25%, or 5 minutes on average, and seeing that each of our three developers starts about 6-10 pipelines a day (depending on the workload and type of work), we’ve essentially saved between 90-150 minutes of development time a day, as well as increased morale because &lt;strong&gt;&lt;em&gt;nobody likes to wait.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 02 Mar 2021 15:20:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/faster-pipelines-with-knapsack-pro-by-parallelizing-cypress-tests-on-gitlab-ci</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Setting up Knapsack Pro in your Ruby RSpec project</title>
        <description>&lt;p&gt;Knapsack Pro offers two modes for RSpec tests in your Ruby application: the Regular Mode and the Queue Mode. They differ in their approach towards dividing your test files between parallel CI nodes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/setting-up-knapsack-pro-in-ruby-rspec-project/regular_vs_queue.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Knapsack Pro Regular Mode vs Queue Mode in Ruby RSpec&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;regular-mode-vs-queue-mode&quot;&gt;Regular Mode vs Queue Mode&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Regular Mode&lt;/strong&gt; uses timing data for every test file and splits all tests based on that. For example, if you use 6 parallel CI nodes, it will split your tests into 6 sets, roughly equal by their combined timings (based on previously collected timing data). These sets will then be distributed across parallel machines and run by them. We call this a &lt;em&gt;static&lt;/em&gt; distribution of tests.&lt;/p&gt;

&lt;p&gt;With the &lt;strong&gt;Queue Mode&lt;/strong&gt;, there is one central queue comprising all of the test files. Each parallel node receives a small subset of tests from the queue and runs them. The pattern repeats until the whole queue is empty. (BTW, The Queue Mode also uses the timing data - but this is used only to order the tests in the queue in a way that ensures the best overall performance.)
We don’t know which tests will end up distributed to which node beforehand - that’s why this distribution is &lt;em&gt;dynamic&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The way the Queue Mode works helps mitigate unstable test run times and parallel node performance. When the metrics can vary, the queue mode is the answer.&lt;/p&gt;

&lt;p&gt;The Regular vs Queue Mode distinction is covered more in-depth in a &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;separate post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After helping many different projects over the years, we can say, that the Queue Mode is usually the best solution. In the real world, the performance of individual machines and tests is subject to all sorts of unpredictability. Using Queue Mode results in the most robust optimization despite these constraints.&lt;/p&gt;

&lt;h2 id=&quot;always-start-your-setup-with-regular-mode&quot;&gt;Always start your setup with Regular Mode&lt;/h2&gt;

&lt;p&gt;Since we are claiming that the Queue Mode is the ultimate best for most projects, you might be tempted to jump right in and give it a try. This is &lt;em&gt;not&lt;/em&gt; what we advise, however. Let me explain why.&lt;/p&gt;

&lt;h3 id=&quot;non-trivial-setup&quot;&gt;Non-trivial setup&lt;/h3&gt;

&lt;p&gt;We are doing our best to make the &lt;a href=&quot;https://docs.knapsackpro.com/&quot;&gt;Knapsack setup&lt;/a&gt; experience as smooth as possible for every project giving our solution a try. Some things just lie outside of our control, though.&lt;/p&gt;

&lt;p&gt;The combination of your specific CI pipeline configuration and your RSpec usage might require some adjustments along the way.&lt;/p&gt;

&lt;p&gt;What we know from experience is that usually the Queue Mode uncovers different adjustment needs than the Regular Mode. The reason behind that is that in the Queue Mode, each one of your parallel CI nodes will usually run the RSpec command multiple times (as compared to just one run per node in the Regular Mode). This distinction might result in additional challenges to solve.&lt;/p&gt;

&lt;p&gt;As with all debugging, it’s best to narrow down (isolate) possible issues we are tackling at a given time. This is why we strongly suggest to &lt;a href=&quot;https://docs.knapsackpro.com/&quot;&gt;set up&lt;/a&gt; the Regular Mode first. When you ensure it works as expected, and confirm that in your &lt;a href=&quot;https://knapsackpro.com/sessions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=setting-up-knapsack-pro-in-rspec-project&quot;&gt;User Dashboard&lt;/a&gt;, you are ready to proceed to the Queue Mode setup.&lt;/p&gt;

&lt;p&gt;And now for the good news: we have documented all of the common issues you might encounter during your setup in our &lt;a href=&quot;https://knapsackpro.com/faq?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=setting-up-knapsack-pro-in-rspec-project&quot;&gt;FAQ&lt;/a&gt;. Keep that in mind during your onboarding.&lt;/p&gt;

&lt;p&gt;Head over to our &lt;a href=&quot;https://docs.knapsackpro.com/&quot;&gt;installation guide&lt;/a&gt; and get the best out of Knapsack for your project today! We would very happy to assist you along the way, so do not hesitate to &lt;a href=&quot;https://knapsackpro.com/contact?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=setting-up-knapsack-pro-in-rspec-project&quot;&gt;contact us&lt;/a&gt; if you need anything.&lt;/p&gt;

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

&lt;p&gt;Following the described order of steps (using Regular Mode before Queue Mode) should result in the smoothest &lt;a href=&quot;https://docs.knapsackpro.com/&quot;&gt;Knapsack Pro setup&lt;/a&gt; experience. In case you are stuck at any point, please consult our &lt;a href=&quot;https://docs.knapsackpro.com/knapsack_pro-ruby/guide/&quot;&gt;installation guide&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/faq?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=setting-up-knapsack-pro-in-rspec-project&quot;&gt;FAQ&lt;/a&gt;, or simply &lt;a href=&quot;https://knapsackpro.com/contact?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=setting-up-knapsack-pro-in-rspec-project&quot;&gt;contact us&lt;/a&gt;. We’ll be more than happy to help!&lt;/p&gt;
</description>
        <pubDate>Thu, 25 Feb 2021 08:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/setting-up-knapsack-pro-in-rspec-project</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/setting-up-knapsack-pro-in-rspec-project</guid>
        
        
        <category>techtips</category>
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Best Heroku add-ons for Ruby on Rails project</title>
        <description>&lt;p&gt;After working for over 8 years with Heroku and Ruby on Rails projects I have my own favorite set of Heroku add-ons that work great with Rails apps. You are about to learn about the add-ons that come in handy in your daily Ruby developer life.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/best-heroku-add-ons-for-ruby-on-rails-project/heroku_addons.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Heroku, Heroku Marketplace, Heroku add-on, add-on, add-ons, Ruby, RoR, Rails, Ruby on Rails&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;heroku-add-ons&quot;&gt;Heroku add-ons&lt;/h2&gt;

&lt;p&gt;Here it is, a list of my favorite Heroku add-ons from Heroku Marketplace and why I choose them for my Ruby on Rails projects hosted on Heroku.&lt;/p&gt;

&lt;h3 id=&quot;heroku-scheduler&quot;&gt;Heroku Scheduler&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://elements.heroku.com/addons/scheduler&quot;&gt;Heroku Scheduler&lt;/a&gt; can run scheduled tasks every 10 minutes, every hour, or every day. I use it to run my scheduled rake tasks. For instance every day I run a rake task that will send a summary of users who signed up in the last 24 hours to my mailbox.&lt;/p&gt;

&lt;p&gt;Heroku Scheduler add-on is free. The only limitation is that it has fewer options than the cron in the Unix system. If you need to run a rake task every Monday then you need to set up a rake task as a daily task in Heroku Scheduler and do a check of the day in the rake task itself to skip it when needed.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# lib/tasks/schedule/notify_users_about_past_due_subscription.rake&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:schedule&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Send notification about past due subscriptions to users&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;notify_users_about_past_due_subscription: :environment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;monday?&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Billing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;NotifyUsersAboutPastDueSubscriptionWorker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_async&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Skip schedule:notify_users_about_past_due_subscription task.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;new-relic-apm&quot;&gt;New Relic APM&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://elements.heroku.com/addons/newrelic&quot;&gt;New Relic&lt;/a&gt; add-on does application performance monitoring. It’s one of my favorite add-ons. It allows to track each process like puma/unicorn/sidekiq per dyno and its performance. You can see which Rails controller actions take the most time. You can see your API endpoints with the highest throughput and those which are time-consuming. New Relic helped me many times to debug bottlenecks in my app and thanks to that I was able to make &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=best-heroku-add-ons-for-ruby-on-rails-project&quot;&gt;Knapsack Pro API&lt;/a&gt; with an average 50ms response time. Who said the Rails app has to be slow? :)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/best-heroku-add-ons-for-ruby-on-rails-project/new_relic_api_response_time.png&quot; alt=&quot;New Relic, response time, Rails, Ruby, API&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;rollbar&quot;&gt;Rollbar&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://elements.heroku.com/addons/rollbar&quot;&gt;Rollbar&lt;/a&gt; allows for exception tracking in your Ruby code and also in JS code on the front end side. It has a generous free plan with a 5000 exception limit per month.&lt;/p&gt;

&lt;p&gt;You can easily ignore some common Rails exceptions to stay within the free plan limit.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# config/initializers/rollbar.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Rollbar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;access_token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ROLLBAR_ACCESS_TOKEN&apos;&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;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;test?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;development?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enabled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Add exception class names to the exception_level_filters hash to&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# change the level that exception is reported at. Note that if an exception&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# has already been reported and logged the level will need to be changed&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# via the rollbar interface.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Valid levels: &apos;critical&apos;, &apos;error&apos;, &apos;warning&apos;, &apos;info&apos;, &apos;debug&apos;, &apos;ignore&apos;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# &apos;ignore&apos; will cause the exception to not be reported at all.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exception_level_filters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ActionController::RoutingError&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;ignore&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exception_level_filters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ActionController::InvalidAuthenticityToken&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;ignore&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exception_level_filters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ActionController::BadRequest&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;ignore&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exception_level_filters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ActiveRecord::RecordNotFound&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;ignore&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exception_level_filters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Rack::Timeout::RequestTimeoutException&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;ignore&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exception_level_filters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Rack::QueryParser::InvalidParameterError&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;ignore&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exception_level_filters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ActionDispatch::Http::MimeNegotiation::InvalidType&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;ignore&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;environment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ROLLBAR_ENV&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;presence&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;redis-cloud&quot;&gt;Redis Cloud&lt;/h3&gt;

&lt;p&gt;If you use Redis in your Ruby on Rails app then &lt;a href=&quot;https://elements.heroku.com/addons/rediscloud&quot;&gt;Redis Cloud&lt;/a&gt; is your add-on. It has a free plan and paid plans are more affordable than other add-ons have.&lt;/p&gt;

&lt;p&gt;Redis Cloud add-on does automatic backups of your data and offers a nice web UI to preview the live Redis usage and historical usage of your database instance.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/best-heroku-add-ons-for-ruby-on-rails-project/redis_cloud_graphs.png&quot; alt=&quot;Redis Cloud, Redis&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I like to use Redis Cloud + sidekiq gem in my Rails apps. Also, Redis is useful if you need to cache some data quickly in the memory and expire it after some time.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;redis_connection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Redis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# use REDISCLOUD_URL when app is running on Heroku,&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# or fallback to local Redis (useful for development)&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;url: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;REDISCLOUD_URL&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;redis://localhost:6379/0&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# tune network timeouts to be a little more lenient when you are seeing occasional timeout&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# errors for Heroku Redis Cloud addon&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# https://github.com/sidekiq/sidekiq/wiki/Using-Redis#life-in-the-cloud&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;timeout: &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;n&quot;&gt;redis_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;my-key-name&apos;&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;nf&quot;&gt;hour&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;this value will expire in 1 hour&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;twilio-sendgrid&quot;&gt;Twilio SendGrid&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://elements.heroku.com/addons/sendgrid&quot;&gt;SendGrid&lt;/a&gt; is a free add-on that allows you to start sending emails from your Ruby on Rails. You can even connect your domain to it so your users get emails from your domain.&lt;/p&gt;

&lt;p&gt;There are free 12,000 emails per month in the free plan.&lt;/p&gt;

&lt;h2 id=&quot;heroku-add-on-to-save-you-time--money&quot;&gt;Heroku Add-on to save you time &amp;amp; money&lt;/h2&gt;

&lt;p&gt;Here are a few of my favorite add-ons that will help you save money and time in your project.&lt;/p&gt;

&lt;h3 id=&quot;autoidle&quot;&gt;AutoIdle&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://elements.heroku.com/addons/autoidle&quot;&gt;AutoIdle&lt;/a&gt; lets you save money by automatically putting your staging and review apps to sleep on Heroku. I use it to turn off my web and worker dyno for the staging app when there is no traffic to the app. No more paying for Heroku resources during the night and weekends. ;)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/best-heroku-add-ons-for-ruby-on-rails-project/autoidle.png&quot; alt=&quot;AutoIdle, Heroku&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;rails-autoscale&quot;&gt;Rails Autoscale&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://elements.heroku.com/addons/rails-autoscale&quot;&gt;Rails Autoscale&lt;/a&gt; is a powerful add-on that will help you save money on Heroku. It will measure requests queue time and based on that add or remove dynos for your web processes. If you have higher traffic during the day it will add more dynos. During the night when the traffic is low, it will remove dynos.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/best-heroku-add-ons-for-ruby-on-rails-project/rails_autoscale_graph.png&quot; alt=&quot;Rails Autoscale, Heroku, request queue&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Rails Autoscale can also track your worker queue. For instance, if you have a lot of jobs scheduled in Sidekiq then Rails Autoscale will add more worker dynos to process your job queue faster. It can even shut down worker dyno when there are no jobs to be processed which can save you even more money.&lt;/p&gt;

&lt;h3 id=&quot;knapsack-pro&quot;&gt;Knapsack Pro&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://elements.heroku.com/addons/knapsack-pro&quot;&gt;Knapsack Pro&lt;/a&gt; is a Heroku add-on and ruby gem that can run your Rails tests in RSpec, Cucumber, Minitest, etc, and automatically split the tests between parallel machines on any CI server. It works with Heroku CI, CircleCI, Buildkite, Travis CI, etc. It will help you save time by doing &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;a dynamic split of tests with Queue Mode&lt;/a&gt; to ensure all parallel jobs finish work at a similar time. This way you optimize your CI build runs and save the most time.&lt;/p&gt;

&lt;p&gt;Below you can see an example of the optimal distribution of tests, where each parallel CI machine performs tests for 10 minutes, thanks to which the entire CI build lasts only 10 minutes instead of 40 if you would run tests on a single CI server only.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/optimal-tests-split.png&quot; style=&quot;width:100%;&quot; alt=&quot;optimal tests split on CI server, CI parallelism&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve been working on the &lt;a href=&quot;https://elements.heroku.com/addons/knapsack-pro&quot;&gt;Knapsack Pro add-on&lt;/a&gt; and I’d love to hear your feedback if you give it a try.&lt;/p&gt;
</description>
        <pubDate>Thu, 25 Feb 2021 07:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/best-heroku-add-ons-for-ruby-on-rails-project</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/best-heroku-add-ons-for-ruby-on-rails-project</guid>
        
        
        <category>techtips</category>
        
      </item>
    
      <item>
        <title>RSpec testing parallel jobs with CircleCI and JUnit XML report</title>
        <description>&lt;p&gt;You will learn how to run RSpec tests for your Ruby on Rails project on CircleCI with parallel jobs to shorten the running time of your CI build. Moreover, you will learn how to configure JUnit formatter to generate an XML report for your tests to show failing RSpec test examples nicely in CircleCI web UI. Finally, you will see how to automatically detect slow spec files and divide their test examples between parallel jobs to eliminate the bottleneck job that’s taking too much time to run tests.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/rspec-testing-parallel-jobs-with-circleci-and-junit-xml-report/rspec_circleci.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;RSpec, CircleCI, Ruby, gem&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;ruby-gems-to-configure-your-ror-project&quot;&gt;Ruby gems to configure your RoR project&lt;/h2&gt;

&lt;p&gt;Here are the key elements you need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/sj26/rspec_junit_formatter&quot;&gt;rspec_junit_formatter&lt;/a&gt; - it’s a ruby gem that generates an XML report for executed tests with information about test failures. This report can be automatically read by CircleCI to present it in CircleCI web UI. No more browsing through long RSpec output - just look at highlighted failing specs in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TESTS&lt;/code&gt; tab :)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/rspec-testing-parallel-jobs-with-circleci-and-junit-xml-report/circleci_web_ui_failed_test.png&quot; alt=&quot;CircleCI web UI, failure, RSpec, test, test case&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=rspec-testing-parallel-jobs-with-circleci-and-junit-xml-report&quot;&gt;knapsack_pro&lt;/a&gt; - it’s a Ruby gem for running tests on parallel CI jobs to ensure all jobs finish work at a similar time to save you as much time as possible and eliminate bottlenecks.
    &lt;ul&gt;
      &lt;li&gt;It uses the &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;Queue Mode to dynamically split test files between parallel jobs&lt;/a&gt;.&lt;/li&gt;
      &lt;li&gt;Knapsack Pro can also &lt;a href=&quot;https://docs.knapsackpro.com/ruby/split-by-test-examples/&quot;&gt;detect your slow RSpec test files and divide them between parallel jobs by test examples&lt;/a&gt;. You don’t have to manually split your big spec file into smaller files if you want to split work between parallel container on CircleCI :)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just add the above gems to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;rspec&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;rspec_junit_formatter&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:development&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;knapsack_pro&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=rspec-testing-parallel-jobs-with-circleci-and-junit-xml-report&quot;&gt;Knapsack Pro you will need an API token&lt;/a&gt; and you need to follow the &lt;a href=&quot;/knapsack_pro-ruby/guide/&quot;&gt;installation guide&lt;/a&gt; to configure your project.&lt;/p&gt;

&lt;p&gt;If you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; gem in Queue Mode with CircleCI you may want to collect metadata like JUnit XML report about your RSpec test suite.&lt;/p&gt;

&lt;p&gt;It’s important to copy the XML report to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/test-results&lt;/code&gt;. Learn more about &lt;a href=&quot;https://docs.knapsackpro.com/ruby/circleci/#collect-metadata-in-queue-mode&quot;&gt;collecting metadata on CircleCI&lt;/a&gt; in the docs.&lt;/p&gt;

&lt;h2 id=&quot;circleci-yml-configuration-for-rspec&quot;&gt;CircleCI YML configuration for RSpec&lt;/h2&gt;

&lt;p&gt;Here is the complete CircleCI YML config file for RSpec, Knapsack Pro and JUnit formatter.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# Ruby CircleCI 2.0 configuration file&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Check https://circleci.com/docs/2.0/language-ruby/ for more details&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parallelism&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# https://circleci.com/docs/2.0/configuration-reference/#resource_class&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;resource_class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;small&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# specify the version you desire here&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/ruby:2.7.1-node-browsers&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;127.0.0.1&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my_db_user&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Split slow RSpec test files by test examples&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Specify service dependencies here if necessary&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# CircleCI maintains a library of pre-built images&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# documented at https://circleci.com/docs/2.0/circleci-images/&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/postgres:10.6-alpine-ram&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my_db_name&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my_db_user&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Rails verifies Time Zone in DB is the same as time zone of the Rails app&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;TZ&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Europe/Warsaw&quot;&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;redis:6.0.7&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;working_directory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;~/repo&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;TZ&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Europe/Warsaw&quot;&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Download and cache dependencies&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;restore_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v2-dependencies-bundler-{{ checksum &quot;Gemfile.lock&quot; }}-{{ checksum &quot;.ruby-version&quot; }}&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# fallback to using the latest cache if no exact match is found&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v2-dependencies-bundler-&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;install ruby dependencies&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;bundle install --jobs=4 --retry=3 --path vendor/bundle&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;save_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v2-dependencies-bundler-{{ checksum &quot;Gemfile.lock&quot; }}-{{ checksum &quot;.ruby-version&quot; }}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Database setup&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bin/rails db:prepare&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;run tests&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;mkdir -p /tmp/test-results&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;bundle exec rake &quot;knapsack_pro:queue:rspec[--format documentation --format RspecJunitFormatter --out /tmp/test-results/rspec.xml]&quot;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# collect reports&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;store_test_results&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/tmp/test-results&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;store_artifacts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/tmp/test-results&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test-results&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;You’ve just learned how to make your CircleCI builds way faster! Now your RSpec tests can be automatically run on many parallel machines to save you time. Please let us know if it was helpful or if you have any questions. Feel free to &lt;a href=&quot;https://knapsackpro.com/registrations?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=rspec-testing-parallel-jobs-with-circleci-and-junit-xml-report&quot;&gt;sign up at Knapsack Pro&lt;/a&gt; or down below and try it yourself.&lt;/p&gt;
</description>
        <pubDate>Tue, 23 Feb 2021 07:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/rspec-testing-parallel-jobs-with-circleci-and-junit-xml-report</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/rspec-testing-parallel-jobs-with-circleci-and-junit-xml-report</guid>
        
        
        <category>techtips</category>
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>How to build Knapsack Pro API client from scratch in any programming language</title>
        <description>&lt;p&gt;You will learn how to integrate with Knapsack Pro API to run parallel tests in any programming language for any testing framework. You will see what’s needed to build from scratch Knapsack Pro API client similar to our existing clients like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; Ruby gem, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt; for Jest in JavaScript, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt; for Cypress test runner (also in JavaScript).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-build-knapsack-pro-api-client-from-scratch-in-any-programming-language/api.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;API, integration, knapsack, knapsack problem, knapsack pro&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here you can find the &lt;a href=&quot;/&quot;&gt;list of existing Knapsack Pro clients&lt;/a&gt; to run your tests in parallel for programming languages like Ruby, JavaScript, and their testing frameworks.&lt;/p&gt;

&lt;h2 id=&quot;introduction---learn-basics&quot;&gt;Introduction - learn basics&lt;/h2&gt;

&lt;p&gt;First, you need to understand what &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-build-knapsack-pro-api-client-from-scratch-in-any-programming-language&quot;&gt;Knapsack Pro&lt;/a&gt; does and how it splits test files in parallel CI nodes to run your CI build fast.&lt;/p&gt;

&lt;p&gt;Learn &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;what Regular Mode and Queue Mode are&lt;/a&gt; in Knapsack Pro and how they work.&lt;/p&gt;

&lt;p&gt;Please see below the dictionary of terms we will use in this article:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Knapsack Pro API&lt;/strong&gt; - it’s an API responsible for deciding how to split test files between parallel CI nodes. Your API client is going to send recorded time execution of your test files to the API to see results in the &lt;a href=&quot;https://knapsackpro.com/sessions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-build-knapsack-pro-api-client-from-scratch-in-any-programming-language&quot;&gt;Knapsack Pro user dashboard&lt;/a&gt;. Knapsack Pro API will use the data to better predict how to split your test files in future CI build runs. Here is the &lt;a href=&quot;/api/&quot;&gt;documentation for all API endpoints&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Knapsack Pro client&lt;/strong&gt; - is a library that you install in your project. It contains business logic responsible for connecting with Knapsack Pro API. The client knows how to read environment variables for various CI providers to automatically detect git commit hash, branch name, number of total parallel CI nodes, and CI node index. Knapsack Pro client connects with the Knapsack Pro API to fetch a list of test files to run a proper set of tests on a given parallel CI node. Knapsack Pro client also knows how to integrate with a test runner in a given programming language. For instance, the Knapsack Pro client in Ruby programming language is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; ruby gem. It knows how to run tests for test runners like RSpec, Cucumber, Minitest, etc. Simply speaking, Knapsack Pro client is a wrapper around test runner (testing framework) in a given programing language. Here is a &lt;a href=&quot;/&quot;&gt;list of existing Knapsack Pro clients&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Test runner (testing framework)&lt;/strong&gt; - each programming language has its own testing framework. For instance, in Ruby programming language there are test runners like RSpec, Cucumber, Minitest. In JavaScript, you can find Jest, Puppeteer, Karma, Jasmine, Cypress, TestCafe, etc. In Python, there are pytest, unittest.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Knapsack Pro Regular Mode&lt;/strong&gt; - it’s a static split of tests between parallel CI nodes (performed deterministically). Basically, before starting tests we know up front what set of test files should be run on each parallel CI node.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Knapsack Pro Queue Mode&lt;/strong&gt; - it’s a dynamic way of splitting tests between parallel CI nodes. In this case, each parallel CI node asks Knapsack Pro API for a set of tests and runs it. Once completed, it asks for another set of tests. It’s repeated until all tests are executed and the Knapsack Pro API has no more test files in the Queue. Please read the article about the &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;difference between Regular Mode and Queue Mode&lt;/a&gt; to learn about it in detail and see some graphs showing the difference.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you know a few useful terms. Before we start learning how to build a Knapsack Pro client from scratch in your favorite programming language, let’s check how such a client looks like in JavaScript. In order to integrate with Knapsack Pro API in JavaScript, we created an NPM package called &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt;. This package knows how to communicate with Knapsack Pro API and how to read environment variables for various CI providers.&lt;/p&gt;

&lt;p&gt;As you probably know, there are many testing frameworks written in JavaScript, e.g. Jest, Cypress, etc. For the Jest testing framework we created another package &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt;&lt;/a&gt; for the Jest testing framework that uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt; NPM package contains business logic responsible for integration with the Jest library so you can run Jest tests in parallel using Knapsack Pro API.&lt;/p&gt;

&lt;p&gt;Before you start building your own Knapsack Pro client in your programming language I highly recommend reading the article where we covered &lt;a href=&quot;/2020/how-to-build-native-integration-with-knapsack-pro-api-to-run-tests-in-parallel-for-any-test-runner-testing-framework&quot;&gt;how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt; work&lt;/a&gt;.
Those are lightweight NPM packages and the source code is easy to understand. You can get some ideas about code organization and technical requirements for building a Knapsack Pro client from scratch.&lt;/p&gt;

&lt;h2 id=&quot;how-to-build-knapsack-pro-client&quot;&gt;How to build Knapsack Pro client&lt;/h2&gt;

&lt;p&gt;You will see how to build a Knapsack Pro client from scratch based on the JavaScript example. Knapsack Pro client is built as 2 packages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Knapsack Pro Core&lt;/strong&gt; - (i.e. &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt;) which is responsible for:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;connecting with the Knapsack Pro API. It can respond to, and handle common response types and errors coming from the API.&lt;/li&gt;
      &lt;li&gt;reading environment variables specific to Knapsack Pro client like API token, log level, API endpoint URL, etc.&lt;/li&gt;
      &lt;li&gt;CI providers environment variables integration - Knapsack Pro Core library can read environment variables for popular CI providers. Thanks to that it can automatically detect git commit hash, branch name, number of parallel CI nodes, etc.&lt;/li&gt;
      &lt;li&gt;Logger - it can log useful tips for the output or warnings.&lt;/li&gt;
      &lt;li&gt;Fallback Mode - it knows how to run tests in parallel when there is a network issue and the connection with Knapsack Pro API is not working.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Knapsack Pro Test Runner&lt;/strong&gt; - (i.e. &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt;&lt;/a&gt;) is responsible for:&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;integration of Knapsack Pro Core with your test runner (testing framework) like Jest, etc.&lt;/li&gt;
      &lt;li&gt;knowing how to run tests for a given test runner, how to record time execution, and report it back to Knapsack Pro Core so the recorded test files can be saved on the Knapsack Pro API side.&lt;/li&gt;
      &lt;li&gt;reading environment variables specific for the test runner, for instance how to detect a list of Jest test files residing on the disk.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;knapsack-pro-core&quot;&gt;Knapsack Pro Core&lt;/h2&gt;

&lt;p&gt;The core functionality of the Knapsack Pro client is in the Core package (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;). We will review a few main elements and describe how they work.&lt;/p&gt;

&lt;h3 id=&quot;environment-variables-integration&quot;&gt;Environment variables integration&lt;/h3&gt;

&lt;p&gt;Knapsack Pro Core client should understand a few environment variables. See example for &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/master/src/config/knapsack-pro-env.config.ts&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; environment variables&lt;/a&gt;.
Users can define those environment variables in their CI server settings to control the behavior of the Knapsack Pro client.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_LOG_LEVEL&lt;/code&gt; - it determines how much debugging info should be produced by the Knapsack Pro client to the output during the runtime of tests. The default is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt;. If you set the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debug&lt;/code&gt; value then the Knapsack Pro client should show in the output a payload of requests and responses from the Knapsack Pro API.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_ENDPOINT&lt;/code&gt; - it’s the URL of the Knapsack Pro API. The default value is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://api.knapsackpro.com&lt;/code&gt; which is production API. You can use our production API and your API token from the user dashboard for testing purposes.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN&lt;/code&gt; - it’s an API token that you can use to connect with Knapsack Pro API. If the value is not defined then an error should be raised.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/code&gt; - it’s a flag to control the behavior of Queue Mode. The default value is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;If the value is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; then the API will cache the way test files were split between parallel CI nodes. So when you retry the CI build the tests won’t be dynamically split. Instead, they will be split in the same order as during the very first run (which was a dynamic tests split).&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Do you want to use “retry single failed parallel CI node” feature for your CI? For instance, some of CI providers like Travis CI, Buildkite or Codeship allow you to retry only one of failed parallel CI nodes instead of retrying the whole CI build with all parallel CI nodes. If you want to be able to retry only a single failed parallel CI node then you need to tell Knapsack Pro API to remember the way test files were allocated across parallel CI nodes by adding to your CI environment variables &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT=true&lt;/code&gt;.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;The default is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT=false&lt;/code&gt; which means that when you want to retry the whole failed CI build then a new dynamic test suite split will happen across all retried parallel CI nodes. Some people may prefer to retry the whole failed CI build with test files allocated across parallel CI nodes in the same order as it happened for the failed CI build - in such a case you should set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT=true&lt;/code&gt;.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;To learn more about this flag you can also see &lt;a href=&quot;https://docs.knapsackpro.com/ruby/queue-mode/#dynamic-split-vs-fixed-split&quot;&gt;examples in knapsack_pro ruby gem related to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT=true&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/code&gt; - the default value conveying the number of parallel CI nodes used.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/code&gt; has a value then it should be used.&lt;/li&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/code&gt; has no value then Knapsack Pro client should read CI provider environment variables to determine CI node total number.&lt;/li&gt;
      &lt;li&gt;If no value is detected then an error should be raised. Please see the &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/config/knapsack-pro-env.config.ts#L47,L61&quot;&gt;source code of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt; - it is the index of the parallel CI node (parallel job). It should start from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL - 1&lt;/code&gt;. If you use 2 parallel CI nodes in total then indexes should be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt; has a value then it should be used.&lt;/li&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt; has no value then Knapsack Pro client should read CI provider environment variables to determine CI node index.&lt;/li&gt;
      &lt;li&gt;If no value is detected then an error should be raised. Please see the &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/config/knapsack-pro-env.config.ts#L63,L76&quot;&gt;source code of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_BUILD_ID&lt;/code&gt; - a user of your Knapsack Pro client can define CI build ID with this environment variable. For instance, if the user uses Jenkins as a CI provider then Jenkins has no autogenerated CI build ID out of the box. In such a case, the user should create a custom value (unique for every CI build) and assign it to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_BUILD_ID&lt;/code&gt; environment variable.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;CI build has many parallel CI nodes. Each parallel CI node should have the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_BUILD_ID&lt;/code&gt; value. This means the parallel CI nodes belong to the same CI build.&lt;/li&gt;
      &lt;li&gt;Knapsack Pro client by default should try to detect CI build ID for popular CI providers by looking for it in the environment variables.&lt;/li&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_BUILD_ID&lt;/code&gt; value was defined by the user then it should be used during a request to the Knapsack Pro API. It has &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/config/knapsack-pro-env.config.ts#L79,L81&quot;&gt;higher priority&lt;/a&gt; than &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/config/knapsack-pro-env.config.ts#L83,L86&quot;&gt;detected CI build ID from a CI provider&lt;/a&gt; environment variables.&lt;/li&gt;
      &lt;li&gt;If the user did not define &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_BUILD_ID&lt;/code&gt; then a default value &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;missing-build-id&lt;/code&gt; should be used.
        &lt;ul&gt;
          &lt;li&gt;Knapsack Pro API understands &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;missing-build-id&lt;/code&gt; string and knows the CI build has an undefined CI build ID then. In such a case only one parallel CI build can be run at a time for a given set of values (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git commit hash&lt;/code&gt; AND &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;branch name&lt;/code&gt; AND &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;number of parallel CI nodes&lt;/code&gt;) - otherwise, tests could be accidentally split between 2 CI builds.
            &lt;ul&gt;
              &lt;li&gt;Why this set of values matter? From the Knapsack Pro API perspective, a unique CI build is a set of test files that belongs to a git commit hash, branch name and it is split across a certain number of parallel CI nodes. When the user will run a few CI builds at the same time for the same git commit, branch name and on the same number of parallel CI nodes then we need a way to distinguish CI builds from each other. That’s why CI build ID is useful and recommended to be pass in request to Knapsack Pro API.&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;It might be easier to understand this logic, just &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/config/knapsack-pro-env.config.ts#L78&quot;&gt;check the source code of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_COMMIT_HASH&lt;/code&gt; - it’s a commit hash.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_COMMIT_HASH&lt;/code&gt; has a value then it should be used.&lt;/li&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_COMMIT_HASH&lt;/code&gt; has no value then Knapsack Pro client should read CI provider environment variables to determine git commit hash.&lt;/li&gt;
      &lt;li&gt;If no value is detected then a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rev-parse HEAD&lt;/code&gt; command should be run to determine the commit hash.&lt;/li&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt; is not installed then raise an error. Please see the &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/config/knapsack-pro-env.config.ts#L108,L145&quot;&gt;source code of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_BRANCH&lt;/code&gt; - it’s a branch name.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_BRANCH&lt;/code&gt; has a value then it should be used.&lt;/li&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_BRANCH&lt;/code&gt; has no value then Knapsack Pro client should read CI provider environment variables to determine a branch name.&lt;/li&gt;
      &lt;li&gt;If no value is detected then a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rev-parse --abbrev-ref HEAD&lt;/code&gt; command should be run to determine the branch name.&lt;/li&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt; is not installed then raise an error. Please see the &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/config/knapsack-pro-env.config.ts#L147,L184&quot;&gt;source code of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_USER_SEAT&lt;/code&gt; - A user name that started the CI build. It is usually the same person that made the git commit. It must be masked with a regexp pattern &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/(?&amp;lt;=\w{2})[a-zA-Z]/g&lt;/code&gt;. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;John Doe &amp;lt;john.doe@example.com&amp;gt;&lt;/code&gt; should be masked to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Jo** Do* &amp;lt;jo**.do*@ex*****.co*&amp;gt;&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;CI providers environment variables integration - Knapsack Pro client should try to read environment variables for popular CI providers. Thanks to that user have to do less work to set up the Knapsack Pro client with his project.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Here can be found a &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/master/src/config/ci-env.config.ts&quot;&gt;list of supported CI providers&lt;/a&gt;.&lt;/li&gt;
      &lt;li&gt;Here is a list of &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/tree/master/src/ci-providers&quot;&gt;environment variables for each CI provider&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;fallback-mode&quot;&gt;Fallback Mode&lt;/h3&gt;

&lt;p&gt;Knapsack Pro Core should have implemented business logic for running tests in Fallback Mode. When Knapsack Pro API is not reachable because of downtime then tests should be run in Fallback Mode without the need to use the API.&lt;/p&gt;

&lt;p&gt;How Fallback Mode works? The service responsible for Fallback Mode should take a list of test files and the number of total parallel CI nodes. You can sort the test files and divide them by the total parallel CI nodes number.&lt;/p&gt;

&lt;p&gt;It’s also possible that during tests runtime in Queue Mode the connection with Knapsack Pro API will be lost. This could mean that some of the test files were already executed based on the set of test files fetched from Queue API and then the connection was lost. In such a case Fallback Mode should exclude test files that were already executed.
In Queue Mode the Fallback Mode guarantees each of the test files is run at least once across parallel CI nodes to make sure we never skip a test file.&lt;/p&gt;

&lt;p&gt;Here you can see the source code of &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/master/src/fallback-test-distributor.ts&quot;&gt;Fallback Test Distributor&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;logger&quot;&gt;Logger&lt;/h3&gt;

&lt;p&gt;Knapsack Pro Core should have a logger with a default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt; log level. A user should be able to control log level with the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_LOG_LEVEL&lt;/code&gt;. You use the logger to produce useful tips to the output during tests runtime:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;info when Fallback Mode was started&lt;/li&gt;
  &lt;li&gt;when log level is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debug&lt;/code&gt; then show request payload&lt;/li&gt;
  &lt;li&gt;when log level is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debug&lt;/code&gt; then show response body&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/master/src/knapsack-pro-logger.ts&quot;&gt;service for the logger&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;knapsack-pro-api-integration&quot;&gt;Knapsack Pro API integration&lt;/h3&gt;

&lt;p&gt;Knapsack Pro Core should have implemented &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/master/src/knapsack-pro-api.ts&quot;&gt;business logic for making requests to Knapsack Pro API&lt;/a&gt;. There are a few basic elements you need to cover:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Send headers with the client name and client version in each request to the Knapsack Pro API. You should add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK-PRO-CLIENT-NAME&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK-PRO-CLIENT-VERSION&lt;/code&gt; &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-js/blob/8139035bfd58b3355672a67c692551a1a326d748/packages/core/src/knapsack-pro-api.ts#L25,L26&quot;&gt;headers in each request&lt;/a&gt;. Note that the Knapsack Pro Core (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;) is just a core library, so it means the actual client name and version should be defined in the Knapsack Pro Test Runner client (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt;) and provided as an &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest/blob/e6eca4868df9379ce17fe5df865302b11434803c/src/knapsack-pro-jest.ts#L30,L31&quot;&gt;argument to the Knapsack Pro Core&lt;/a&gt;. Please use &lt;a href=&quot;https://semver.org/&quot;&gt;semantic versioning&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Send the CI provider name as a header in each request to the Knapsack Pro API with the name &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-js/blob/8139035bfd58b3355672a67c692551a1a326d748/packages/core/src/knapsack-pro-api.ts#L27&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK-PRO-CI-PROVIDER&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When a request to the Knapsack Pro API fails then it should be repeated 3 times.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;There are exceptions when a &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/knapsack-pro-api.ts#L68,L70&quot;&gt;response status indicates a failure&lt;/a&gt; - in these cases the request should never be repeated:
        &lt;ul&gt;
          &lt;li&gt;When response status is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;400&lt;/code&gt; then it means request attributes error.&lt;/li&gt;
          &lt;li&gt;When response status is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;422&lt;/code&gt; then it means validation error.&lt;/li&gt;
          &lt;li&gt;When the response status is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;403&lt;/code&gt; then a free trial period ended.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;For all above &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4xx&lt;/code&gt; response statuses you should show the error body response to the output and stop running tests. Ensure that the &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/knapsack-pro-core.ts#L77&quot;&gt;process has exit code &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;&lt;/a&gt; - thanks to that CI provider will know the CI build failed.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When the Knapsack Pro API returns different response status than listed above. For instance when you get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;500&lt;/code&gt; status then you should repeat the request 3 times. If the 3rd response has a non-&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2xx&lt;/code&gt; status as well, then you should &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/knapsack-pro-core.ts#L83,L110&quot;&gt;run test files in Fallback Mode&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Ensure you set max request &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/knapsack-pro-api.ts#L80&quot;&gt;timeout to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;15&lt;/code&gt; seconds&lt;/a&gt;. When Knapsack Pro API won’t send a response within 15 seconds then it’s better to cancel the request and &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/knapsack-pro-api.ts#L188,L199&quot;&gt;wait some time before repeating the request&lt;/a&gt;. You can wait 8 seconds, and increase by another 8 seconds each consequent request that must be repeated (e.g. wait for 8s, then 16s, then 24s).&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;knapsack-pro-api---queue-mode&quot;&gt;Knapsack Pro API - Queue Mode&lt;/h4&gt;

&lt;p&gt;Knapsack Pro Core should contain logic for making requests to Knapsack Pro API for Queue Mode. Here is described the &lt;a href=&quot;/api/v1/#queues_queue_post&quot;&gt;Queue Mode API endpoint&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please read the API documentation. Especially an example of &lt;a href=&quot;/api/v1/#queues_queue_post&quot;&gt;the request body&lt;/a&gt;. There are 3 types of requests to ensure we can connect with the Queue on the API side in a fast way by sending a request payload as small as possible.&lt;/p&gt;

&lt;p&gt;I’ll describe below an example covering all 3 types of request payloads.
There are different types of payloads because the Knapsack Pro client runs at the same time on parallel CI nodes and we don’t know which one will be connected with the Knapsack Pro API first. We need to deal with the parallel request problem. For instance, the very first request to Knapsack Pro Queue API should initialize a new Queue with the test files on the API side. But we don’t know which parallel CI nodes will connect to the API first. There is mutex protection on the API side to detect the very first request but we also need to take care of things on the Knapsack Pro Core side.&lt;/p&gt;

&lt;p&gt;Let’s start with a simple example. You have 2 parallel CI nodes. The first CI node has node index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;. The second parallel CI node has index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;. Note that the convention is to start index number from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N-1&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N&lt;/code&gt; is a total number of parallel CI nodes).&lt;/p&gt;

&lt;p&gt;Let’s assume that only the first parallel CI node (CI node index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;) sends requests to the Knapsack Pro API because the CI machine for the first CI node started work earlier than the second CI node.&lt;/p&gt;

&lt;p&gt;The first CI node sends the below request. Its purpose is to attempt to connect to the existing Queue on the API side.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;// 1st type of request to Queue API should set attributes:
// can_initialize_queue: true AND attempt_connect_to_queue: true
// Note that there is no test_files parameter in the payload to make the request fast and keep the payload small.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;can_initialize_queue&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;kc&quot;&gt;true&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;attempt_connect_to_queue&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;kc&quot;&gt;true&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;fixed_queue_split&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;kc&quot;&gt;false&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;commit_hash&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;6e3396177d9f8ca87e2b93b4b0a25babd09d574d&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;branch&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;main&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;node_total&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;2&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;node_index&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;0&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;node_build_id&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;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;nl&quot;&gt;&quot;user_seat&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;Jo** Do* &amp;lt;jo**.do*@ex*****.co*&amp;gt;&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;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The above request was the very first request sent to the API and on the API side the Queue does not exist yet. It means the API response returns an error informing us about the Queue not existing.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;// 1st type of response to the 1st type of request (when can_initialize_queue: true AND attempt_connect_to_queue: true)
// It can happen only when the queue does not exist on the API side or cannot be read from the cache on the API side&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;queue_name&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;1:6baacadcdd493c1a6024ee7e51f018f5&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;A queue with a list of test files does not exist on the API side yet. If you see this message, everything works as expected. Now Knapsack Pro client will initialize a queue on the API side with a list of test files you want to run. The request to initialize the queue will have attributes like can_initialize_queue=true, attempt_connect_to_queue=false, and test_files, etc.&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;code&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;ATTEMPT_CONNECT_TO_QUEUE_FAILED&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;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You need to make a second request. This time, it should contain a list of test files residing on the disk. These will be used to create the Queue.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;// 2nd type of request to Queue API should happen only if the API response for 1st type of request has:
// &quot;code&quot;: &quot;ATTEMPT_CONNECT_TO_QUEUE_FAILED&quot;
// it means an attempt to connect to the queue failed because the queue does not exist on the API side yet.
// You must initialize a new queue with the below request.
// It should set attributes:
// can_initialize_queue: true AND attempt_connect_to_queue: false
// Note that there is a test_files attribute in the payload to initialize a queue based on the list of test_files from your disk.
// This request can be slow if you provide a large number of test files (~1000+).
// That is why we did 1st request to try to connect to the existing queue first (as one of the other parallel CI nodes could have already initialized it).&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;can_initialize_queue&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;kc&quot;&gt;true&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;attempt_connect_to_queue&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;kc&quot;&gt;false&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;fixed_queue_split&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;kc&quot;&gt;false&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;commit_hash&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;6e3396177d9f8ca87e2b93b4b0a25babd09d574d&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;branch&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;main&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;node_total&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;2&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;node_index&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;0&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;node_build_id&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;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;nl&quot;&gt;&quot;user_seat&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;Jo** Do* &amp;lt;jo**.do*@ex*****.co*&amp;gt;&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;test_files&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;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;path&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;test/fast/a_test.rb&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;nl&quot;&gt;&quot;path&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;test/fast/b_test.rb&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;nl&quot;&gt;&quot;path&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;test/slow/c_test.rb&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;nl&quot;&gt;&quot;path&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;test/slow/d_test.rb&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;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;API should return a set of test files assigned to the first CI node (CI node index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;). You should run the test files with your test runner now (using the Knapsack Pro Test Runner client - we will describe it later).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;// 2nd type of response can happen for all types of request
// It returns a list of test files that should be run with your test runner&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;queue_name&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;1:6baacadcdd493c1a6024ee7e51f018f5&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;build_subset_id&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;kc&quot;&gt;null&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;test_files&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;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;path&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;test/slow/d_test.rb&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;time_execution&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;mf&quot;&gt;3.14&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;nl&quot;&gt;&quot;path&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;test/fast/b_test.rb&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;time_execution&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;kc&quot;&gt;null&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;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After you execute the test files, you should ask the API for another set of test files until the API response contains an empty list of test files. This signifies that the whole Queue has been consumed.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;// 3rd type of request to Queue API should happen only if 1st or 2nd type of request returned a list of test_files.
// With the below request you can continue fetching test files from the queue to run them with your test runner.
// Request payload should have attributes:
// can_initialize_queue: false AND attempt_connect_to_queue: false
// Note there is no test_files attribute in the payload to make the request fast and keep the payload small.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;can_initialize_queue&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;kc&quot;&gt;false&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;attempt_connect_to_queue&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;kc&quot;&gt;false&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;fixed_queue_split&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;kc&quot;&gt;false&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;commit_hash&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;6e3396177d9f8ca87e2b93b4b0a25babd09d574d&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;branch&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;main&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;node_total&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;2&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;node_index&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;0&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;node_build_id&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;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;nl&quot;&gt;&quot;user_seat&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;Jo** Do* &amp;lt;jo**.do*@ex*****.co*&amp;gt;&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;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When the API response has no test files it means the Queue was consumed and all test files were executed.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;queue_name&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;1:6baacadcdd493c1a6024ee7e51f018f5&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;build_subset_id&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;kc&quot;&gt;null&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;test_files&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;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After test files have been run and their execution time has been recorded you can send the test files timing data to the Knapsack Pro API. You need to &lt;a href=&quot;/api/v1/#build_subsets_post&quot;&gt;create a build subset record&lt;/a&gt; on the API side.&lt;/p&gt;

&lt;p&gt;You can consult the &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/master/src/knapsack-pro-api.ts&quot;&gt;source code of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; responsible for making requests to Knapsack Pro API&lt;/a&gt; for Queue Mode and for a request to create a build subset.&lt;/p&gt;

&lt;p&gt;Please also see how Knapsack Pro Core (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;) uses a &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/master/src/knapsack-pro-api.ts&quot;&gt;service for the API&lt;/a&gt; to &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/master/src/knapsack-pro-core.ts&quot;&gt;run tests, record tests execution time, and save recorded test files time for a given CI node as a build subset&lt;/a&gt; in the API.&lt;/p&gt;

&lt;h2 id=&quot;knapsack-pro-test-runner-integration&quot;&gt;Knapsack Pro Test Runner integration&lt;/h2&gt;

&lt;p&gt;In this section, you will learn what’s need to be covered in the Knapsack Pro Test Runner source code (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;You need to &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest/blob/master/src/env-config.ts&quot;&gt;recognize environment variables&lt;/a&gt; defined by user of Knapsack Pro client. The user define them in CI environment variables settings.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST&lt;/code&gt; - the value of it should override the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN&lt;/code&gt; so that Knapsack Pro Core (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;) can use the API token during requests.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_FILE_PATTERN&lt;/code&gt; - it should contain a default test file pattern that can be used to detect test files on the disk in a directory specific to your test runner. We use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glob&lt;/code&gt; function to detect test files on the disk.&lt;/li&gt;
  &lt;li&gt;There should be a &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest/blob/master/src/test-files-finder.ts&quot;&gt;test file finder service&lt;/a&gt; that can recognize the pattern and find the list of test files on the disk. We use this list of test files to send them in request to the API so that the API server can split those test files into parallel CI nodes.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN&lt;/code&gt; - is an exclude pattern. If a user wants to ignore some of the test files she can provide a pattern for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Knapsack Pro Test Runner library (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt;) should have their &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest/blob/e6eca4868df9379ce17fe5df865302b11434803c/src/knapsack-pro-jest.ts#L29,L31&quot;&gt;name and version and it should be passed to Knapsack Pro Core&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;) when you will use &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/master/src/knapsack-pro-core.ts&quot;&gt;core functionality&lt;/a&gt; to connect with the API (for instance to run tests in Queue Mode).&lt;/p&gt;

&lt;p&gt;Please note that Knapsack Pro Test Runner should &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest/blob/e6eca4868df9379ce17fe5df865302b11434803c/src/knapsack-pro-jest.ts#L68,L76&quot;&gt;track recorded test files time execution in seconds&lt;/a&gt; and pass it back to Knapsack Pro Core. It should also pass info whether &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest/blob/e6eca4868df9379ce17fe5df865302b11434803c/src/knapsack-pro-jest.ts#L82&quot;&gt;tests are green or red&lt;/a&gt; (failing). Thanks to that Knapsack Pro Core will &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js/blob/0f44c6a3daa369cd4353e315abbf5539295289ea/src/knapsack-pro-core.ts#L124&quot;&gt;set proper process exit status&lt;/a&gt;. When at least 1 test fails then the process exit status should be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; so the CI provider will mark your CI build as a failed one.&lt;/p&gt;

&lt;h2 id=&quot;testing-your-knapsack-pro-client&quot;&gt;Testing your Knapsack Pro client&lt;/h2&gt;

&lt;p&gt;For testing your Knapsack Pro client I recommend creating a new project with tests in your testing framework (test runner). Here is an &lt;a href=&quot;https://github.com/KnapsackPro/jest-example-test-suite#jest-example-test-suite-and-knapsackprocom&quot;&gt;example project with Jest tests&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can create a &lt;a href=&quot;https://github.com/KnapsackPro/jest-example-test-suite/blob/master/bin/knapsack_pro_jest&quot;&gt;bin script that runs tests&lt;/a&gt; for a given CI node index using the Knapsack Pro client.&lt;/p&gt;

&lt;p&gt;For instance use:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/knapsack_pro_jest 0 2&lt;/code&gt; - run tests on CI node index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;. The total number of CI nodes is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/knapsack_pro_jest 1 2&lt;/code&gt; - run tests on CI node index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;readme&quot;&gt;README&lt;/h2&gt;

&lt;p&gt;It’s good to create a well-documented README for your packages. You can get inspired by checking documentation for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js#knapsack-procore&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; readme&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest#knapsack-projest&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt; readme&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;extras---regular-mode-integration&quot;&gt;Extras - Regular Mode integration&lt;/h2&gt;

&lt;p&gt;If you would like to build the Knapsack Pro client that uses Regular Mode instead of Queue Mode you need to &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest/blob/e6eca4868df9379ce17fe5df865302b11434803c/src/knapsack-pro-jest.ts#L89&quot;&gt;replace the step with using Queue Mode&lt;/a&gt; and just use &lt;a href=&quot;/api/v1/#build_distributions_subset_post&quot;&gt;Regular Mode API&lt;/a&gt; instead. It’s much simpler than Queue API.&lt;/p&gt;

&lt;p&gt;In Regular Mode, you need to send a list of existing test files on the disk to the API. The API returns a set of test files to run. Once you execute the tests you need to &lt;a href=&quot;/api/v1/#build_subsets_post&quot;&gt;create a build subset&lt;/a&gt; record in the API.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_TEST_SUITE_SPLIT&lt;/code&gt; - Regular Mode has a flag to control whether tests split should be cached on the API side. It’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; by default. Learn more about it from &lt;a href=&quot;https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_test_suite_split-test-suite-split-based-on-seed&quot;&gt;knapsack_pro ruby gem documentation&lt;/a&gt;.
    &lt;ul&gt;
      &lt;li&gt;Value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_TEST_SUITE_SPLIT&lt;/code&gt; should be sent as attribute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fixed_test_suite_split&lt;/code&gt; in &lt;a href=&quot;http://localhost:4000/api/v1/#build_distributions_subset_post&quot;&gt;request to the API&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;We covered how to build Knapsack Pro client integration from scratch based on the example of the existing JavaScript/TypeScript client built from 2 NPM packages &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I hope you find it useful. I recommend digging into the source code of the above packages. They are lightweight and should be easy to understand. You can replicate their behavior to build your integration with Knapsack Pro API for your favorite programming language and your test runner (testing framework).&lt;/p&gt;
</description>
        <pubDate>Thu, 18 Feb 2021 07:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/how-to-build-knapsack-pro-api-client-from-scratch-in-any-programming-language</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/how-to-build-knapsack-pro-api-client-from-scratch-in-any-programming-language</guid>
        
        
        <category>techtips</category>
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Fix intermittently failing CI builds due to flaky tests in RSpec</title>
        <description>&lt;p&gt;Randomly failing CI builds can be frustrating. One run is green, another is red (with no changes in the code!). Disheartening. Let’s see what can be done with this.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/fix-intermittently-failing-ci-builds-flaky-tests-rspec/flaky_tests.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Knapsack Pro API&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;random-failures&quot;&gt;Random failures&lt;/h2&gt;

&lt;p&gt;When builds are randomly failing, flaky tests are often the culprit.&lt;/p&gt;

&lt;p&gt;A flaky test is a test that can both fail and pass for the exact same code. In other (and more fancy) words, the test is &lt;em&gt;nondeterministic&lt;/em&gt;. Its outcome varies, even if (seemingly) nothing else does.&lt;/p&gt;

&lt;h2 id=&quot;trust-issues&quot;&gt;Trust issues&lt;/h2&gt;

&lt;p&gt;Nondeterministic tests erode trust. Keeping them in the mix can quickly cause the whole test suite to lose its value. Think about it. The failed build is useful when the failure actually signifies something. It needs to make us stop. To signal that something needs fixing. The more it happens for trivial reasons, the less alert we become to it.&lt;/p&gt;

&lt;p&gt;It’s better to not run the test case at all if it can’t be trusted to provide useful feedback.&lt;/p&gt;

&lt;h2 id=&quot;first-things-first&quot;&gt;First things first&lt;/h2&gt;

&lt;p&gt;When you encounter this problem, think if you need a given test at all. If it wasn’t there already, would you write it? Ask other team members for their opinion.&lt;/p&gt;

&lt;p&gt;If the test is deemed important, the correct (and safest) solution would be to debug and solve the underlying issues right away.&lt;/p&gt;

&lt;p&gt;Not possible at the moment? At least make sure that it’s the &lt;em&gt;failure&lt;/em&gt; of the test which is random, and not its &lt;em&gt;passing&lt;/em&gt;. Then I strongly suggest documenting the issue, creating a ticket, or planning to solve it in some other way.&lt;/p&gt;

&lt;p&gt;Raise the issue inside your team. If they care about quality assurance and continuous delivery (or their own &lt;em&gt;sanity&lt;/em&gt;, really), they will understand.&lt;/p&gt;

&lt;p&gt;When this is taken care of, we have a few short-term options.&lt;/p&gt;

&lt;h2 id=&quot;rerunning-failed-builds&quot;&gt;Rerunning failed builds&lt;/h2&gt;

&lt;p&gt;When the failure of a CI build is attributed to randomness, the natural reaction is to retry the build. If the rerun build passes, it would be best to still document what happened.&lt;/p&gt;

&lt;p&gt;Some CI providers let you configure auto-retrying failed builds. It might be tempting to “solve” randomness this way. I advise against using it (for flaky tests anyway).&lt;/p&gt;

&lt;p&gt;Here at Knapsack we are all about &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=fix-intermittently-failing-ci-builds-flaky-tests-rspec&quot;&gt;decreasing build times&lt;/a&gt;. We strongly believe that the shorter the build times, the more productive the team becomes. Frequently rerunning the whole build results in just the opposite. It wastes a lot of time.&lt;/p&gt;

&lt;h2 id=&quot;if-you-dont-trust-it-x-it&quot;&gt;If you don’t trust it, x it!&lt;/h2&gt;

&lt;p&gt;In RSpec, we can change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; method (denoting a test example) to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xit&lt;/code&gt; to skip a given test case [1]. By default, RSpec’s output shows how many tests were skipped. This way, they stay visible. This increases the chance that they will be fixed. That’s what makes this solution advantageous over just commenting the problematic code out.&lt;/p&gt;

&lt;h2 id=&quot;rerunning-failed-test-case&quot;&gt;Rerunning failed test case&lt;/h2&gt;

&lt;p&gt;Some flaky tests are caused by inadvertent order dependence or timing issues. Rerunning the individual example could result in a pass. &lt;a href=&quot;https://github.com/NoRedInk/rspec-retry&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rspec-retry&lt;/code&gt;&lt;/a&gt; is a library for RSpec that allows doing just that. With it, you can configure which tests should be retried on failure, and how many times. It’s also possible to specify wait time before subsequent retries.&lt;/p&gt;

&lt;p&gt;Retrying on a test case level limits the amount of time wasted. If you use &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation#dynamic-tests-split---queue-mode&quot;&gt;Knapsack Pro in the Queue Mode&lt;/a&gt;, the waste should be negligible.&lt;/p&gt;

&lt;p&gt;If you don’t use RSpec, just search for a similar solution for your test runner. It’s likely to be built-in or provided as an extension by an additional library.&lt;/p&gt;

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

&lt;p&gt;By understanding the consequences of each solution, we are well-equipped to tackle flaky tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does your team respond to random failures?&lt;/strong&gt; Please share in the comments below!&lt;/p&gt;

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

&lt;hr /&gt;
&lt;p&gt;[1] The ‘x’ prefix works with other methods as well. Please consult the &lt;a href=&quot;https://rspec.info/features/3-13/rspec-core/pending-and-skipped-examples/&quot;&gt;RSpec docs&lt;/a&gt; for details.&lt;/p&gt;
</description>
        <pubDate>Mon, 15 Feb 2021 08:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2021/fix-intermittently-failing-ci-builds-flaky-tests-rspec</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2021/fix-intermittently-failing-ci-builds-flaky-tests-rspec</guid>
        
        
        <category>continuous_integration</category>
        
        <category>techtips</category>
        
        <category>rspec</category>
        
      </item>
    
      <item>
        <title>How to merge SimpleCov results with parallel Rails specs on Semaphore CI</title>
        <description>&lt;p&gt;If you are running &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;Rails specs on parallel machines with Knapsack Pro&lt;/a&gt;, one challenge you will run into is combining the code coverage results generated by SimpleCov.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-merge-simplecov-results-with-parallel-rails-specs/logo.png&quot; style=&quot;width:150px;margin-right: 15px;float:left;&quot; alt=&quot;SimpleCov, SemaphoreCI, Knapsack Pro&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
This post will show you how to generate a report of the total combined code coverage after all the tests have executed. Here’s what the pipeline diagram looks like at a high level with unrelated sections blurred out:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-merge-simplecov-results-with-parallel-rails-specs/pipeline_diagram.png&quot; alt=&quot;Pipeline Diagram&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;configure&quot;&gt;&lt;strong&gt;Configure&lt;/strong&gt;&lt;/h2&gt;

&lt;h3 id=&quot;simplecov&quot;&gt;SimpleCov&lt;/h3&gt;

&lt;p&gt;First lets look at the config file used for SimpleCov below. Note no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minimum_coverage&lt;/code&gt; configuration for failing the build. This is because each node most likely will not meet the minimum coverage threshold on its own so it could lead to the build failing erroneously.&lt;/p&gt;

&lt;p&gt;Also note, &lt;a href=&quot;https://knapsackpro.com/faq/question/what-hooks-are-supported-in-queue-mode&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before_queue&lt;/code&gt; hook for Knapsack Pro&lt;/a&gt;. This is the important piece, it will set a command name based on the CI node index so that the results are recorded against it.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.simplecov&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;SimpleCov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_filter&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;%r{^/config/}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_filter&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;%r{^/db/}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_filter&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;%r{^/spec/}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;add_group&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Admin&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;app/admin&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_group&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Controllers&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;app/controllers&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_group&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helpers&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;app/helpers&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_group&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Jobs&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;app/jobs&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_group&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Libraries&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;lib/&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_group&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Mailers&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;app/mailers&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_group&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Models&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;app/models&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_group&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Policies&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;app/policies&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;add_group&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Serializers&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;app/serializers&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eager_load!&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;KnapsackPro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hooks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;before_queue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;SimpleCov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;command_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rspec_ci_node_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;KnapsackPro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So now when SimpleCov creates a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.resultset.json&lt;/code&gt; it will have a specific key depending on which CI node it was run in like the example below. This will be useful down the line when it comes to combining the results.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;rspec_ci_node_0&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;coverage&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;err&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;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;nl&quot;&gt;&quot;rspec_ci_node_1&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;coverage&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;err&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;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;err&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;semaphore-ci&quot;&gt;Semaphore CI&lt;/h3&gt;

&lt;p&gt;Below is the relevant portions of the Semaphore CI configuration. It runs the Rails tests and then uploads the coverage results as a Semaphore workflow artifact. After all the parallel tests have completed, it will run a job to collate the coverage results from all the machines.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.semaphore/semaphore.yml&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Tests&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Rails&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;parallelism&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./.semaphore/helpers/rails_tests.sh&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;epilogue&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;always&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./.semaphore/helpers/upload_test_artifacts.sh&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker-hub&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;knapsack-pro-rails&lt;/span&gt;

&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Code&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Coverage&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Tests&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;env_vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;SEMAPHORE_RAILS_JOB_COUNT&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;10&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Collate Results&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./.semaphore/helpers/calc_code_coverage.sh&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker-hub&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Below is the bash file which executes the Rails tests on each parallel machine. It sets up the Rails environment and then runs &lt;a href=&quot;/2019/run-parallel-jobs-on-semaphore-ci-2-0-to-get-faster-ci-build-time&quot;&gt;Knapsack Pro in Queue Mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.semaphore/helpers/rails_tests.sh&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-euo&lt;/span&gt; pipefail

docker-compose &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; docker-compose.semaphore.yml &lt;span class=&quot;nt&quot;&gt;--no-ansi&lt;/span&gt; run &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;KNAPSACK_PRO_TEST_FILE_PATTERN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/**/*_spec.rb&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  ci bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bin/rake ci:setup db:create db:structure:load knapsack_pro:queue:rspec[&apos;--no-color --format progress --format RspecJunitFormatter --out tmp/rspec-junit/rspec.xml&apos;]&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you want you can also learn &lt;a href=&quot;https://knapsackpro.com/faq/question/how-to-use-junit-formatter&quot;&gt;how to use RspecJunitFormatter with KnapsackPro Queue Mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the bash file which is responsible for uploading the SimpleCov results from each machine. It compresses the coverage directory and uploads it to Semaphore.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.semaphore/helpers/upload_test_artifacts.sh&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;tmp/rspec-junit&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Pushing rspec junit results&quot;&lt;/span&gt;
  artifact push job tmp/rspec-junit &lt;span class=&quot;nt&quot;&gt;--destination&lt;/span&gt; semaphore/test-results/
&lt;span class=&quot;k&quot;&gt;fi

if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;coverage&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Pushing simplecov results&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;czf coverage_&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SEMAPHORE_JOB_INDEX&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;.tgz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; coverage &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
  artifact push workflow coverage_&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SEMAPHORE_JOB_INDEX&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;.tgz
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Lastly, this is the bash file for collating all the results from Semaphore. It will download the coverage artifacts from each parallel machine and run a rake task which will collate them and then upload the results into a combined &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;total_coverage.tgz&lt;/code&gt; file as shown below:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.semaphore/helpers/calc_code_coverage.sh&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-euo&lt;/span&gt; pipefail

&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;i &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;eval echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{1..&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SEMAPHORE_RAILS_JOB_COUNT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
  &lt;/span&gt;artifact pull workflow coverage_&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;.tgz&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;coverage_&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-xzf&lt;/span&gt; coverage_&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;.tgz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; coverage_&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;done

&lt;/span&gt;docker-compose &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; docker-compose.semaphore.yml &lt;span class=&quot;nt&quot;&gt;--no-ansi&lt;/span&gt; run ci bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bin/rake coverage:report&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;czf total_coverage.tgz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; coverage &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
artifact push workflow total_coverage.tgz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coverage:report&lt;/code&gt; rake task will simply call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SimpleCov.collate&lt;/code&gt; which will go through the coverage results in each folder and combine them into a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.resultset.json&lt;/code&gt; shown below&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib/task/coverage_report.rake&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:coverage&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Collates all result sets generated by the different test runners&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;report: :environment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;simplecov&apos;&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;SimpleCov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;collate&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;coverage_*/.resultset.json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.resultset.json&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;rspec_ci_node_0, rspec_ci_node_1, rspec_ci_node_2, rspec_ci_node_3, rspec_ci_node_4, rspec_ci_node_5, rspec_ci_node_6, rspec_ci_node_7, rspec_ci_node_8, rspec_ci_node_9&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;coverage&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;err&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;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Finally here’s what your Semaphore workflow artifacts will look like. It will have a compressed coverage file generated on each machine and a total coverage file that we created at the very end:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-merge-simplecov-results-with-parallel-rails-specs/workflow_artifacts.png&quot; alt=&quot;Workflow Artifacts&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This approach can also be easily ported over to other CI providers by simply changing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;artifact push&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;artifact pull&lt;/code&gt; commands to S3 or another CI specific artifact upload command.&lt;/p&gt;

&lt;p&gt;I hope this article was useful to you. Let me know if you have any questions or feedback. If you liked this post, please share it on social media and &lt;a href=&quot;https://twitter.com/jesalg&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;follow me on Twitter!&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 05 Nov 2020 21:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2020/how-to-merge-simplecov-results-with-parallel-rails-specs</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2020/how-to-merge-simplecov-results-with-parallel-rails-specs</guid>
        
        
        <category>techtips</category>
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>How to build native integration with Knapsack Pro API to run tests in parallel for any test runner (testing framework)</title>
        <description>&lt;p&gt;Do you know that Knapsack Pro API can work with any test runner in any programming language?&lt;/p&gt;

&lt;p&gt;If your test runner is not listed here as one of &lt;a href=&quot;/&quot;&gt;the supported test runners out of the box in Knapsack Pro&lt;/a&gt;, then you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; npm package to directly integrate with Knapsack Pro API and build your test runner integration with Knapsack Pro API.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-build-native-integration-with-knapsack-pro-api-to-run-tests-in-parallel-for-any-test-runner-testing-framework/api.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Knapsack Pro API&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We have users who did that, for instance, for the TestCafe test runner.&lt;/p&gt;

&lt;p&gt;Knapsack Pro offers out of the box support for test runners like Cypress and Jest with these packages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They both use &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt; which is a wrapper around Knapsack Pro API.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; provides support for Knapsack Pro Queue Mode API. Thanks to that, you can run tests in parallel CI nodes using a dynamic test suite split with Queue Mode. To learn more about how the Queue Mode works, you can see the section &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dynamic tests split&lt;/code&gt; of the article describing &lt;a href=&quot;/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation#dynamic-tests-split&quot;&gt;the difference between Regular Mode and Queue Mode&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;how-queue-mode-works-with-knapsack-pro-api&quot;&gt;How Queue Mode works with Knapsack Pro API&lt;/h2&gt;

&lt;p&gt;Here is the general idea behind Queue Mode in Knapsack Pro.&lt;/p&gt;

&lt;p&gt;There are parallel CI nodes on your CI server. Each CI node is running the Knapsack Pro client command to run tests.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The very first request from the Knapsack Pro client command (example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx @knapsack-pro/cypress&lt;/code&gt;) sends a list of all test files existing on the disk to &lt;a href=&quot;/api/v1/#queues_queue_post&quot;&gt;Knapsack Pro API Queue&lt;/a&gt;. Then API returns the proper set of tests for the CI node.&lt;/li&gt;
  &lt;li&gt;There is a Queue with a list of test files on the Knapsack Pro API side. The Queue is build based on a list of tests sent to the API and based on historically recorded data about your tests execution time in order to sort tests in the Queue from slowest to fastest.&lt;/li&gt;
  &lt;li&gt;Each Knapsack Pro client command connects with the Knapsack Pro API Queue and consumes a set of tests fetched from the Queue. API returns a set of tests from the top of the Queue (slowest first).&lt;/li&gt;
  &lt;li&gt;Once the set of tests is executed on the CI node then the Knapsack Pro client command asks for another set of tests from the Queue. This is repeated until the Queue is empty.&lt;/li&gt;
  &lt;li&gt;Once all tests are executed and their execution time is recorded then the Knapsack Pro client command sends recorded time of each test file to Knapsack Pro API (this &lt;a href=&quot;/api/v1/#build_subsets_post&quot;&gt;creates a Build Subset on the API side&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to the Queue Mode, tests are allocated between parallel CI nodes in a dynamic way to ensure that all CI nodes finish their work at a similar time. This allows getting optimal CI build time (as fast as possible).&lt;/p&gt;

&lt;h2 id=&quot;build-your-own-integration-with-knapsack-pro-api&quot;&gt;Build your own integration with Knapsack Pro API&lt;/h2&gt;

&lt;p&gt;You can fork one of the existing integrations like Cypress (&lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt;&lt;/a&gt;) or Jest (&lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt;&lt;/a&gt;) and replace the Cypress/Jest test runner with your own test runner to build the integration.&lt;/p&gt;

&lt;p&gt;This article explains how to do it based on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt; npm package.&lt;/p&gt;

&lt;p&gt;First, you need to clone &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt; repository which is a core library that allows you to connect with &lt;a href=&quot;/api/&quot;&gt;Knapsack Pro API&lt;/a&gt;. In the &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js#knapsack-procore&quot;&gt;README file&lt;/a&gt;, you can find instructions on &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js#development&quot;&gt;how to configure and build the project in the development&lt;/a&gt; environment.&lt;/p&gt;

&lt;p&gt;The next step is to clone &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt;&lt;/a&gt; repository. You will have to replace Cypress with your test runner npm package.&lt;/p&gt;

&lt;p&gt;Here is some basic info about the project structure. It’s written in TypeScript. The TypeScript source code is in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; directory. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib&lt;/code&gt; directory contains TypeScript code compiled to JavaScript. You should not modify files in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib&lt;/code&gt; directory because they are overridden during compilation. You can find tips on how to compile the project in the &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress#contributing&quot;&gt;README file&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can rename forked project &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/my-test-runner&lt;/code&gt; and update the info in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Update name to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/my-test-runner&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.0.0&lt;/code&gt;. &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress/blob/8942e0430e9b529ab27cf877b15b2d2964f89222/package.json#L2,L3&quot;&gt;See in code&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Add your package to the &lt;a href=&quot;https://docs.npmjs.com/cli/v11/configuring-npm/package-json#peerdependencies&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;peerDependency&lt;/code&gt;&lt;/a&gt;. This enforces your users to install a specific version of the test runner in their project alongside your package. &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress/blob/8942e0430e9b529ab27cf877b15b2d2964f89222/package.json#L62&quot;&gt;See in code&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Add your test runner package to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devDependencies&lt;/code&gt;. This allows using a specific version of the test runner in development for testing your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/my-test-runner&lt;/code&gt; with another local project using an example test suite supported by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my-test-runner&lt;/code&gt; npm package. For example, to test &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapscak-pro/cypress&lt;/code&gt; we have a &lt;a href=&quot;https://github.com/KnapsackPro/cypress-example-kitchensink&quot;&gt;separate repository with an example test suite written in Cypress&lt;/a&gt;. We verify with it that our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt; package (&lt;a href=&quot;https://github.com/KnapsackPro/cypress-example-kitchensink/blob/5c5ddf80f8ca0fb317572d50d5d264070bb61af0/package.json#L67&quot;&gt;See in code&lt;/a&gt;) works fine. In order to do, we’ve created &lt;a href=&quot;https://github.com/KnapsackPro/cypress-example-kitchensink/blob/5c5ddf80f8ca0fb317572d50d5d264070bb61af0/bin/knapsack_pro_cypress_test_file_pattern#L29&quot;&gt;an example bin script&lt;/a&gt; to connect with Knapsack Pro API. Please remove from it the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENDPOINT&lt;/code&gt; environment variable - this way the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; will connect to the production API (https://api.knapsackpro.com) by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note how we pass to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KnapsackProCore&lt;/code&gt; the list of all existing test files on the disk - &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress/blob/8942e0430e9b529ab27cf877b15b2d2964f89222/src/knapsack-pro-cypress.ts#L30&quot;&gt;see in code&lt;/a&gt;. This is needed to initialize the Queue on the API side with the very first request to the API (as mentioned earlier). Those test files will be used to run your tests.&lt;/p&gt;

&lt;p&gt;The most important place in the code is running your test runner and passing recorded tests timing data and info if tests are green or red back to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;. See how it’s done for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt; - &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress/blob/8942e0430e9b529ab27cf877b15b2d2964f89222/src/knapsack-pro-cypress.ts#L37&quot;&gt;see in code&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;The description above should allow you to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; and build your own integration for your test runner like TestCafe, etc.&lt;/p&gt;

&lt;p&gt;You can fork &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt;&lt;/a&gt; or check &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt;&lt;/a&gt; which is even thinner than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt;. Just take a look at the source code and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;README&lt;/code&gt; for those projects to learn more.&lt;/p&gt;

&lt;p&gt;Note that using &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-core-js&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt;&lt;/a&gt; instead of directly writing requests to Knapsack Pro API has the benefit of being able to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/core&lt;/code&gt; features, like the &lt;a href=&quot;https://knapsackpro.com/faq/question/what-happens-when-knapsack-pro-api-is-not-available-how-fallback-mode-works&quot;&gt;Fallback Mode&lt;/a&gt;. When the library is not able to connect to the API then it can auto-retry requests and show warnings in the logger and also run the tests in the Fallback Mode. As you can see, using the library can help you avoid dealing with many hassles along the way!&lt;/p&gt;

&lt;p&gt;I hope this article was useful to you. Let us know if you have any questions or if you would like to see an out of the box integration for your favorite test runner. We’d like to add more test runners to our &lt;a href=&quot;/&quot;&gt;list of supported out of the box test runners&lt;/a&gt; in the future.&lt;/p&gt;

&lt;h3 id=&quot;related-articles&quot;&gt;Related articles&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/how-to-build-knapsack-pro-api-client-from-scratch-in-any-programming-language&quot;&gt;How to build a custom Knapsack Pro API client from scratch in any programming language&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 24 Sep 2020 22:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2020/how-to-build-native-integration-with-knapsack-pro-api-to-run-tests-in-parallel-for-any-test-runner-testing-framework</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2020/how-to-build-native-integration-with-knapsack-pro-api-to-run-tests-in-parallel-for-any-test-runner-testing-framework</guid>
        
        
        <category>techtips</category>
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>How to run slow RSpec files on Github Actions with parallel jobs by doing an auto split of the spec file by test examples</title>
        <description>&lt;p&gt;Splitting your CI build jobs between multiple machines running in parallel is a great way to make the process fast, which results in more time for building features. Github Actions allows running parallel jobs easily. In a previous article, we explained how you can use Knapsack Pro to &lt;a href=&quot;/2019/how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;split your RSpec test files efficiently between parallel jobs on GitHub Actions&lt;/a&gt;. Today we’d like to show how to address the problem of slow test files negatively impacting the whole build times.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-slow-rspec-files-on-github-actions-with-parallel-jobs-by-doing-an-auto-split-of-the-spec-file-by-test-examples/cut-spec-file.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;GitHub, Actions, RSpec, spec file, test file, cut, scissors&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;consider-the-split&quot;&gt;Consider the split&lt;/h2&gt;

&lt;p&gt;Imagine you have a project with 30 RSpec spec files. Each file contains multiple test examples (RSpec “&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt;s”). Most of the files are fairly robust, fast unit tests. Let’s say there are also some slower files, like feature specs. Perhaps one such feature spec file takes approximately 5 minutes to execute.&lt;/p&gt;

&lt;p&gt;When we run different spec files on different parallel machines, we strive for similar execution time on all of them. In a described scenario, even if we run 30 parallel jobs (each one running just one test file), the 5 minute feature spec would be a bottleneck of the whole build. 29 machines may finish their work in a matter of seconds, but the whole build won’t be complete until the 1 remaining node finishes executing its file.&lt;/p&gt;

&lt;h2 id=&quot;divide-and-conquer&quot;&gt;Divide and conquer&lt;/h2&gt;

&lt;p&gt;To solve the problem of a slow test file, we need to split what’s inside it. We could refactor it and ensure the test examples live in separate, smaller test files. There are two problems with that though:&lt;/p&gt;

&lt;p&gt;First, it needs work. Although admittedly quite plausible in a described scenario, in real life it’s usually not just the one file that’s causing problems. Oftentimes there is a number of slow and convoluted test files, with their own complex setups, like nested &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt; blocks, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt;s, etc. We’ve all seen them (and probably contributed to them ending-up this way), haven’t we? ;-) Refactoring files like that is no fun, and there seem to always be more higher priority work to be done, at least from our experience.&lt;/p&gt;

&lt;p&gt;Second, we believe that the code organization should be based on other considerations. How you create your files and classes is most likely a result of following some approach agreed upon in your project. Dividing classes into smaller ones so that the CI build can run faster encroaches on your conventions. It might be more disturbing to some than the others, but we feel it’s fair to say it’d be best to avoid - if there was a better way to achieve the same…&lt;/p&gt;

&lt;h2 id=&quot;enter-split-by-test-examples&quot;&gt;Enter split by test examples&lt;/h2&gt;

&lt;p&gt;As you certainly know, RSpec allows us to run individual examples instead of whole files. We decided to take advantage of that, and solve the problem of bottleneck test files by gathering information about individual examples from such slower files. Such examples are then dynamically distributed between your parallel nodes and run individually, so no individual file can be a bottleneck for the whole build. What’s important, no additional work is needed - this is done automatically by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; gem. Each example is run in its correct context that’s set-up exactly the same as if you had run the whole file.&lt;/p&gt;

&lt;p&gt;If you are already using &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-slow-rspec-files-on-github-actions-with-parallel-jobs-by-doing-an-auto-split-of-the-spec-file-by-test-examples&quot;&gt;Knapsack Pro&lt;/a&gt; in queue mode, you can enable this feature just by adding one ENV variable to your GitHub Actions workflow config: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true&lt;/code&gt; (please make sure you’re running the newest version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; gem). After a few runs, Knapsack Pro will start automatically splitting your slowest test files by individual examples.&lt;/p&gt;

&lt;p&gt;Here’s a full example GitHub Actions workflow config for a Rails project using RSpec:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .github/workflows/main.yaml&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Main&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# If you need DB like PostgreSQL, Redis then define service below.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# https://github.com/actions/example-services/tree/main/.github/workflows&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:10.8&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5432:5432&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# needed because the postgres container does not provide a healthcheck&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# tmpfs makes DB faster by using RAM&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;&amp;gt;-&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--mount type=tmpfs,destination=/var/lib/postgresql/data&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-cmd pg_isready&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-interval 10s&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-timeout 5s&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;--health-retries 5&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;redis&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;6379:6379&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--entrypoint redis-server&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Set N number of parallel jobs you want to run tests on.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Use higher number if you have slow tests to split them on more parallel jobs.&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Remember to update ci_node_index below to 0..N-1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# set N-1 indexes for parallel jobs&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# When you run 2 parallel jobs then first job will have index 0, the second job will have index 1 etc&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set up Ruby&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/setup-ruby@v1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ruby-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2.6&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ runner.os }}-gems-${{ hashFiles(&apos;**/Gemfile.lock&apos;) }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;restore-keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;${{ runner.os }}-gems-&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Bundle install&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle config path vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle install --jobs 4 --retry 3&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create DB&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# use localhost for the host here because we have specified a container for the job.&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# If we were running the job on the VM this would be postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bin/rails db:prepare&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_total }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ matrix.ci_node_index }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;KNAPSACK_PRO_LOG_LEVEL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;info&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can find more details in the video below:&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/N7i2FF0DSIw&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Let us know in the comments what you think about this solution. If you’d like to give this setup a try, you can also consult our FAQ entry  explaining &lt;a href=&quot;https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-slow-rspec-files-on-github-actions-with-parallel-jobs-by-doing-an-auto-split-of-the-spec-file-by-test-examples&quot;&gt;how to split slow RSpec test files&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As always, don’t hesitate to ask questions if you encounter any troubles with configuring GitHub Actions in your project - we’d be happy to help!&lt;/p&gt;
</description>
        <pubDate>Mon, 15 Jun 2020 06:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2020/how-to-run-slow-rspec-files-on-github-actions-with-parallel-jobs-by-doing-an-auto-split-of-the-spec-file-by-test-examples</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2020/how-to-run-slow-rspec-files-on-github-actions-with-parallel-jobs-by-doing-an-auto-split-of-the-spec-file-by-test-examples</guid>
        
        
        <category>techtips</category>
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>How to merge Cypress test reports generated by Mochawesome on Github Actions</title>
        <description>&lt;p&gt;Looking through CI logs to find out which of your Cypress tests failed can be time consuming and error-prone, especially when your tests are spread out across several machines. With the Mochawesome test report generator and Github Actions, you can easily generate a beautiful, easy-to-read report of your test run, which merges results from all parallel nodes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/tech/mochajs.png&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;mocha, mochajs, mocha reports, mochawesome&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;cypress-setup&quot;&gt;Cypress Setup&lt;/h2&gt;

&lt;p&gt;First, add Mochawesome as a dependency of your project:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn install --save-dev mochawesome&lt;/code&gt; or&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install --save-dev mochawesome&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, tell Cypress to use Mochawesome by adding the following to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cypress.json&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;reporter&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;reporterOptions&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;overwrite&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;kc&quot;&gt;false&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;html&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;kc&quot;&gt;false&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;json&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;kc&quot;&gt;true&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;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, when you run a Cypress test suite, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.json&lt;/code&gt; test report file will be generated for each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.spec&lt;/code&gt; file ran, inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mochawesome-report&lt;/code&gt; directory. Since Knapsack Pro runs tests one at a time, we need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;overwrite&quot;: false&lt;/code&gt; to ensure that previous test reports are kept during subsequent test runs.&lt;/p&gt;

&lt;p&gt;Note that we wrote &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;html&quot;: false&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;json&quot;: true&lt;/code&gt;. Since we’re running tests one at a time, generating an HTML report now will be useless, since it will only show the results of one spec file. These options tell Mochawesome to only generate a JSON report. After all the tests are done, we will merge all the JSON reports together, then generate an HTML report, so that we have one report with all of our test results.&lt;/p&gt;

&lt;h2 id=&quot;github-actions-workflow&quot;&gt;Github Actions Workflow&lt;/h2&gt;

&lt;p&gt;Now we can configure our Github Actions workflow. The first job will run our Cypress tests with Knapsack Pro:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;  &lt;span class=&quot;na&quot;&gt;test_e2e&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;10.16.2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Knapsack Pro parallelization&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;err&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;- name&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Configure Knapsack Pro&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;echo ::set-env name=KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS::${{ secrets.KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS }}&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;echo ::set-env name=KNAPSACK_PRO_TEST_FILE_PATTERN::&quot;cypress/**/*.spec.{js,ts}&quot;&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;echo ::set-env name=KNAPSACK_PRO_CI_NODE_TOTAL::${{ matrix.ci_node_total }}&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;echo ::set-env name=KNAPSACK_PRO_CI_NODE_INDEX::${{ matrix.ci_node_index }}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In this case, I am using 4 parallel CI nodes, but you should modify this accordingly if you’re using more or less. We will use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt; environment variable later, as this tells us the index of the currently running parallel node. Since I’m using 4 machines, this will be a number from 0 - 3.&lt;/p&gt;

&lt;p&gt;Now we can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt; as usual:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run E2E tests&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npx @knapsack-pro/cypress&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upload E2E test reports&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;always()&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test-reports-${{ env.KNAPSACK_PRO_CI_NODE_INDEX }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mochawesome-report&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Running the tests will create the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mochawesome-report&lt;/code&gt; directory, containing all our individual test reports. To pass these along to the next job, which will take care of merging them, we use Github’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upload-artifact&lt;/code&gt; action. Note that we provide the condition &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if: always()&lt;/code&gt;, to ensure that test reports will always be uploaded, even if some of our tests failed.&lt;/p&gt;

&lt;p&gt;When naming the artifact, we’re using the node index environment variable from earlier. When running on 4 machines for example, this means that 4 artifacts will be uploaded: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-reports-0&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-reports-1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-reports-2&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-reports-3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The next job, which runs after all the parallel tests finish, will download the test reports, merge all of them, and upload a final HTML report.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;  &lt;span class=&quot;na&quot;&gt;gen_report&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Generate test report&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test_e2e&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;always()&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Install dependencies&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm install mochawesome-merge mochawesome-report-generator&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if: always()&lt;/code&gt; ensures that reports will be generated even if any tests fail. Next, we install the dependencies needed to merge and generate the report.&lt;/p&gt;

&lt;p&gt;Now we need to download all our test artifacts, which were uploaded as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-reports-0&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-reports-1&lt;/code&gt;, etc. Unfortunately, since Github Actions doesn’t support YAML anchors, this part requires a bit of copy and paste. Just like before, tweak this part depending on how many parallel nodes you’re using:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create reports directory&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mkdir reports&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Download test-reports-0&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test-reports-0&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;reports/0&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Download test-reports-1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test-reports-1&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;reports/1&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Download test-reports-2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test-reports-2&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;reports/2&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Download test-reports-3&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test-reports-3&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;reports/3&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reports&lt;/code&gt; directory, with subdirectories containing reports from each node. Our directory structure will now look like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;❯ tree reports
reports
├── 0
│   ├── mochawesome_001.json
│   ├── mochawesome_002.json
│   ├── mochawesome_003.json
│   ├── mochawesome_004.json
│   ├── mochawesome_005.json
│   └── mochawesome.json
├── 1
│   ├── mochawesome_001.json
│   ├── mochawesome_002.json
│   ├── mochawesome_003.json
│   └── mochawesome.json
├── 2
│   ├── mochawesome_001.json
│   ├── mochawesome_002.json
│   ├── mochawesome_003.json
│   └── mochawesome.json
└── 3
    ├── mochawesome_001.json
    ├── mochawesome_002.json
    ├── mochawesome_003.json
    └── mochawesome.json

4 directories, 18 files&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To merge these reports, we want to have them all in one directory. The next step is a short Bash script:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;      - name: Move all reports into one directory
        run: |
          &lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; mochawesome-report
          &lt;span class=&quot;nv&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
          &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;file &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;find reports &lt;span class=&quot;nt&quot;&gt;-type&lt;/span&gt; f &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; mochawesome&lt;span class=&quot;se&quot;&gt;\*&lt;/span&gt;.json&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
            &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;basename&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$file&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; .json&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;-&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;.json
            &lt;span class=&quot;nb&quot;&gt;mv&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$file&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; mochawesome-report/&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$filename&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;ls &lt;/span&gt;mochawesome-report
            &lt;span class=&quot;nv&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;$((&lt;/span&gt;i &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;))&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This script finds all the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.json&lt;/code&gt; report files and moves them to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mochawesome-report&lt;/code&gt; folder, which will look like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;❯ tree mochawesome-report 
mochawesome-report
├── mochawesome_001-10.json
├── mochawesome_001-16.json
├── mochawesome_001-2.json
├── mochawesome_001-6.json
├── mochawesome_002-15.json
├── mochawesome_002-1.json
├── mochawesome_002-5.json
├── mochawesome_002-9.json
├── mochawesome_003-0.json
├── mochawesome_003-14.json
├── mochawesome_003-4.json
├── mochawesome_003-8.json
├── mochawesome_004-13.json
├── mochawesome_005-12.json
├── mochawesome-11.json
├── mochawesome-17.json
├── mochawesome-3.json
└── mochawesome-7.json

0 directories, 18 files&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that a number is appended to the end of each filename. This is only to prevent filename conflicts when we’re moving all the reports into one directory. The name of the file won’t have any affect on the generated report.&lt;/p&gt;

&lt;p&gt;Now we can actually generate the final report:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Merge and generate reports&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;mkdir report&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;npx mochawesome-merge &amp;gt; report/report.json&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;npx marge --inline report/report.json -o report/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We ran these commands to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdir report&lt;/code&gt;: Create the directory that will hold the final report&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx mochawesome-merge &amp;gt; report/report.json&lt;/code&gt;: Merge all of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.json&lt;/code&gt; files from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mochawesome-report&lt;/code&gt; directory into one file, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;report/report.json&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx marge --inline report/report.json -o report/&lt;/code&gt;: Take the merged report and generate a pretty HTML report from it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we upload this report:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upload report&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;E2E Test Reports&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;report/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now you will be able to download your report from the build artifacts:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-merge-cypress-test-reports-generated-by-mochawesome-on-github-actions/final-reports.png&quot; style=&quot;width:500px;&quot; alt=&quot;Github build artifacts&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you want to learn more about how to test your Javascript applications faster then check out the &lt;a href=&quot;/blog/&quot;&gt;blog&lt;/a&gt; and website about the &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-merge-cypress-test-reports-generated-by-mochawesome-on-github-actions&quot;&gt;Knapsack Pro tool for CI parallelisation&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Thu, 05 Mar 2020 11:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2020/how-to-merge-cypress-test-reports-generated-by-mochawesome-on-github-actions</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2020/how-to-merge-cypress-test-reports-generated-by-mochawesome-on-github-actions</guid>
        
        
        <category>techtips</category>
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>How to speed up Ruby and JavaScript tests with CI parallelisation</title>
        <description>&lt;p&gt;When working on a larger project, you may struggle with the problem of an increasingly growing set of tests, which over time begins to perform slower on your continuous integration (CI) server. I had this problem while working on a project in Ruby on Rails, where RSpec tests on &lt;a href=&quot;https://knapsackpro.com/ci_servers/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;CircleCI&lt;/a&gt; took about 15 minutes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/knapsack.jpg&quot; style=&quot;width:200px;margin-left: 15px;float:right;&quot; alt=&quot;knapsack, backpack, knapsack problem, knapsack pro&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As it was bothering me, I decided to do something about it, which resulted in building an open-source Knapsack Ruby gem library (the name derives from the knapsack problem), which deals with distributing tests between parallel CI servers. In this article, you will &lt;b&gt;learn about two approaches to split tests on parallel &lt;a href=&quot;https://knapsackpro.com/ci_servers/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;continuous integration servers&lt;/a&gt; - static and dynamic&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;If you have in your project a test suite that takes to execute on a CI server a dozen or so minutes, or maybe even a few hours, you know how inconvenient this is for programmers. When you are working on some new feature and pushing a new git commit into the repository, you have to wait a long time for your CI server until it executes CI build.&lt;/p&gt;

&lt;p&gt;Waiting a few minutes or an hour is delaying the feedback you can get from the CI server about tests that may have not been completed (red tests). After all, we all want to get information about whether our CI build is green or red as soon as possible so that the work of programmers is not blocked.&lt;/p&gt;

&lt;h2 id=&quot;problem-with-running-parallel-tests-on-the-ci-server&quot;&gt;Problem with running parallel tests on the CI server&lt;/h2&gt;

&lt;p&gt;To &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;speed up the execution of the CI build&lt;/a&gt;, you can use parallelism on the CI server, i.e. launching several parallel CI machines (CI containers, e.g. in Docker), where each parallel server will perform a part of the test set. However, there is a problem as to which tests should be run on which servers (CI nodes) so that their distribution is fairly even and you don’t have to wait for a CI node that is a bottleneck.&lt;/p&gt;

&lt;p&gt;Below you can see an example of a non-optimal distribution of tests on 4 CI servers, where the second server marked in red is a bottleneck, so the waiting time for the completion of the entire CI build is up to 20 minutes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/not-optimal-tests-split.png&quot; style=&quot;width:100%;&quot; alt=&quot;not optimal tests split on CI server, CI parallelism&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;optimal-distribution-of-tests-on-parallel-ci-servers&quot;&gt;Optimal distribution of tests on parallel CI servers&lt;/h2&gt;

&lt;p&gt;In an ideal scenario, the tests should be distributed in such a way that all parallel CI servers end operations at a similar time. In the following part, I will show how this can be achieved.&lt;/p&gt;

&lt;p&gt;Below you can see an example of the optimal distribution of tests, where each parallel CI machine performs tests for 10 minutes, thanks to which the entire CI build lasts only 10 minutes, not 20 as in the previous example.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/optimal-tests-split.png&quot; style=&quot;width:100%;&quot; alt=&quot;optimal tests split on CI server, CI parallelism&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;static-split-of-tests-in-a-deterministic-way---regular-mode&quot;&gt;Static split of tests in a deterministic way - Regular Mode&lt;/h2&gt;

&lt;p&gt;One way to determine how to divide tests between parallel machines on a CI server so that each server completes tests at a similar time is to use the measured runtime of the files in the test suite. This was the first approach I implemented in Knapsack Ruby gem.&lt;/p&gt;

&lt;p&gt;After measuring the test execution time, we can assign individual test files between parallel CI servers to make sure that the CI build does not have a bottleneck.&lt;/p&gt;

&lt;p&gt;With the help of the knapsack library, you can run tests for many test runners in Ruby, such as RSpec, Minitest, Cucumber, Spinach, and Turnip. Using test runtime, Knapsack gem can build a list of tests to be performed on a specific CI node.&lt;/p&gt;

&lt;p&gt;I improved this way of dividing tests by measuring test files timing per git commit and branches. In the below video I show how Regular Mode a static split of tests in a deterministic way works in Knapsack Pro. In the next section, you will learn about some of the edge cases of this approach and how to solve it.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/ZEb6NeRRfQ4&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;problem-with-the-static-split-of-tests&quot;&gt;Problem with the static split of tests&lt;/h2&gt;

&lt;p&gt;While collecting information from users, I found out that the distribution of tests in a static way is not always a good solution. Sometimes some tests have a random execution time, which depends, for example, on how busy the CI server is or on the fact that the test does not pass due to a software error, quitting work faster than usual, etc.&lt;/p&gt;

&lt;p&gt;For example, tests using a browser can have fluctuations in execution runtime (tests in Capybara in Ruby or E2E tests in JavaScript).&lt;/p&gt;

&lt;p&gt;The problem also grows depending on what CI server you use. Does each of the parallel CI machines have similar performance or does it share resources like a CPU or RAM? Does the CI container run in a shared environment? If the CI node is overloaded, then our tests may, of course, be slower.&lt;/p&gt;

&lt;p&gt;Besides, there will be problems with whether all parallel machines start at a similar time or not. If you have purchased a pool of parallel CI servers, someone else might be using it too, e.g. another CI build from the current project or another project from your organization.&lt;/p&gt;

&lt;p&gt;If not all CI nodes start at the same time or the boot time of certain steps in the middle of the CI node execution can take a random time, then we would like to be able to make sure that all CI machines finish their work at a similar moment. Slow CI machines or those which started work late should do fewer tests, and those machines that have started work earlier can easily do more.&lt;/p&gt;

&lt;p&gt;All parallel CI nodes must stop working at a similar time to avoid a bottleneck, that is, overloading the machine with tests.&lt;/p&gt;

&lt;h2 id=&quot;dynamic-tests-split---queue-mode&quot;&gt;Dynamic tests split - Queue Mode&lt;/h2&gt;

&lt;p&gt;The solution to the above problem is to dynamically divide tests between parallel machines within one CI build. This is a problem I have been working on in recent years, creating the &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;Knapsack Pro&lt;/a&gt; library and the Queue Mode for Ruby and JavaScript with support for several popular test runners like Jest or Cypress.&lt;/p&gt;

&lt;p&gt;The idea is simple. We have a set of tests that are queued on the Knapsack Pro server. Individual parallel CI machines consume the queue with Knapsack Pro API until the queue is over. Thanks to this, the tests are optimally distributed among CI servers, helping you to avoid a bottleneck in the form of an overloaded (too slow) CI server. Below you can see an example:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation/knapsack-pro-cloud-v2.png&quot; style=&quot;width:70%;display:block;margin:0 auto;&quot; alt=&quot;tests split on CI server with Knapsack Pro Queue Mode, CI parallelism&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Dynamic test suite split solves our problem with random test execution time, with slow running CI servers or with servers that are overloaded which work is slower. No matter when they start or finish work - it’s important that they don’t take too many tests to execute until they finish their current work.&lt;/p&gt;

&lt;p&gt;See how dynamic test suite split works in Queue Mode for Knapsack Pro.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;implementation-of-knapsack-pro-in-ruby-and-javascript&quot;&gt;Implementation of Knapsack Pro in Ruby and JavaScript&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;Knapsack Pro&lt;/a&gt; has native support for many popular CI servers. It is also an agnostic CI tool, so you can use any CI server. All you have to do is configure the Knapsack Pro command for each parallel CI server running within one CI build. Below you can see a general example of how config YAML might look for a CI server with Knapsack Pro:&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/ArturT/580df4fd7852e67379e9b263228e1994.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;If you use RSpec and you have very slow test files you can auto split them. Knapsack Pro &lt;a href=&quot;https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it&quot;&gt;detects slow RSpec test files to split it by test examples on parallel jobs&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;Knapsack Pro&lt;/a&gt; supports Ruby and several test runners in JavaScript such as Jest and Cypress, but there are plans to add support for more test runners and programming languages. I would love to hear &lt;a href=&quot;https://docs.google.com/forms/d/e/1FAIpQLSe7Z6k__VczmRMmXykjA5i2MVEA3nEJ90gbiIeCRjecWhPOig/viewform?hl=en&quot;&gt;what you use to test applications and which CI servers&lt;/a&gt;. In case you are considering changing your CI provider, check out our &lt;a href=&quot;https://knapsackpro.com/ci_servers/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;list of CI servers features&lt;/a&gt;. You can contact me on &lt;a href=&quot;https://www.linkedin.com/in/arturtrzop/&quot;&gt;LinkedIn&lt;/a&gt;, and you can find more information about the described solution at &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation&quot;&gt;KnapsackPro.com&lt;/a&gt;. I hope this article was useful to you. :)&lt;/p&gt;
</description>
        <pubDate>Sun, 02 Feb 2020 15:30:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2020/how-to-speed-up-ruby-and-javascript-tests-with-ci-parallelisation</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>URI.escape is obsolete. Percent-encoding your query string</title>
        <description>&lt;p&gt;Have you encountered one of those warnings in your Ruby 2.7.0 project?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/uri-escape-is-obsolete-percent-encoding-your-query-string/encoded_space.jpeg&quot; style=&quot;width:200px;margin-left: 15px;float:right;&quot; alt=&quot;url encoded space&quot; /&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;warning: URI.escape is obsolete
warning: URI.encode is obsolete&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Find out how to fix it!&lt;/p&gt;

&lt;h2 id=&quot;a-little-bit-of-history&quot;&gt;A little bit of history&lt;/h2&gt;

&lt;p&gt;Ruby 2.7.0 shows a warning when invoking &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.escape&lt;/code&gt; or its alias, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.encode&lt;/code&gt;. It might look like a fresh deprecation, but the fact is, these methods have been marked as obsolete for… &lt;a href=&quot;https://github.com/ruby/ruby/commit/238b979f1789f95262a267d8df6239806f2859cc&quot;&gt;over 10 years now&lt;/a&gt;! If you’re wondering how come you’ve never encountered the warning before, here’s the answer: previously it was displayed only if you run your script in a verbose mode, and this has changed just recently.&lt;/p&gt;

&lt;h2 id=&quot;so-why-is-uriescape-obsolete-anyway&quot;&gt;So why is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.escape&lt;/code&gt; obsolete, anyway?&lt;/h2&gt;

&lt;p&gt;The trouble with a concept of “escaping the URI” is that URI consists of many components (like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;query&lt;/code&gt;), and we don’t want to escape them in the same way. For example, it’s fine for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#&lt;/code&gt; character to appear at the end of the URI (when it’s used as what’s usually called an anchor, or in URI parlance - a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fragment&lt;/code&gt; component) - but when the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#&lt;/code&gt; is part of user’s input (like in a search query), we want to encode it to ensure correct interpretation. Similarly, if the query string value contains other reserved characters, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt;, we &lt;em&gt;do&lt;/em&gt; want to escape them so that they are not incorrectly interpreted as delimiters, as if they were used as reserved characters.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.escape&lt;/code&gt; relies on a simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gsub&lt;/code&gt; operation for the whole string and doesn’t differentiate between distinct components, which doesn’t take into account intricacies like those mentioned above.&lt;/p&gt;

&lt;h2 id=&quot;how-to-fix-it&quot;&gt;How to fix it?&lt;/h2&gt;

&lt;p&gt;Since I haven’t found an existing solution that would, when given a whole-URI string, interpret distinct components and apply different escaping rules in a desired way on its own, my advice is to encode different components separately. The most common (and most sensitive) use-case is probably encoding of the query string in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;query&lt;/code&gt; component, so I’ll focus on this case. And Ruby’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI&lt;/code&gt; module itself provides two handy methods that will help us achieve just that!&lt;/p&gt;

&lt;h2 id=&quot;percent-encoding-your-query-string&quot;&gt;Percent-encoding your query string&lt;/h2&gt;

&lt;h4 id=&quot;uriencode_www_form_componentstring-encnil&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.encode_www_form_component(string, enc=nil)&lt;/code&gt;&lt;/h4&gt;

&lt;p&gt;This method will percent-encode every reserved character, leaving only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*, -, ., 0-9, A-Z, _, a-z&lt;/code&gt; intact.
It also substitues space with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt;. Examples:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Tom &amp;amp; Jerry&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode_www_form_component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;Tom+%26+Jerry&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;So scared, but let&apos;s do this! #yolo&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode_www_form_component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;So+scared%2C+but+let%27s+do+this%21+%23yolo&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see, we can now safely build our query strings with values escaped this way. But if this seems like too much of manual work, there is a handy way to handle the whole query for you. Introducing…&lt;/p&gt;

&lt;h4 id=&quot;uriencode_www_formenum-encnil&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI.encode_www_form(enum, enc=nil)&lt;/code&gt;&lt;/h4&gt;

&lt;p&gt;Slightly different interface. It takes an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable&lt;/code&gt; (usually a nested &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; or a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt;) and builds the query accordingly. It uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.encode_www_form_component&lt;/code&gt; internally, so the encoding rules stay the same. The difference lies in the way you use it. Examples:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode_www_form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;search&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Tom &amp;amp; Jerry&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;s2&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;search=Tom+%26+Jerry&amp;amp;page=3&quot;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode_www_form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;search&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;2 + 2 = 5&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;search=2+%2B+2+%3D+5&quot;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode_www_form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;search: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;why is URI.escape obsolete&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;category: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;meta&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;search=why+is+URI.escape+obsolete&amp;amp;category=meta&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Shameless plug&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode_www_form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;q: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;how to speed up your CI?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;tag: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;#devops&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#productivity&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;lang: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;q=how+to+speed+up+your+CI%3F&amp;amp;tag=%23devops&amp;amp;tag=%23productivity&amp;amp;lang=en&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Quite straightforward, isn’t it?&lt;/p&gt;

&lt;h3 id=&quot;rails-project&quot;&gt;Rails project?&lt;/h3&gt;

&lt;h4 id=&quot;hashto_query&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash#to_query&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;(also aliased as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash.to_param&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Rails extends the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; class with this handy method. It will return a query string in a correct format, with its values correctly encoded where needed.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;query_data&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;ss&quot;&gt;q: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;how to speed up your CI?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;tag: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;#devops&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#productivity&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;lang: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;en&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;query_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_query&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;lang=en&amp;amp;q=how+to+speed+up+your+CI%3F&amp;amp;tag%5B%5D=%23devops&amp;amp;tag%5B%5D=%23productivity&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;(Notice how it also sorts the keys alphabetically.)&lt;/p&gt;

&lt;p&gt;You can also pass an optional namespace to this method, which would enclose the key names in the returned string, resulting in a format like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search[q]=&quot;how to speed up your CI?&quot;&lt;/code&gt; (but obviously percent-encoded):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;query_data&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;ss&quot;&gt;q: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;how to speed up your CI?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;tag: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;#devops&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#productivity&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;lang: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;en&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;query_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;search&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;search%5Blang%5D=en&amp;amp;search%5Bq%5D=how+to+speed+up+your+CI%3F&amp;amp;search%5Btag%5D%5B%5D=%23devops&amp;amp;search%5Btag%5D%5B%5D=%23productivity&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summing-up&quot;&gt;Summing up&lt;/h2&gt;

&lt;p&gt;I hope that short article cleared up any confusion you might have had about encoding your strings for use in URI. Please let us know in the comments if you use any other ways of dealing with percent-encoding in your Ruby/Rails projects!&lt;/p&gt;

&lt;p&gt;And if your project is struggling with slow CI builds, check out &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=uri-escape-is-obsolete-percent-encoding-your-query-string&quot;&gt;Knapsack Pro&lt;/a&gt;, which can help you solve that problem.&lt;/p&gt;
</description>
        <pubDate>Sun, 19 Jan 2020 12:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2020/uri-escape-is-obsolete-percent-encoding-your-query-string</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2020/uri-escape-is-obsolete-percent-encoding-your-query-string</guid>
        
        
        <category>techtips</category>
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>How to run Cypress parallel tests on Codefresh CI server</title>
        <description>&lt;p&gt;When you work with end to end tests in Cypress you may notice they quickly get time-consuming for running the whole test suite. The more complex the project the more test cases you may end up with and this can take a long time to run - dozens of minutes or even hours.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-cypress-parallel-tests-on-codefresh-ci-server/cypress-codefresh.jpeg&quot; style=&quot;width:200px;margin-left: 15px;float:right;&quot; alt=&quot;Cypress, Codefresh&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One way of executing faster tests in Cypress is to use CI parallelism which is essentially running a few chunks of test suite spread across parallel jobs on CI server. In this article, you will learn how to &lt;b&gt;configure Codefresh.io server with matrix feature to run parallel steps for your Cypress tests&lt;/b&gt; and this way save time on faster CI builds.&lt;/p&gt;

&lt;h2 id=&quot;configure-codefreshio-to-run-cypressio-tests&quot;&gt;Configure Codefresh.io to run Cypress.io tests&lt;/h2&gt;

&lt;p&gt;You need in your project repository a Dockerfile that will be used by Codefresh to build Docker image which will be used to run Docker container with your Cypress tests inside of it.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot; data-lang=&quot;dockerfile&quot;&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; cypress/base:10&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;apt-get update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;  apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;  python3-dev &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;  python3-pip

&lt;span class=&quot;c&quot;&gt;# Install AWS CLI&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;pip3 &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;awscli

&lt;span class=&quot;c&quot;&gt;# Install Codefresh CLI&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;wget https://github.com/codefresh-io/cli/releases/download/v0.31.1/codefresh-v0.31.1-alpine-x64.tar.gz
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-xf&lt;/span&gt; codefresh-v0.31.1-alpine-x64.tar.gz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; /usr/local/bin/

&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; . /src&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; /src&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next thing is to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.codefresh/codefresh.yml&lt;/code&gt; file with instructions to run your application HTTP server in the background and then run Cypress tests against the application.&lt;/p&gt;

&lt;p&gt;In the below example, you will see that you can leverage matrix environment configuration for Codefresh. Set a few environmental variables with the node index for each parallel step. This is required by Knapsack Pro tool. &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-cypress-parallel-tests-on-codefresh-ci-server&quot;&gt;Knapsack Pro is a wrapper around Cypress test runner&lt;/a&gt; which is responsible for running a proper set of tests across parallel jobs (parallel steps on Codefresh).&lt;/p&gt;

&lt;p&gt;Knapsack Pro for Cypress has built-in Queue Mode mechanism which splits test files in a dynamic way across parallel CI nodes (parallel jobs) and thanks to that ensure each parallel step takes a similar time so your CI build is executed in optimal time. You save time by avoiding a not optimal split of tests. You simply eliminate bottleneck slow job that could happen when you allocate too many slow test files to the same parallel step.&lt;/p&gt;

&lt;p&gt;You will learn more how exactly Queue Mode works at the end of this article but for now, let’s take a look at Codefresh YAML configuration.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/ArturT/9e219ad72cbffb64e3c0d82bbf2cee2b.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;You also need to add an API token for the Cypress test suite. You can get the API token at &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-cypress-parallel-tests-on-codefresh-ci-server&quot;&gt;Knapsack Pro&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You need to set an API token &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS&lt;/code&gt; in Codefresh dashboard, see left menu Pipelines -&amp;gt; settings (cog icon next to the pipeline) -&amp;gt; Variables tab (see a vertical menu on the right side).&lt;/li&gt;
  &lt;li&gt;Set where Codefresh YAML file can be found. In Codefresh dashboard, see left menu Pipelines -&amp;gt; settings (cog icon next to pipeline) -&amp;gt; Workflow tab (horizontal menu on the top) -&amp;gt; Path to YAML (set there &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./.codefresh/codefresh.yml&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Set how many parallel jobs (parallel CI nodes) you want to run with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/code&gt; environment variable in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.codefresh/codefresh.yml&lt;/code&gt; file.&lt;/li&gt;
  &lt;li&gt;Ensure in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matrix&lt;/code&gt; section you listed all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt; environment variables with a value from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL-1&lt;/code&gt;. Codefresh will generate a matrix of parallel jobs where each job has a different value for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt;. Thanks to that Knapsack Pro knows what tests should be run on each parallel job.&lt;/li&gt;
&lt;/ul&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/3OTu6a-WGgo&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;how-dynamic-test-suite-split-works-in-queue-mode&quot;&gt;How dynamic test suite split works in Queue Mode&lt;/h2&gt;

&lt;p&gt;You can learn how dynamic test suite split with &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-cypress-parallel-tests-on-codefresh-ci-server&quot;&gt;Knapsack Pro&lt;/a&gt; Queue Mode works in the below video. There are examples of a few edge cases that Queue Mode helps with.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;I hope you find this article helpful. You can also check &lt;a href=&quot;/&quot;&gt;other articles&lt;/a&gt;. For instance, I wrote an article about &lt;a href=&quot;/2019/how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project&quot;&gt;Codefresh and Ruby on Rails project setup&lt;/a&gt; with a video showing how it works.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;If you are considering using Cypress in your project, check out our &lt;a href=&quot;https://knapsackpro.com/testing_frameworks/alternatives_to/cypress-io?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-cypress-parallel-tests-on-codefresh-ci-server&quot;&gt;comparison of Cypress.io with other solutions&lt;/a&gt;. And to compare Codefresh CI to other CIs, you can visit our comparison pages: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/codefresh-ci/vs/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-cypress-parallel-tests-on-codefresh-ci-server&quot;&gt;Codefresh vs CircleCI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/codefresh-ci/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-cypress-parallel-tests-on-codefresh-ci-server&quot;&gt;Codefresh vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/codefresh-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-cypress-parallel-tests-on-codefresh-ci-server&quot;&gt;Github Actions vs Codefresh CI&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-cypress-parallel-tests-on-codefresh-ci-server#codefresh-ci&quot;&gt;Codefresh vs Other CI providers&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 19 Oct 2019 16:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/how-to-run-cypress-parallel-tests-on-codefresh-ci-server</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/how-to-run-cypress-parallel-tests-on-codefresh-ci-server</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>How to use Codefresh CI parallel steps to run RSpec a few times faster for Rails project</title>
        <description>&lt;p&gt;Codefresh.io seems to be a very nice CI solution to work with if you are a Ruby developer. I’ve tested one of my projects on Codefresh to see how it allows running fast tests. Codefresh has a matrix feature that lets you run parallel steps for the CI build. In this article, you will see how to leverage Codefresh matrix configuration and Knapsack Pro client library for testing in parallel your Ruby on Rails project with RSpec test suite.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project/codefresh.png&quot; style=&quot;width:500px;margin-left: 15px;float:right;&quot; alt=&quot;Codefresh&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;configure-rails-project-on-codefreshio&quot;&gt;Configure Rails project on Codefresh.io&lt;/h2&gt;

&lt;p&gt;In order to run CI builds for Rails project on Codefresh you need YAML configuration file and Docker image that will be used as your base Docker container running your tests.&lt;/p&gt;

&lt;p&gt;We also need DB on the CI server. In my Ruby on Rails project, I use PostgreSQL so I have to configure Codefresh service to run Postgres.&lt;/p&gt;

&lt;p&gt;Let’s start with creating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.codefresh/codefresh.yml&lt;/code&gt; file and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Test.Dockerfile&lt;/code&gt; that you need to add to your repository.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/ArturT/722bccf19bfdce3e5d2dbbc2cb89834a.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;As you can see I use &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project&quot;&gt;knapsack_pro ruby gem&lt;/a&gt; as a command to run my RSpec tests. It will know what set of tests should be executed on a particular parallel step based on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt; value. The node index is generated based on the list of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt;
variables defined for the matrix.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project&quot;&gt;Knapsack Pro&lt;/a&gt; will automatically split tests across parallel jobs (steps) to ensure each step takes a similar time. It’s possible thanks to Knapsack Pro Queue Mode which does dynamic test suite split across parallel steps. You can learn more about technical details on how Queue Mode works from the video at the end of this article but let’s check now how Codefresh works in action. You can see my CI builds in Codefresh web dashboard on the below video.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/6yaE63RGZ0M&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;If you forgot steps from the video then I listed things you need to set up in Codefresh dashboard and in YAML config file.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You need to set an API token like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/code&gt; in Codefresh dashboard, see left menu Pipelines -&amp;gt; settings (cog icon next to the pipeline) -&amp;gt; Variables tab (see a vertical menu on the right side). Add there new API token depending on the test runner you use:
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CUCUMBER&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_MINITEST&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TEST_UNIT&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_SPINACH&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Set where Codefresh YAML file can be found. In Codefresh dashboard, see left menu Pipelines -&amp;gt; settings (cog icon next to pipeline) -&amp;gt; Workflow tab (horizontal menu on the top) -&amp;gt; Path to YAML (set there &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./.codefresh/codefresh.yml&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Set how many parallel jobs (parallel CI nodes) you want to run with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL&lt;/code&gt; environment variable in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.codefresh/codefresh.yml&lt;/code&gt; file.&lt;/li&gt;
  &lt;li&gt;Ensure in the matrix section you listed all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt; environment variables with a value from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL-1&lt;/code&gt;. Codefresh will generate a matrix of parallel jobs where each job has a different value for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX&lt;/code&gt;. Thanks to that Knapsack Pro knows what tests should be run on each parallel job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;how-queue-mode-works&quot;&gt;How Queue Mode works&lt;/h2&gt;

&lt;p&gt;In this video, I explained technical details for a dynamic test suite split and what problems it solves to help you run fast CI builds in optimal time.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;There are more test runners in Ruby like Cucumber, Minitest, etc that are supported by &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project&quot;&gt;Knapsack Pro&lt;/a&gt;. Even JavaScript tools like Cypress.io or Jest can be launched with Knapsack Pro wrapper (&lt;a href=&quot;/&quot;&gt;see installation guide&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I hope you find this tutorial useful and you can benefit from &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project&quot;&gt;faster CI builds&lt;/a&gt; for your Rails project thanks to running parallel steps on Codefresh.io.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;If you are currently considering a choice between Codefresh and other solutions, check out our comparisons: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/codefresh-ci/vs/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project&quot;&gt;Codefresh vs CircleCI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/codefresh-ci/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project&quot;&gt;Codefresh vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/codefresh-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project&quot;&gt;Github Actions vs Codefresh CI&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project#codefresh-ci&quot;&gt;Codefresh vs Other CI providers&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 18 Oct 2019 17:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/how-to-use-codefresh-ci-parallel-steps-to-run-rspec-a-few-times-faster-for-rails-project</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>GitHub Actions CI config for Ruby on Rails project with MySQL, Redis, Elasticsearch - how to run parallel tests</title>
        <description>&lt;p&gt;You will learn how to configure Ruby on Rails project on &lt;a href=&quot;https://knapsackpro.com/ci_servers/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;GitHub Actions&lt;/a&gt;. This specific Rails project has MySQL and Redis database. There is also Elasticsearch service running on CI. If your project is close to that setup below GitHub Actions yaml configuration will allow you to run tests on GitHub CI server.&lt;/p&gt;

&lt;p&gt;If you happen to use PostgreSQL you can check our previous article about &lt;a href=&quot;/2019/how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;Rails app configuration with Postgres on GitHub Actions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests/github-mysql-redis-elasticsearch.png&quot; style=&quot;width:500px;margin-left: 15px;float:right;&quot; alt=&quot;GitHub, Redis, MySQL, Elasticsearch&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;github-actions-configuration-for-rails&quot;&gt;GitHub Actions configuration for Rails&lt;/h2&gt;

&lt;p&gt;In your repository, you need to create file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows/main.yaml&lt;/code&gt; Thanks to it GitHub Actions will run your CI builds. You can find results of executed CI builds in Actions tab on your GitHub repository page.&lt;/p&gt;

&lt;p&gt;In this case, &lt;b&gt;Rails&lt;/b&gt; application has &lt;b&gt;MySQL&lt;/b&gt;, &lt;b&gt;Redis&lt;/b&gt;, and &lt;b&gt;Elasticsearch&lt;/b&gt; databases. You need to set up services with docker container to run each. In the below config, there is also a step for health check the MySQL and Elasticsearch to ensure both are up and running before you can start running tests.&lt;/p&gt;

&lt;p&gt;The tests are executed across parallel jobs thanks to matrix feature in &lt;a href=&quot;https://knapsackpro.com/ci_servers/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;GitHub Actions&lt;/a&gt; and the &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;Knapsack Pro&lt;/a&gt; ruby gem that will auto-balance tests distribution across jobs. Auto balancing tests using Knapsack Pro Queue Mode will ensure each parallel job finish work at a similar time. Thanks to that there is no bottleneck (no slow job with too many tests to run) and you can enjoy fast CI build time because you get optimal tests split across parallel tasks.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/ArturT/b3679cfe7c2d3d8625d54fb5a8966874.js&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;how-knapsack-pro-queue-mode-works&quot;&gt;How Knapsack Pro Queue Mode works&lt;/h2&gt;

&lt;p&gt;In this video, you will learn how dynamic test suite spilt across parallel jobs (parallel CI nodes) work.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;how-knapsack-pro-regular-mode-works&quot;&gt;How Knapsack Pro Regular Mode works&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;Knapsack Pro&lt;/a&gt; has also a deterministic way of splitting tests. Tests are split only once before running tests. This is the most simple way that you will try for the first time to record your CI build before you switch to Queue Mode.&lt;/p&gt;

&lt;p&gt;You can learn more about Knapsack Pro in the &lt;a href=&quot;/&quot;&gt;installation guide&lt;/a&gt;.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/ZEb6NeRRfQ4&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;If you would like to learn more about &lt;a href=&quot;https://knapsackpro.com/ci_servers/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;GitHub Actions&lt;/a&gt; check our previous article about &lt;a href=&quot;/2019/how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;testing Rails app with PostgreSQL on GitHub Actions&lt;/a&gt;. There is also a video showing it in action.&lt;/p&gt;

&lt;p&gt;Learn &lt;a href=&quot;/2020/how-to-run-slow-rspec-files-on-github-actions-with-parallel-jobs-by-doing-an-auto-split-of-the-spec-file-by-test-examples&quot;&gt;how to split slow RSpec spec files by test examples on GitHub Actions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you are still considering using GitHub Actions, then our &lt;a href=&quot;https://knapsackpro.com/ci_comparisons?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests#github-actions&quot;&gt;comparison of GitHub Actions vs other CIs&lt;/a&gt; may be helpful for you. The most commonly visited comparisons are: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;GitHub Actions vs Circle CI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;GitHub Actions vs Jenkins&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/travis-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;GitHub Actions vs Travis CI&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 27 Sep 2019 14:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests</guid>
        
        
        <category>techtips</category>
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>How to run RSpec on GitHub Actions for Ruby on Rails app using parallel jobs</title>
        <description>&lt;p&gt;GitHub introduced their own CI server solution called &lt;a href=&quot;https://knapsackpro.com/ci_servers/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;GitHub Actions&lt;/a&gt;. You will learn how to set up your Ruby on Rails application on GitHub Actions with YAML config file. To run your RSpec test suite faster you will configure parallel jobs with matrix strategy on GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs/github-octopus.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;GitHub, cat, octopus&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;automate-your-workflow-on-github-actions&quot;&gt;Automate your workflow on GitHub Actions&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com/ci_servers/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;GitHub Actions&lt;/a&gt; makes it easy to automate all your software workflows with world-class CI/CD. Building, testing, and deploying your code right from GitHub became available with simple YAML configuration.&lt;/p&gt;

&lt;p&gt;You can even create a few YAML config files to run a different set of rules on your CI like scheduling daily CI builds. But let’s focus strictly on how to get running tests for Rails app on &lt;a href=&quot;https://knapsackpro.com/ci_servers/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;GitHub Actions&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;setup-ruby-on-rails-on-github-actions-with-yaml-config&quot;&gt;Setup Ruby on Rails on GitHub Actions with YAML config&lt;/h2&gt;

&lt;p&gt;In your project repository, you need to create file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows/main.yaml&lt;/code&gt; Thanks to it GitHub will run your CI build. You can find results of CI builds in &lt;i&gt;Actions&lt;/i&gt; tab for your GitHub repository.&lt;/p&gt;

&lt;p&gt;In our case Rails application has Postgres database so you need to set up service with docker container to run Postgres DB.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# If you need DB like PostgreSQL then define service below.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Example for Redis can be found here:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# https://github.com/actions/example-services/tree/main/.github/workflows&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:10.8&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# will assign a random free host port&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5432/tcp&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# needed because the postgres container does not provide a healthcheck&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To be able to install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pg&lt;/code&gt; ruby gem from project &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; you will need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libpq-dev&lt;/code&gt; library in Ubuntu system hence the step to install it.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libpq&lt;/code&gt; is a set of library functions that allow client programs to pass queries to the PostgreSQL backend server and to receive the results of these queries. We need it to compile &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pg&lt;/code&gt; gem.
Next step will be installing our Ruby gems.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# required to compile pg ruby gem&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;install PostgreSQL client&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sudo apt-get install libpq-dev&lt;/span&gt;

&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build and create DB&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# use localhost for the host here because we have specified a container for the job.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# If we were running the job on the VM this would be postgres&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;PGPORT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${ { job.services.postgres.ports[5432] }}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# get randomly assigned published port&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;gem install bundler&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;bundle config path vendor/bundle&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;bundle install --jobs 4 --retry 3&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;bin/rails db:setup&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To run RSpec tests across parallel jobs you need to set up matrix feature and thanks to that run the whole test suite distributed across jobs.&lt;/p&gt;

&lt;h2 id=&quot;configuring-a-build-matrix&quot;&gt;Configuring a build matrix&lt;/h2&gt;

&lt;p&gt;A build matrix provides different configurations for the virtual environment to test. For instance, a workflow can run a job for more than one supported version of a language, operating system, etc. For each configuration, a copy of the job runs and reports status.&lt;/p&gt;

&lt;p&gt;In case of running parallel tests, you want to run the Rails application on the same Ruby version and Ubuntu system. But you want to split RSpec test suite into 2 sets so half of the tests go to a first parallel job and the second half to another job.&lt;/p&gt;

&lt;p&gt;To split tests you can use Ruby gem &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;Knapsack Pro&lt;/a&gt; that will split tests across parallel GitHub jobs in a dynamic way. Thanks to that each parallel job will be consuming a set of tests fetched from Knapsack Pro API Queue to ensure each parallel job finishes work at a similar time. This allows for evenly distributed tests and no bottleneck in parallel jobs (no slow job). Your CI build will be as fast as possible.&lt;/p&gt;

&lt;p&gt;In our case, you split tests across 2 parallel jobs so you need to set 2 as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matrix.ci_node_total&lt;/code&gt;. Then each parallel job should have assigned index to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matrix.ci_node_index&lt;/code&gt; starting from 0. The first parallel job gets index 0 and the second job gets index 1. This allows &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;Knapsack Pro&lt;/a&gt; to know what tests should be executed on a particular job.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;fail-fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Set N number of parallel jobs you want to run tests on.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Use higher number if you have slow tests to split them on more parallel jobs.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Remember to update ci_node_index below to 0..N-1&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ci_node_total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# set N-1 indexes for parallel jobs&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# When you run 2 parallel jobs then first job will have index 0,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# the second job will have index 1 etc&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You need to specify also API token for &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;Knapsack Pro&lt;/a&gt;, for RSpec it will be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then you can run tests with Knapsack Pro in Queue Mode for RSpec:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;bundle exec rake knapsack_pro:queue:rspec&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;full-github-actions-config-file-for-rails-tests&quot;&gt;Full GitHub Actions config file for Rails tests&lt;/h2&gt;

&lt;p&gt;Here you can find the full YAML configuration file for GitHub Actions and Ruby on Rails project.&lt;/p&gt;

&lt;p&gt;I also recorded video showing how it all works and how CI builds with parallel jobs are configured on GitHub Actions.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/HhvP4HbE_BU&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;script src=&quot;https://gist.github.com/ArturT/a35284f34c2dc0b61a0ad2b4dd4bacae.js&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;dynamic-test-suite-split-with-queue-mode&quot;&gt;Dynamic test suite split with Queue Mode&lt;/h2&gt;

&lt;p&gt;If you would like to better understand how Queue Mode works in Knapsack Pro and what else problems it solves you will find a few useful information in below video.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Also check out another article describing the &lt;a href=&quot;/2019/github-actions-ci-config-for-ruby-on-rails-project-with-mysql-redis-elasticsearch-how-to-run-parallel-tests&quot;&gt;parallelisation setup for GitHub Actions for Rails with MySQL, Redis and Elasticsearch&lt;/a&gt; or learn &lt;a href=&quot;/2020/how-to-run-slow-rspec-files-on-github-actions-with-parallel-jobs-by-doing-an-auto-split-of-the-spec-file-by-test-examples&quot;&gt;how to auto split slow RSpec test files by test examples&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope you find this helpful.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;If you are currently considering moving to GitHub Actions, definitely check out our &lt;a href=&quot;https://knapsackpro.com/ci_comparisons?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs#github-actions&quot;&gt;Comparison of GitHub Actions to other CI solutions&lt;/a&gt;. The most popular ones include &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;Github Actions vs Circle CI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;Github Actions vs Jenkins&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/travis-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs&quot;&gt;Github Actions vs Travis CI&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Sep 2019 20:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/how-to-run-rspec-on-github-actions-for-ruby-on-rails-app-using-parallel-jobs</guid>
        
        
        <category>continuous_integration</category>
        
        <category>github</category>
        
      </item>
    
      <item>
        <title>How to run parallel jobs for RSpec tests on GitLab CI Pipeline and speed up Ruby &amp; JavaScript testing</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com/ci_servers/gitlab-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing&quot;&gt;GitLab CI&lt;/a&gt; allows you to run tests much faster thanks to CI parallelisation feature. You can run parallel jobs across multiple GitLab Runners. In order to do it, you will learn how to split tests in a dynamic way across parallel tasks to ensure there is no bottleneck in GitLab Pipeline. Thanks to that CI build can be run as fast as possible so your &lt;strong&gt;Ruby &amp;amp; JS tests can be finely fast&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing/gitlab.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;GitLab logo&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;gitlab-ci-parallelisation&quot;&gt;GitLab CI parallelisation&lt;/h2&gt;

&lt;p&gt;The common problem, when you want to run tests in parallel to complete your 1-hour test suite in a few minutes instead of waiting hours, is to find a way how to split tests on parallel jobs. Some of your Ruby or JavaScript tests can take milliseconds and some even a few minutes per test file (for instance when using Capybara in RSpec features testing). Problem with slow tests also occurs in E2E (end to end testing) when using &lt;a href=&quot;/2019/cypress-parallel-testing-with-jenkins-pipeline-stages&quot;&gt;Cypress test runner as browser testing&lt;/a&gt; can take quite a long time to execute.&lt;/p&gt;

&lt;p&gt;If you add more parallel GitLab Runners you also may notice that some runners can start work later or not all jobs can be started at the same time (for instance when you run GitLab Runners on your own infrastructure and other CI builds occupies some of the runners).&lt;/p&gt;

&lt;h2 id=&quot;dynamic-test-suite-split-to-eliminate-ci-build-bottlenecks&quot;&gt;Dynamic test suite split to eliminate CI build bottlenecks&lt;/h2&gt;

&lt;p&gt;A solution to optimal tests distribution across parallel jobs (parallel CI nodes) is to distribute test files in smaller chunks across parallel GitLab runners. This ensures active runners can consume and execute your tests while too busy runners with slow tests would run fewer test cases. What is important is to ensure that all parallel runners will finish work at a similar time and thanks to that you won’t see stuck GitLab runner with too much work to process.&lt;/p&gt;

&lt;p&gt;To &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing&quot;&gt;split tests in a dynamic way for Ruby &amp;amp; JavaScript tests you can use Queue Mode in Knapsack Pro&lt;/a&gt;. Below I will explain how Queue Mode works and what problems it solves.&lt;/p&gt;

&lt;iframe style=&quot;width: 100%; max-width: 853; height: 480px&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;gitlab-yaml-config-for-parallel-testing&quot;&gt;GitLab YAML config for parallel testing&lt;/h2&gt;

&lt;p&gt;Here you can find an example config &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitlab-ci.yml&lt;/code&gt; for Ruby on Rails project that has RSpec tests executed across 2 parallel jobs using &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing&quot;&gt;Knapsack Pro Queue Mode&lt;/a&gt;. The similar configuration would be for JavaScript projects with Jest or Cypress tests (&lt;a href=&quot;/&quot;&gt;full list of supported test runners&lt;/a&gt; in Knapsack Pro can be found here).&lt;/p&gt;

&lt;p&gt;Please remember to set API token for &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing&quot;&gt;Knapsack Pro&lt;/a&gt; as environment variable name &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/code&gt; in GitLab Settings -&amp;gt; CI/CD -&amp;gt; Variables (Expand) as a masked variable.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .gitlab-ci.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ruby:2.6&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;database_name&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;postgresql://postgres:postgres@postgres:5432/database_name&quot;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: it is set in Settings -&amp;gt; CI/CD -&amp;gt; Variables (Expand) as masked variable&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;before_script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apt-get update -qq &amp;amp;&amp;amp; apt-get install -y -qq nodejs&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby -v&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;which ruby&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gem install bundler --no-document&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle install --jobs $(nproc)  &quot;${FLAGS[@]}&quot;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Database setup&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bin/rails db:setup&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;rspec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note you can run dozens of parallel jobs by changing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallel&lt;/code&gt; option and thanks to that run the very long test suite in a few minutes instead of waiting hour.&lt;/p&gt;

&lt;iframe style=&quot;width: 100%; max-width: 853; height: 480px&quot; src=&quot;https://www.youtube.com/embed/Td0IKEYn4Zk&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com/ci_servers/gitlab-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing&quot;&gt;GitLab&lt;/a&gt; with its CI/CD tool allows to run fast CI builds thanks to parallelisation of your tests. By using &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing&quot;&gt;Knapsack Pro Queue Mode&lt;/a&gt; you can ensure your tests are split across parallel jobs in an optimal way so your team gets test results as fast as possible.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;If you are looking for an optimal CI solution for your project, check out our comparisons: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/gitlab-ci/vs/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing&quot;&gt;GitLab CI vs Github Actions&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/gitlab-ci/vs/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing&quot;&gt;GitLab vs Circle CI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/jenkins/vs/gitlab-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing&quot;&gt;Jenkins vs GitLab CI&lt;/a&gt;, or &lt;a href=&quot;https://knapsackpro.com/ci_comparisons?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing#gitlab-ci&quot;&gt;GitLab vs other CI&lt;/a&gt; providers.&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 05 Jul 2019 15:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Cucumber testing with Jenkins parallel pipeline to get down CI build time</title>
        <description>&lt;p&gt;Cucumber is a popular automation testing tool for Behaviour-Driven Development (BDD) but when you use it for some time in your work project then the amount of automated tests adds up and you can spend dozens of minutes to run your Cucumber test suite. Sometimes complex projects can have a few hours of execution time for the Cucumber tests. To save time and speed up your Cucumber builds on CI (Continuous Integration) you can use CI parallelization. In this article, you will see how to do it for &lt;a href=&quot;https://knapsackpro.com/ci_servers/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;Jenkins&lt;/a&gt; using Jenkins parallel pipeline.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time/cucumber_jenkins.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Cucumber, Jenkins&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-to-split-cucumber-tests-in-parallel&quot;&gt;How to split Cucumber tests in parallel&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com/ci_servers/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;Jenkins&lt;/a&gt; allows you to configure pipeline as code and use Jenkins pipeline stages to define tasks that will be executed in parallel (at the same time).&lt;/p&gt;

&lt;p&gt;In this example, you will use continuous integration tools like cucumber ruby gem and &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;knapsack_pro gem to split tests across parallel Jenkins stages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you are testing your Ruby on Rails project with Cucumber you want to ensure tests executed in parallel will finish work at a similar time to fully utilize your CI server resources. As you know some test files can have very little tests and other much more test cases. Depend on the test file’s content the execution time for each test file may vary which makes harder to allocate tests in an optimal way across parallel tasks.&lt;/p&gt;

&lt;p&gt;It’s important to split test files from the test suite across each Jenkins parallel stage in a way that you eliminate bottleneck (a slow stage that is running tests longer than other stages). To do that you can &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;split test suite in a dynamic way across parallel stages using Knapsack Pro Queue Mode for Cucumber&lt;/a&gt; test runner. In below video you will learn how Queue Mode works:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;jenkins-pipeline-parallel-cucumber-testing&quot;&gt;Jenkins Pipeline parallel Cucumber testing&lt;/h2&gt;

&lt;p&gt;This is a configuration for Jenkinsfile where you run 4 parallel stages with Cucumber tests using &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;knapsack_pro plugin&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;timeout(time: 60, unit: &apos;MINUTES&apos;) {
  node() {
    stage(&apos;Checkout&apos;) {
      checkout([/* checkout code from git */])

      // determine git commit hash because we need to pass it to knapsack_pro
      COMMIT_HASH = sh(returnStdout: true, script: &apos;git rev-parse HEAD&apos;).trim()

      stash &apos;source&apos;
    }
  }

  def num_nodes = 4; // define your total number of CI nodes (how many parallel jobs will be executed)
  def nodes = [:]

  for (int i = 0; i &amp;lt; num_nodes; i++) {
    def index = i;
    nodes[&quot;ci_node_${i}&quot;] = {
      node() {
        stage(&apos;Setup&apos;) {
          unstash &apos;source&apos;
          // other setup steps
        }

        def knapsack_options = &quot;&quot;&quot;\
            KNAPSACK_PRO_CI_NODE_TOTAL=${num_nodes}\
            KNAPSACK_PRO_CI_NODE_INDEX=${index}\
            KNAPSACK_PRO_COMMIT_HASH=${COMMIT_HASH}\
            KNAPSACK_PRO_BRANCH=${env.BRANCH_NAME}\
        &quot;&quot;&quot;

        // Example how to run Cucumber tests in Knapsack Pro Queue Mode
        // Queue Mode should be a last stage if you have other stages in your pipeline
        // thanks to that it can autobalance CI build time if other tests were not perfectly distributed
        stage(&apos;Run Cucumber tests&apos;) {
          sh &quot;&quot;&quot;KNAPSACK_PRO_CI_NODE_BUILD_ID=${env.BUILD_TAG} ${knapsack_options} bundle exec rake knapsack_pro:queue:cucumber&quot;&quot;&quot;
        }
      }
    }
  }

  parallel nodes // run CI nodes in parallel
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/QWfFiJF1GyU&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Testing big projects is a consuming task. Automation testing with Cucumber can help with that. Your CI server like &lt;a href=&quot;https://knapsackpro.com/ci_servers/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;Jenkins&lt;/a&gt; can help even more when you can leverage parallel test execution with Jenkins stages and by doing optimal test suite split with &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;Knapsack Pro Queue Mode&lt;/a&gt;. The Queue Mode is working also for other &lt;a href=&quot;/&quot;&gt;Ruby or JavaScript test runners&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Are you considering using Jenkins for your project? If so, check out our popular comparison pages: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/jenkins/vs/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;Jenkins vs Github Actions&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/drone/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;Drone vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/aws-codebuild/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;AWS CodeBuild vs Jenkins&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/google-cloud-build/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time&quot;&gt;Cloud Build vs Jenkins&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 15 Jun 2019 15:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/cucumber-testing-with-jenkins-parallel-pipeline-to-get-down-ci-build-time</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>Cypress parallel testing with Jenkins Pipeline stages</title>
        <description>&lt;p&gt;In this tutorial for JavaScript end to end testing, you will learn about Cypress test runner for UI automation testing and how to use it with &lt;a href=&quot;https://knapsackpro.com/ci_servers/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cypress-parallel-testing-with-jenkins-pipeline-stages&quot;&gt;Jenkins CI server&lt;/a&gt;. Cypress helps with frontend automation testing using headless browser or just regular browser. E2E tests often take a long time to run and for bigger projects, those type of tests can take dozens of minutes or even hours. To save developers time you want to load balancing Cypress tests across Jenkins parallel pipeline stages.  Thanks to that you can run your 1-hour test suite in a few minutes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/cypress-parallel-testing-with-jenkins-pipeline-stages/cypress-jenkins.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Cypress Jenkins&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-to-speed-up-cypress-tests&quot;&gt;How to speed up Cypress tests&lt;/h2&gt;

&lt;p&gt;Cypress is a Javascript End to End testing framework that has built in parallelisation but in this article, we will cover Cypress parallel without dashboard service. You want to be able to run tests also when external Cypress dashboard service API is down. It’s possible thanks to &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cypress-parallel-testing-with-jenkins-pipeline-stages&quot;&gt;Knapsack Pro client for Cypress with built-in Fallback Mode&lt;/a&gt;.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/G6ixK4IK-3Y&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;run-cypress-tests-in-parallel-with-jenkins-pipeline&quot;&gt;Run Cypress tests in parallel with Jenkins Pipeline&lt;/h2&gt;

&lt;p&gt;You may want to run Cypress concurrent tests but for that, we will use declarative Jenkins Pipeline with defined &lt;a href=&quot;https://knapsackpro.com/ci_servers/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cypress-parallel-testing-with-jenkins-pipeline-stages&quot;&gt;Jenkins&lt;/a&gt; stages. This allows us to run Cypress parallel tests.&lt;/p&gt;

&lt;p&gt;Let’s look at this Jenkins Pipeline as a code to understand Cypress Jenkins integration:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;timeout(time: 60, unit: &apos;MINUTES&apos;) {
  node() {
    stage(&apos;Checkout&apos;) {
      checkout([/* checkout code from git */])

      // determine git commit hash because we need to pass it to Knapsack Pro
      COMMIT_HASH = sh(returnStdout: true, script: &apos;git rev-parse HEAD&apos;).trim()

      stash &apos;source&apos;
    }
  }

  def num_nodes = 4; // define your total number of CI nodes (how many parallel jobs will be executed)
  def nodes = [:]

  for (int i = 0; i &amp;lt; num_nodes; i++) {
    def index = i;
    nodes[&quot;ci_node_${i}&quot;] = {
      node() {
        stage(&apos;Setup&apos;) {
          unstash &apos;source&apos;
          // other setup steps
        }

        def knapsack_options = &quot;&quot;&quot;\
            KNAPSACK_PRO_CI_NODE_TOTAL=${num_nodes}\
            KNAPSACK_PRO_CI_NODE_INDEX=${index}\
            KNAPSACK_PRO_COMMIT_HASH=${COMMIT_HASH}\
            KNAPSACK_PRO_BRANCH=${env.BRANCH_NAME}\
        &quot;&quot;&quot;

        // Example how to run Cypress tests in Knapsack Pro Queue Mode
        // Queue Mode should be a last stage if you have other stages in your pipeline
        // thanks to that it can autobalance CI build time if other tests were not perfectly distributed
        stage(&apos;Run Cypress in parallel&apos;) {
          sh &quot;&quot;&quot;KNAPSACK_PRO_CI_NODE_BUILD_ID=${env.BUILD_TAG} ${knapsack_options} npx @knapsack-pro/cypress&quot;&quot;&quot;
        }
      }
    }
  }

  parallel nodes // run CI nodes in parallel
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;how-dynamic-tests-split-works-in-knapsack-pro-queue-mode&quot;&gt;How dynamic tests split works in Knapsack Pro Queue Mode&lt;/h2&gt;

&lt;p&gt;To better understand how test suite split works when you allocate tests in a dynamic way across parallel stages please see below video. You will find here more edge cases that can be solved when &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cypress-parallel-testing-with-jenkins-pipeline-stages&quot;&gt;running tests in parallel and how Knapsack Pro Queue Mode helps with CI parallelisation&lt;/a&gt;. Ensuring each of your parallel tasks run a similar amount of time is important to get optimal CI build time and thanks to that save you as much time as possible.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;i&gt;To compare Jenkins with other CI solutions, check out our popular comparison pages: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/jenkins/vs/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cypress-parallel-testing-with-jenkins-pipeline-stages&quot;&gt;Jenkins vs Github Actions&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/drone/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cypress-parallel-testing-with-jenkins-pipeline-stages&quot;&gt;Drone vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/aws-codebuild/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cypress-parallel-testing-with-jenkins-pipeline-stages&quot;&gt;AWS CodeBuild vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/google-cloud-build/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cypress-parallel-testing-with-jenkins-pipeline-stages&quot;&gt;Cloud Build vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=cypress-parallel-testing-with-jenkins-pipeline-stages#jenkins&quot;&gt;Jenkins vs Other CI providers&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 02 Jun 2019 10:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/cypress-parallel-testing-with-jenkins-pipeline-stages</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/cypress-parallel-testing-with-jenkins-pipeline-stages</guid>
        
        
        <category>continuous_integration</category>
        
        <category>Cypress</category>
        
        <category>JavaScript</category>
        
        <category>E2E</category>
        
        <category>testing</category>
        
        <category>Jenkins</category>
        
        <category>Pipeline</category>
        
        <category>stages</category>
        
      </item>
    
      <item>
        <title>Split RSpec tests with Jenkins Parallel Pipeline to run specs faster</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com/ci_servers/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster&quot;&gt;Jenkins CI server&lt;/a&gt; has a declarative pipeline that allows you to set Jenkins parallel stages. You can use the stages to run them at the same time (parallel run) to execute your RSpec test suite in a few smaller faster chunks instead of one long test suite run.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster/rspec_jenkins.jpeg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;RSpec, Jenkins, Ruby&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;running-stages-in-parallel-with-jenkins-workflow-pipeline&quot;&gt;Running stages in parallel with Jenkins workflow pipeline&lt;/h2&gt;

&lt;p&gt;You will use Jenkinsfile and pipeline syntax to get parallel execution of tasks. RSpec tests need to be split in equal time across stages and to do that you need to ensure the time of each RSpec spec file won’t compound on one of the stages because that could lead to bottleneck - a stage that takes more time to run tests than other stages.&lt;/p&gt;

&lt;p&gt;To split RSpec tests evenly you can use &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster&quot;&gt;knapsack_pro gem&lt;/a&gt; with its Queue Mode that will dynamically split specs on parallel Jenkins stages to ensure each stage takes similar amount of time. You can learn more from below video about &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster&quot;&gt;knapsack_pro Queue Mode&lt;/a&gt; and what kind of edge cases it solves when you split tests on CI server.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;stages-in-declarative-jenkins-pipeline&quot;&gt;Stages in Declarative Jenkins Pipeline&lt;/h2&gt;

&lt;p&gt;Pipeline as Code in &lt;a href=&quot;https://knapsackpro.com/ci_servers/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster&quot;&gt;Jenkins&lt;/a&gt; is a very popular way to define your configuration. Here is example for running RSpec tests with &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster&quot;&gt;knapsack_pro ruby gem&lt;/a&gt; to ensure your Ruby on Rails tests are split in optimal way across parallel stages.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;timeout(time: 60, unit: &apos;MINUTES&apos;) {
  node() {
    stage(&apos;Checkout&apos;) {
      checkout([/* checkout code from git */])

      // determine git commit hash because we need to pass it to knapsack_pro
      COMMIT_HASH = sh(returnStdout: true, script: &apos;git rev-parse HEAD&apos;).trim()

      stash &apos;source&apos;
    }
  }

  def num_nodes = 4; // define your total number of CI nodes (how many parallel jobs will be executed)
  def nodes = [:]

  for (int i = 0; i &amp;lt; num_nodes; i++) {
    def index = i;
    nodes[&quot;ci_node_${i}&quot;] = {
      node() {
        stage(&apos;Setup&apos;) {
          unstash &apos;source&apos;
          // other setup steps
        }

        def knapsack_options = &quot;&quot;&quot;\
            KNAPSACK_PRO_CI_NODE_TOTAL=${num_nodes}\
            KNAPSACK_PRO_CI_NODE_INDEX=${index}\
            KNAPSACK_PRO_COMMIT_HASH=${COMMIT_HASH}\
            KNAPSACK_PRO_BRANCH=${env.BRANCH_NAME}\
        &quot;&quot;&quot;

        // Example how to run RSpec tests in Knapsack Pro Queue Mode
        // Queue Mode should be a last stage if you have other stages in your pipeline
        // thanks to that it can autobalance CI build time if other tests were not perfectly distributed
        stage(&apos;Run rspec&apos;) {
          sh &quot;&quot;&quot;KNAPSACK_PRO_CI_NODE_BUILD_ID=${env.BUILD_TAG} ${knapsack_options} bundle exec rake knapsack_pro:queue:rspec&quot;&quot;&quot;
        }
      }
    }
  }

  parallel nodes // run CI nodes in parallel
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Automatic splitting of tests to speed up test stages is a way to ensure your CI builds for Ruby on Rails project are finally as fast as possible. You can learn about &lt;a href=&quot;/2018/jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;Jenkins parallel pipeline for other test runners in Ruby or JavaScript like Cypress or Jest&lt;/a&gt; and how CI parallelisation can help save time with faster testing.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/QWfFiJF1GyU&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;i&gt;If you are currently considering using Jenkins among other solutions, here are some comparison pages that may prove useful to you: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/jenkins/vs/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster&quot;&gt;Jenkins vs Github Actions&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/drone/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster&quot;&gt;Drone vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/aws-codebuild/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster&quot;&gt;AWS CodeBuild vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/google-cloud-build/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster&quot;&gt;Cloud Build vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster#jenkins&quot;&gt;Jenkins vs Other CI providers&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 01 Jun 2019 11:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/split-rspec-tests-with-jenkins-parallel-pipeline-to-run-specs-faster</guid>
        
        
        <category>continuous_integration</category>
        
        <category>Jenkins</category>
        
        <category>Pipeline</category>
        
        <category>Stages</category>
        
        <category>CI</category>
        
        <category>parallelisation</category>
        
        <category>RSpec</category>
        
        <category>Ruby</category>
        
        <category>Rails</category>
        
      </item>
    
      <item>
        <title>How to run tests faster on Heroku CI with parallel dynos</title>
        <description>&lt;p&gt;Heroku provides a CI solution out of the box for teams. Heroku CI can run tests in dyno instance for your project. What is more interesting you can run parallel dynos as part of your CI build. This allows you to split tests on parallel dynos to complete CI build faster and save time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos/heroku.jpg&quot; style=&quot;width:300px;border:none;box-shadow:none;margin-left: 15px;float:right;&quot; alt=&quot;Heroku&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Heroku charges you for seconds spent in runtime for each dyno. It means instead of running your slow test suite on a single dyno you could split it across multiple dynos and pay more or less the same and significantly reduce the CI build time for your project.&lt;/p&gt;

&lt;h2 id=&quot;how-to-start-with-heroku-ci&quot;&gt;How to start with Heroku CI&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you already have a team in Heroku dashboard then you can use Heroku CI and run tests for your project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Heroku, you can open your team and particular pipeline for one of your projects. You can find there a &lt;i&gt;Tests&lt;/i&gt; tab where you can enable Heroku CI.&lt;/p&gt;

&lt;p&gt;You also need an &lt;i&gt;app.json&lt;/i&gt; file in a repository of your project. The file contains information about what is needed to run the project on Heroku. You can add to the &lt;i&gt;app.json&lt;/i&gt; file additional configuration needed for Heroku CI.&lt;/p&gt;

&lt;p&gt;In order to use Heroku CI Parallel Test Runs, we need to have it enabled. You can ask Heroku support to activate it for your project. This feature allows to run up to 32 parallel dynos for your CI build.&lt;/p&gt;

&lt;p&gt;You can also watch all the steps on more detailed video or copy some examples from this article.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/4lJVzdA11OQ&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Below you find examples how to split tests on Heroku CI for Ruby and JavaScript projects.&lt;/p&gt;

&lt;h2 id=&quot;how-to-run-parallel-dynos-for-test-suite-in-ruby-on-rails-project&quot;&gt;How to run parallel dynos for test suite in Ruby on Rails project&lt;/h2&gt;

&lt;p&gt;You can split Ruby tests written in RSpec, Minitest, Cucumber or other tests runners across parallel dynos in a dynamic way where all dynos will finish work at similar time so you get results about your test suite being green or red as soon as possible. To do it you can use &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos&quot;&gt;Knapsack Pro&lt;/a&gt; tool with its &lt;a href=&quot;https://youtu.be/hUEB1XDKEFY&quot;&gt;Queue Mode for dynamic test suite split&lt;/a&gt;. With &lt;i&gt;quantity&lt;/i&gt; key, you can set how many parallel dynos you want to use to run your &lt;i&gt;scripts test&lt;/i&gt; command.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;environments&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;test&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;formation&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;test&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;quantity&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;mi&quot;&gt;2&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;nl&quot;&gt;&quot;addons&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;s2&quot;&gt;&quot;heroku-postgresql:in-dyno&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;heroku-redis:in-dyno&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;nl&quot;&gt;&quot;scripts&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;test&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;bundle exec rake knapsack_pro:queue:rspec&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;nl&quot;&gt;&quot;env&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;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&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;rspec-token&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;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can also change dyno size for CI build. If you run complex tests that need more CPU or memory then you could add &lt;i&gt;size&lt;/i&gt; parameter to &lt;i&gt;app.json&lt;/i&gt; to define &lt;a href=&quot;https://devcenter.heroku.com/articles/dyno-types&quot;&gt;dyno type&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;environments&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;test&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;formation&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;test&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;quantity&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;mi&quot;&gt;1&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;size&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;performance-l&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;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;run-javascript-tests-across-parallel-heroku-ci-dynos-for-cypress-e2e-test-suite&quot;&gt;Run JavaScript tests across parallel Heroku CI dynos for Cypress E2E test suite&lt;/h2&gt;

&lt;p&gt;End-to-end tests (E2E) often takes a lot of time because clicking through multiple scenarios of your website is time-consuming. Splitting Cypress test suite on multiple dynos will help us save a lot of time and keep CI build fast.&lt;/p&gt;

&lt;p&gt;We can use &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress&quot;&gt;@knapsack-pro/cypress&lt;/a&gt; project for that. It uses &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos&quot;&gt;Knapsack Pro Queue Mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a config for your &lt;i&gt;app.json&lt;/i&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;environments&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;test&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;formation&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;test&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;quantity&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;mi&quot;&gt;2&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;nl&quot;&gt;&quot;addons&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;s2&quot;&gt;&quot;heroku-postgresql:in-dyno&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;heroku-redis:in-dyno&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;nl&quot;&gt;&quot;scripts&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;test&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;npx @knapsack-pro/cypress&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;nl&quot;&gt;&quot;env&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;KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS&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;example&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;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you would like to learn more about Cypress then check the video in an article about &lt;a href=&quot;/2018/run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;running javascript E2E tests with Cypress on parallel CI nodes&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;run-jest-tests-in-javascript-across-parallel-heroku-ci-dynos&quot;&gt;Run Jest tests in JavaScript across parallel Heroku CI dynos&lt;/h2&gt;

&lt;p&gt;We can use &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-jest&quot;&gt;@knapsack-pro/jest&lt;/a&gt; client library to split your Jest tests. It uses &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos&quot;&gt;Knapsack Pro Queue Mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a config for your &lt;i&gt;app.json&lt;/i&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;environments&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;test&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;formation&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;test&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;quantity&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;mi&quot;&gt;2&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;nl&quot;&gt;&quot;addons&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;s2&quot;&gt;&quot;heroku-postgresql:in-dyno&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;heroku-redis:in-dyno&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;nl&quot;&gt;&quot;scripts&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;test&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;npx @knapsack-pro/jest&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;nl&quot;&gt;&quot;env&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;KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST&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;example&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;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;You can find all integrations for various test runners in installation guide.&lt;br /&gt;
&lt;a href=&quot;/&quot;&gt;See supported test runners in Knapsack Pro&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Heroku CI is a great addition to Heroku. If you already use Heroku you can easily leverage parallel dynos and keep low costs of your CI as you pay only for dyno usage. The great thing is that you can run many parallel CI dynos with a subset of your test suite to save a ton of time for your engineering team. You can learn more about how &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos&quot;&gt;Knapsack Pro can help you save time with faster CI builds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Learn more about Queue Mode and what problems it solves in below video.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;i&gt;If you are currently considering a choice between Heroku CI and other CI solutions, check out our comparison pages: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/heroku-ci/vs/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos&quot;&gt;Heroku CI vs Circle CI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/heroku-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos&quot;&gt;Github Actions vs Heroku CI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/netlify-build/vs/heroku-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos&quot;&gt;Netlify vs Heroku&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos#heroku-ci&quot;&gt;Heroku CI vs Other CIs&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 25 May 2019 16:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/how-to-run-tests-faster-on-heroku-ci-with-parallel-dynos</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
      <item>
        <title>CodeClimate and CircleCI 2.0 parallel builds config for RSpec with SimpleCov and JUnit formatter</title>
        <description>&lt;p&gt;How to merge CodeClimate reports for your RSpec test suite executed with parallel builds on CircleCI 2.0? You will learn how to run RSpec parallel tests for your for Ruby on Rails project using &lt;a href=&quot;https://knapsackpro.com/ci_servers/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=codeclimate-and-circleci-2-0-parallel-builds-config-for-rspec-with-simplecov-and-junit-formatter&quot;&gt;CircleCI&lt;/a&gt; and how to send test coverage merged from parallel jobs into CodeClimate. We will cover config examples for:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/codeclimate-and-circleci-2-0-parallel-builds-config-for-rspec-with-simplecov-and-junit-formatter/codeclimate.jpg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;CodeClimate&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;How to use SimpleCov needed by CodeClimate Test Reporter to prepare RSpec test coverage summary on each parallel job and then how to merge it so you will be able to send it to CodeClimate dashboard.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;How to use JUnit formatter for RSpec running on parallel jobs. Thanks to JUnit formatter you can see nice tests results in CircleCI UI view. For instance, when your tests fail then &lt;a href=&quot;https://knapsackpro.com/ci_servers/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=codeclimate-and-circleci-2-0-parallel-builds-config-for-rspec-with-simplecov-and-junit-formatter&quot;&gt;CircleCI&lt;/a&gt; will show you failing tests at the top of your CI build steps.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;How to &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=codeclimate-and-circleci-2-0-parallel-builds-config-for-rspec-with-simplecov-and-junit-formatter&quot;&gt;split your RSpec tests across parallel jobs using Knapsack Pro Queue Mode&lt;/a&gt;. At the end of the article, you will see a video explaining how Queue Mode in knapsack_pro ruby gem dynamically distributes specs across parallel jobs to ensure each job takes a similar amount of time to ensure CI build is as fast as possible (to get you optimal CI build time).&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;codeclimate-and-parallel-builds-on-circleci-20&quot;&gt;CodeClimate and parallel builds on CircleCI 2.0&lt;/h2&gt;

&lt;p&gt;Let’s start with the full YAML config for CircleCI 2.0. You will find comment for each important step and what it does. You may have already configured some of the stuff in your project so looking at a full example below might be more familiar to you. If you are just adding CodeClimate to your project for the first time then except below config you will need to setup simplecov gem and it’s covered in next section.&lt;/p&gt;

&lt;p&gt;Here is the full CircleCI 2.0 example for parallel builds using RSpec and CodeClimate.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .circleci/config.yml&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Specify the number of parallel jobs to run. Increasing the parallelism speeds your CI build.&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parallelism&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# specify the version you desire here&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/ruby:2.6.3-node-browsers&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;127.0.0.1&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rails-app-with-knapsack_pro&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RACK_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;

          &lt;span class=&quot;c1&quot;&gt;# API token should be set in CircleCI environment variables settings instead of here&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: rspec-token&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Specify service dependencies here if necessary&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# CircleCI maintains a library of pre-built images&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# documented at https://circleci.com/docs/2.0/circleci-images/&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/postgres:9.4.12-alpine&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rails-app-with-knapsack_pro_test&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rails-app-with-knapsack_pro&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;working_directory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;~/repo&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Install Code Climate Test Reporter&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 &amp;gt; ./cc-test-reporter&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;chmod +x ./cc-test-reporter&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./cc-test-reporter before-build&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Download and cache dependencies&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;restore_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# NOTE: remove space between { { here and in all below examples&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v2-dependencies-bundler-{ { checksum &quot;Gemfile.lock&quot; }}&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# fallback to using the latest cache if no exact match is found&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v2-dependencies-bundler-&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;install ruby dependencies&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;bundle install --jobs=4 --retry=3 --path vendor/bundle&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;save_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# NOTE: remove space between { { here&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v2-dependencies-bundler-{ { checksum &quot;Gemfile.lock&quot; }}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# wait for postgres to be available&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dockerize -wait tcp://localhost:5432 -timeout 1m&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Database setup&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bin/rails db:setup&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Run RSpec tests with knapsack_pro Queue Mode and use junit formatter&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# junit formatter must be configured as described in FAQ for knapsack_pro Queue Mode&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# this is also described in this article later&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# https://docs.knapsackpro.com/ruby/circleci/#collect-metadata-in-queue-mode&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;run tests&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;mkdir -p /tmp/test-results&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;bundle exec rake &quot;knapsack_pro:queue:rspec[--format documentation --format RspecJunitFormatter --out /tmp/test-results/rspec.xml]&quot;&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Code Climate Test Coverage&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;./cc-test-reporter format-coverage -t simplecov -o &quot;coverage/codeclimate.$CIRCLE_NODE_INDEX.json&quot;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# store coverage directory with CodeClimate reports prepared based on simplecov reports&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# it&apos;s special step used to persist a temporary file to be used by another job in the workflow&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;persist_to_workspace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;coverage&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;codeclimate.*.json&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# store test reports created with junit formatter in order to allow CircleCI&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# show info about executed tests in UI on top of CI build steps&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;store_test_results&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/tmp/test-reports&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# store test reports created with junit formatter in order to allow CircleCI&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# let you browse recorded xml files in Artifacts tab&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;store_artifacts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/tmp/test-reports&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;upload-coverage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/ruby:2.6.3-node&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# you can add your CodeClimate test report ID here or in CircleCI&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# settings for environment variables&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;CC_TEST_REPORTER_ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;use-here-your-codeclimate-test-report-id&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;working_directory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;~/repo&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# This will restore files from persist_to_workspace step&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Thanks to it we will have access to CodeClimate test coverage files from&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# each parallel job. We need them in order to merge it into one file in next step.&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;attach_workspace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;~/repo&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Install Code Climate Test Reporter&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 &amp;gt; ./cc-test-reporter&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;chmod +x ./cc-test-reporter&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# merge CodeClimate files from each parallel job into sum coverage&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# and then upload it to CodeClimate dashboard&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;./cc-test-reporter sum-coverage --output - codeclimate.*.json | ./cc-test-reporter upload-coverage --debug --input -&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;workflows&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# run our CI build with tests&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# once CI build is completed then we merge CodeClimate reports&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# from each parallel job and upload summary coverage to CodeClimate&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;upload-coverage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;requires&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
             &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;simplecov-configuration-for-rspec&quot;&gt;SimpleCov configuration for RSpec&lt;/h2&gt;

&lt;p&gt;When you use &lt;a href=&quot;https://github.com/simplecov-ruby/simplecov&quot;&gt;simplecov&lt;/a&gt; with RSpec, you have to set a unique name for its report with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SimpleCov.command_name&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/rails_helper.rb or spec/spec_helper.rb&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;simplecov&apos;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;SimpleCov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# this is needed when you use knapsack_pro Queue Mode&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;KnapsackPro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hooks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;before_queue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;SimpleCov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;command_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rspec_ci_node_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;KnapsackPro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ci_node_index&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;junit-formatter-for-rspec&quot;&gt;JUnit formatter for RSpec&lt;/h2&gt;

&lt;p&gt;In order to show in CircleCI UI info about your test suite like failed tests, you need to generate xml report for your RSpec test suite using JUnit formatter.&lt;/p&gt;

&lt;p&gt;You can use junit formatter for RSpec thanks to gem &lt;a href=&quot;https://github.com/sj26/rspec_junit_formatter&quot;&gt;rspec_junit_formatter&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# knapsack_pro Queue Mode&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;bundle&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rake&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;knapsack_pro:queue:rspec[--format documentation --format RspecJunitFormatter --out /tmp/test-reports/rspec.xml]&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Learn more about &lt;a href=&quot;https://docs.knapsackpro.com/ruby/circleci/#collect-metadata-in-queue-mode&quot;&gt;collecting metadata in CircleCI&lt;/a&gt; in the docs.&lt;/p&gt;

&lt;h2 id=&quot;summary-and-queue-mode-to-do-dynamic-test-suite-split&quot;&gt;Summary and Queue Mode to do dynamic test suite split&lt;/h2&gt;

&lt;p&gt;CI builds can be much faster thanks to leveraging parallel jobs on Circle CI 2.0 and CI parallelisation on any CI provider (see more &lt;a href=&quot;/continuous_integration/&quot;&gt;parallelisation examples for your CI providers&lt;/a&gt;). You can check &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=codeclimate-and-circleci-2-0-parallel-builds-config-for-rspec-with-simplecov-and-junit-formatter&quot;&gt;Knapsack Pro tool for CI parallelisation&lt;/a&gt; and learn more about Queue Mode and what problems it solves in below video.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;a href=&quot;/knapsack_pro-ruby/guide/&quot;&gt;Installation guide for knapsack_pro gem&lt;/a&gt; can be found here in order to setup your RSpec test suite.&lt;/p&gt;
</description>
        <pubDate>Fri, 24 May 2019 18:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/codeclimate-and-circleci-2-0-parallel-builds-config-for-rspec-with-simplecov-and-junit-formatter</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/codeclimate-and-circleci-2-0-parallel-builds-config-for-rspec-with-simplecov-and-junit-formatter</guid>
        
        
        <category>techtips</category>
        
        <category>CircleCI</category>
        
        <category>CodeClimate</category>
        
        <category>simplecov</category>
        
        <category>Ruby</category>
        
        <category>Rails</category>
        
      </item>
    
      <item>
        <title>How to merge CodeClimate reports for parallel jobs (CI nodes)</title>
        <description>&lt;p&gt;If you run tests on parallel jobs (using CI parallelisation) you need to merge CodeClimate reports from each parallel job (CI node) into a unified report.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/run-parallel-jobs-on-semaphore-ci-2-0-to-get-faster-ci-build-time/semaphore-ci-logo.png&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Semaphore CI&quot; /&gt;&lt;/p&gt;

&lt;p&gt;An additional problem may happen that some of your parallel jobs (one of your parallel CI node) may not have reported CodeClimate result when tests were never executed on that CI node.&lt;/p&gt;

&lt;p&gt;It could happen when you run tests with dynamic tests allocation across parallel jobs using &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-merge-codeclimate-reports-for-parallel-jobs-ci-nodes&quot;&gt;Knapsack Pro Queue Mode&lt;/a&gt;. For instance if once of CI node started running tests after other CI nodes already executed the whole test suite distributed for particular CI build then the node has no CodeClimate report.&lt;/p&gt;

&lt;p&gt;Here is a list of steps we need to follow to ensure we can correctly prepare a final report for CodeClimate.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2019/run-parallel-jobs-on-semaphore-ci-2-0-to-get-faster-ci-build-time&quot;&gt;run tests split across parallel jobs (parallel CI nodes) with dynamic test suite split&lt;/a&gt; called Knapsack Pro Queue Mode&lt;/li&gt;
  &lt;li&gt;if one of CI node did not execute tests then knapsack_pro will log info to log file. We should grep the log file and if there were no tests executed then we don’t generate CodeClimate report based on simple-cov&lt;/li&gt;
  &lt;li&gt;as last step we merge all generated CodeClimate reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is the full example for Semaphore CI 2.0 config but it applies to any other CI provider, just use syntax specifically for your CI tool.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1.0&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;My app&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;agent&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;e1-standard-2&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;os_image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu1804&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Install dependencies&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;agent&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;e1-standard-4&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;os_image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu1804&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;env_vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RAILS_ENV&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;test&quot;&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;semaphore_secrets&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build gems and assets&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache restore $SEMAPHORE_PROJECT_NAME-cc-test-reporter&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;if [ -d &apos;cc-reporter&apos; ]; then&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;echo &apos;Found cc-reporter in cache&apos;;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;else&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;mkdir cc-reporter;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 &amp;gt; ./cc-reporter/cc-test-reporter;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;chmod +x ./cc-reporter/cc-test-reporter;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;cache store $SEMAPHORE_PROJECT_NAME-cc-test-reporter cc-reporter;&lt;/span&gt;
              &lt;span class=&quot;s&quot;&gt;fi&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run specs&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;env_vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RAILS_ENV&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;test&quot;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# write knapsack_pro logs to file instead of stdout:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# log/knapsack_pro_node_0.log&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# log/knapsack_pro_node_1.log&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_LOG_DIR&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;log&quot;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# text we expect to find in logs when there was no tests executed for particular parallel job (CI node)&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;NO_TEST_EXECUTED_STR&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[knapsack_pro]&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;No&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;were&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;executed&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CI&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;node&quot;&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;semaphore_secrets&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;prologue&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache restore $SEMAPHORE_PROJECT_NAME-cc-test-reporter&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mkdir -p coverage/final&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./cc-reporter/cc-test-reporter before-build&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# run 2 parallel jobs with Knapsack Pro Queue Mode&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RSpec node 0 - Knapsack Pro&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2 KNAPSACK_PRO_CI_NODE_INDEX=0 bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;STR_COUNT=`grep -F &quot;$NO_TEST_EXECUTED_STR&quot; log/knapsack_pro_node_0.log | wc -l | tr -d &apos; &apos;`&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;if [ $STR_COUNT -eq 0 ]; then ./cc-reporter/cc-test-reporter format-coverage -t simplecov --output &quot;coverage/final/codeclimate.$SEMAPHORE_JOB_ID.json&quot;; fi&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache store coverage-0-$SEMAPHORE_WORKFLOW_ID coverage/final&lt;/span&gt;

        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RSpec node 1 - Knapsack Pro&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2 KNAPSACK_PRO_CI_NODE_INDEX=1 bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;STR_COUNT=`grep -F &quot;$NO_TEST_EXECUTED_STR&quot; log/knapsack_pro_node_1.log | wc -l | tr -d &apos; &apos;`&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;if [ $STR_COUNT -eq 0 ]; then ./cc-reporter/cc-test-reporter format-coverage -t simplecov --output &quot;coverage/final/codeclimate.$SEMAPHORE_JOB_ID.json&quot;; fi&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache store coverage-1-$SEMAPHORE_WORKFLOW_ID coverage/final&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Post-test tasks&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;semaphore_secrets&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CodeClimate&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache restore $SEMAPHORE_PROJECT_NAME-cc-test-reporter&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# we have 2 parallel jobs so 0..1 range&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;for i in {0..1}; do cache restore coverage-$i-$SEMAPHORE_WORKFLOW_ID; done&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;FILES_COUNT=`ls -1 coverage/final | wc -l | tr -d &apos; &apos;`&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./cc-reporter/cc-test-reporter sum-coverage --output - --parts $FILES_COUNT coverage/final/codeclimate.*.json | ./cc-reporter/cc-test-reporter upload-coverage --input -&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;for i in {0..1}; do cache delete coverage-$i-$SEMAPHORE_WORKFLOW_ID; done&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary-and-queue-mode-for-dynamic-test-suite-split&quot;&gt;Summary and Queue Mode for dynamic test suite split&lt;/h2&gt;

&lt;p&gt;CI builds can be much faster thanks to leveraging parallel jobs on Semaphore CI 2.0 and CI parallelisation on any CI provider (&lt;a href=&quot;/continuous_integration/&quot;&gt;see more parallelisation examples for your CI providers&lt;/a&gt;). You can check &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-merge-codeclimate-reports-for-parallel-jobs-ci-nodes&quot;&gt;Knapsack Pro tool for CI parallelisation&lt;/a&gt; and learn more about Queue Mode and what problems it solves in below video.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
</description>
        <pubDate>Wed, 27 Mar 2019 21:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/how-to-merge-codeclimate-reports-for-parallel-jobs-ci-nodes</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/how-to-merge-codeclimate-reports-for-parallel-jobs-ci-nodes</guid>
        
        
        <category>techtips</category>
        
        <category>CodeClimate</category>
        
        <category>Semaphore</category>
        
      </item>
    
      <item>
        <title>Run parallel jobs on Semaphore CI 2.0 to get faster CI build time</title>
        <description>&lt;p&gt;Semaphore CI 2.0 allows configuring your CI build task with parallel jobs. This way you can run simultaneously a few different commands that do not depend on each other. But we could also use parallel jobs to split your test suite across a few jobs and this way save time. I will show you how to speed up your CI build for Ruby or JavaScript project (Rails / Node project).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/run-parallel-jobs-on-semaphore-ci-2-0-to-get-faster-ci-build-time/semaphore-ci-logo.png&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Semaphore CI&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With &lt;a href=&quot;https://semaphore.io&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;Semaphore CI 2.0&lt;/a&gt; you don’t pay for a reserved amount of containers that can be run in parallel as in some other CI providers. Instead, they count the amount of work time spent on running containers. This creates an incentive to run more parallel jobs to execute our tests fast and still keep bill at a similar level as if we would just run all tests in single container waisting our own time.&lt;/p&gt;

&lt;h2 id=&quot;lets-save-time-with-parallel-jobs&quot;&gt;Let’s save time with parallel jobs&lt;/h2&gt;

&lt;p&gt;In order to run parallel jobs with our tests in an optimal way we need to ensure each job will finish work at a similar time. This way there will be no bottleneck like job executing too many tests or too slow tests. The slow job could affect and made our whole CI build slower. Especially end to end tests (E2E) can be very slow and their time execution can vary.&lt;/p&gt;

&lt;p&gt;You can split tests across parallel jobs in a dynamic way to ensure all jobs complete work at a similar time using the &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-parallel-jobs-on-semaphore-ci-2-0-to-get-faster-ci-build-time&quot;&gt;Knapsack Pro Queue Mode&lt;/a&gt;. You can learn more about what else problems can be solved with Queue Mode in the video at the very end of this article but right now let’s jump to the Semaphore CI 2.0 demo example and the config examples we could use.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/-oKCIYSk6yg&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Here you can find Semaphore CI 2.0 config for projects using:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#ruby-on-rails-config-for-semaphore-20&quot;&gt;Ruby on Rails&lt;/a&gt; (RSpec, other tests runners like Minitest, Cucumber and so on are also supported)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#cypressio-config-for-semaphore-20&quot;&gt;JavaScript tests in Cypress.io&lt;/a&gt; end to end test runner&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#jest-config-for-semaphore-20&quot;&gt;JavaScript tests in Jest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;ruby-on-rails-config-for-semaphore-20&quot;&gt;Ruby on Rails config for Semaphore 2.0&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; gem supports environment variables provided by Semaphore CI 2.0 to run your tests. You will have to define a few things in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.semaphore/semaphore.yml&lt;/code&gt; config file.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You need to set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/code&gt;. If you don’t want to commit secrets in yml file then you can &lt;a href=&quot;https://docs.semaphoreci.com/using-semaphore/secrets&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;follow this guide&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;You should create as many parallel jobs as you need with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallelism&lt;/code&gt; property. If your test suite is long you should use more parallel jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below you can find full Semaphore CI 2.0 config for Rails project.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .semaphore/semaphore.yml&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Use the latest stable version of Semaphore 2.0 YML syntax:&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1.0&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Name your pipeline. In case you connect multiple pipelines with promotions,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# the name will help you differentiate between, for example, a CI build phase&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# and delivery phases.&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Demo Rails 5 app&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# An agent defines the environment in which your code runs.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# It is a combination of one of available machine types and operating&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# system images.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# See https://docs.semaphoreci.com/article/20-machine-types&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# and https://docs.semaphoreci.com/article/32-ubuntu-1804-image&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;agent&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;e1-standard-2&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;os_image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu1804&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Blocks are the heart of a pipeline and are executed sequentially.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Each block has a task that defines one or more jobs. Jobs define the&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# commands to execute.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# See https://docs.semaphoreci.com/article/62-concepts&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Setup&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;env_vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RAILS_ENV&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Checkout code from Git repository. This step is mandatory if the&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# job is to work with your code.&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Optionally you may use --use-cache flag to avoid roundtrip to&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# remote repository.&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# See https://docs.semaphoreci.com/article/54-toolbox-reference#libcheckout&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Restore dependencies from cache.&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Read about caching: https://docs.semaphoreci.com/article/54-toolbox-reference#cache&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH-,gems-main-&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Set Ruby version:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sem-version ruby 2.6.1&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle install --jobs=4 --retry=3 --path vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Store the latest version of dependencies in cache,&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# to be used in next blocks and future workflows:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache store gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock) vendor/bundle&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RSpec tests&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;env_vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RAILS_ENV&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PGHOST&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;127.0.0.1&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PGUSER&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;your_api_token_here&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# This block runs two jobs in parallel and they both share common&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# setup steps. We can group them in a prologue.&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# See https://docs.semaphoreci.com/article/50-pipeline-yaml#prologue&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;prologue&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH-,gems-main-&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Start Postgres database service.&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# See https://docs.semaphoreci.com/article/54-toolbox-reference#sem-service&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sem-service start postgres&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sem-version ruby 2.6.1&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle install --jobs=4 --retry=3 --path vendor/bundle&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake db:setup&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests with Knapsack Pro&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;parallelism&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Step for RSpec in Queue Mode&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Step for Cucumber in Queue Mode&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:queue:cucumber&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Step for RSpec in Regular Mode&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:rspec&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Step for Cucumber in Regular Mode&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:cucumber&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Step for Minitest in Regular Mode&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:minitest&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Step for test-unit in Regular Mode&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:test_unit&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Step for Spinach in Regular Mode&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake knapsack_pro:spinach&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;cypressio-config-for-semaphore-20&quot;&gt;Cypress.io config for Semaphore 2.0&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/cypress&lt;/code&gt; supports environment variables provided by Semaphore CI 2.0 to run your tests. You will have to define a few things in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.semaphore/semaphore.yml&lt;/code&gt; config file.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You need to set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS&lt;/code&gt;. If you don’t want to commit secrets in yml file then you can &lt;a href=&quot;https://docs.semaphoreci.com/using-semaphore/secrets&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;follow this guide&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;You should create as many parallel jobs as you need with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallelism&lt;/code&gt; property. If your test suite is long you should use more parallel jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below you can find example part of Semaphore CI 2.0 config.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Cypress tests&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;env_vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;your_api_token_here&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;prologue&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nvm install --lts carbon&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sem-version node --lts carbon&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests with Knapsack Pro&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;parallelism&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npx @knapsack-pro/cypress&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;jest-config-for-semaphore-20&quot;&gt;Jest config for Semaphore 2.0&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@knapsack-pro/jest&lt;/code&gt; supports environment variables provided by Semaphore CI 2.0 to run your tests. You will have to define a few things in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.semaphore/semaphore.yml&lt;/code&gt; config file.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You need to set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST&lt;/code&gt;. If you don’t want to commit secrets in yml file then you can &lt;a href=&quot;https://docs.semaphoreci.com/using-semaphore/secrets&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;follow this guide&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;You should create as many parallel jobs as you need with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallelism&lt;/code&gt; property. If your test suite is long you should use more parallel jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below you can find example part of Semaphore CI 2.0 config.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;blocks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Cypress tests&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;env_vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_JEST&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;your_api_token_here&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;prologue&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nvm install --lts carbon&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sem-version node --lts carbon&lt;/span&gt;

      &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run tests with Knapsack Pro&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;parallelism&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npx @knapsack-pro/jest&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;the-queue-mode-and-summary&quot;&gt;The Queue Mode and summary&lt;/h2&gt;

&lt;p&gt;As you can see your CI builds can be much faster thanks to leveraging parallel jobs on Semaphore CI 2.0. You can check &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-parallel-jobs-on-semaphore-ci-2-0-to-get-faster-ci-build-time&quot;&gt;Knapsack Pro tool for CI parallelisation&lt;/a&gt; and learn more about Queue Mode and what problems it solves in below video.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
</description>
        <pubDate>Sat, 16 Mar 2019 14:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/run-parallel-jobs-on-semaphore-ci-2-0-to-get-faster-ci-build-time</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/run-parallel-jobs-on-semaphore-ci-2-0-to-get-faster-ci-build-time</guid>
        
        
        <category>continuous_integration</category>
        
        <category>semaphore</category>
        
        <category>Ruby</category>
        
        <category>Rails</category>
        
        <category>JavaScript</category>
        
        <category>Node</category>
        
      </item>
    
      <item>
        <title>Handle invalid JSON payload in Rails 5+ API. Catch JSON parse error</title>
        <description>&lt;p&gt;When someone post invalid JSON payload to your Ruby on Rails URL endpoint then Rails crashes with 500 error.
We could render nice message instead of the error. For instance if you have public API you may want to show reason of the error to explain to your API client why the request failed.&lt;/p&gt;

&lt;p&gt;We can catch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActionDispatch::Http::Parameters::ParseError&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ApplicationController&lt;/code&gt;. If you want to just catch this exception only for your API endpoints exposed by Rails then put the below code in proper API base controller.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;API&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# TODO remove this when a new version of Rails &amp;gt; 5.2.2 will be released&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# https://github.com/rails/rails/issues/34244#issuecomment-433365579&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process_action&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;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionDispatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Parameters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ParseError&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &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;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;errors: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;message&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;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# This will work for Rails &amp;gt; 5.2.2&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# https://github.com/rails/rails/pull/34341#issuecomment-434727301&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rescue_from&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionDispatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Parameters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ParseError&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &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;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;errors: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cause&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;message&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;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Above tip works for Rails 5+. Hope you will find this useful. If you want to learn more how to faster test your API and Rails application then check out &lt;a href=&quot;/blog/&quot;&gt;blog&lt;/a&gt; and website about &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=tech_tips&amp;amp;utm_campaign=handle-invalid-json-payload-in-rails-5-api-catch-json-parse-error&quot;&gt;knapsack_pro gem for CI parallelisation&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Sun, 10 Feb 2019 11:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2019/handle-invalid-json-payload-in-rails-5-api-catch-json-parse-error</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2019/handle-invalid-json-payload-in-rails-5-api-catch-json-parse-error</guid>
        
        
        <category>techtips</category>
        
        <category>Ruby</category>
        
        <category>Rails</category>
        
        <category>API</category>
        
        <category>JSON</category>
        
      </item>
    
      <item>
        <title>Jenkins Pipeline how to run parallel tests in your workflow stages</title>
        <description>&lt;p&gt;Jenkins Pipeline is a suite of plugins that allows creating simple-to-complex build stages for your testing environment on CI.  We can use Jenkins Pipeline to run a few stages at the same time and thanks to that parallelize test suite across a few stages to complete tests faster.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages/jenkins.jpg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;Jenkins&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In order to run parallel stages with &lt;a href=&quot;https://www.jenkins.io/doc/book/pipeline/&quot;&gt;Jenkins Pipeline&lt;/a&gt;, you need a proper Jenkinsfile which represents our delivery pipeline as code via the Pipeline DSL.&lt;/p&gt;

&lt;p&gt;Another thing we will have to figure out is the problem how to divide our test suite across parallel stages in a way that each subset of test suite executed across all stages will complete work at the same time. It’s important to complete the tests on all stages at a similar time to run our CI build as fast as possible and eliminate bottleneck stage.&lt;/p&gt;

&lt;h2 id=&quot;how-to-split-test-suite-evenly-across-parallel-jenkins-stages&quot;&gt;How to split test suite evenly across parallel Jenkins stages&lt;/h2&gt;

&lt;p&gt;To divide our tests across parallel stages we can use &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;Knapsack Pro&lt;/a&gt; which allows to dynamically allocate tests across stages (also known as CI nodes). This way we will run our parallelised tests in optimal time.&lt;/p&gt;

&lt;p&gt;Here you can learn more how dynamic test suite allocation works and with what else problems it can help.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;jenkinsfile-for-parallel-pipeline&quot;&gt;Jenkinsfile for parallel pipeline&lt;/h2&gt;

&lt;p&gt;In this example, I will show you how to split your Ruby and JavaScript tests across parallel &lt;a href=&quot;https://knapsackpro.com/ci_servers/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;Jenkins&lt;/a&gt; stages.&lt;/p&gt;

&lt;p&gt;This is Ruby example how to split Cucumber and RSpec test suite. Other test runners like Minitest, Test::Unit etc are available as well at &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;Knapsack Pro&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;timeout(time: 60, unit: &apos;MINUTES&apos;) {
  node() {
    stage(&apos;Checkout&apos;) {
      checkout([/* checkout code from git */])

      // determine git commit hash because we need to pass it to knapsack_pro
      COMMIT_HASH = sh(returnStdout: true, script: &apos;git rev-parse HEAD&apos;).trim()

      stash &apos;source&apos;
    }
  }

  def num_nodes = 4; // define your total number of CI nodes (how many parallel jobs will be executed)
  def nodes = [:]

  for (int i = 0; i &amp;lt; num_nodes; i++) {
    def index = i;
    nodes[&quot;ci_node_${i}&quot;] = {
      node() {
        stage(&apos;Setup&apos;) {
          unstash &apos;source&apos;
          // other setup steps
        }

        def knapsack_options = &quot;&quot;&quot;\
            KNAPSACK_PRO_CI_NODE_TOTAL=${num_nodes}\
            KNAPSACK_PRO_CI_NODE_INDEX=${index}\
            KNAPSACK_PRO_COMMIT_HASH=${COMMIT_HASH}\
            KNAPSACK_PRO_BRANCH=${env.BRANCH_NAME}\
        &quot;&quot;&quot;

        // example how to run cucumber tests in Knapsack Pro Regular Mode
        stage(&apos;Run cucumber&apos;) {
          sh &quot;&quot;&quot;${knapsack_options} bundle exec rake knapsack_pro:cucumber&quot;&quot;&quot;
        }

        // example how to run rspec tests in Knapsack Pro Queue Mode
        // Queue Mode should be as a last stage so it can autobalance build if tests in regular mode were not perfectly distributed
        stage(&apos;Run rspec&apos;) {
          sh &quot;&quot;&quot;KNAPSACK_PRO_CI_NODE_BUILD_ID=${env.BUILD_TAG} ${knapsack_options} bundle exec rake knapsack_pro:queue:rspec&quot;&quot;&quot;
        }
      }
    }
  }

  parallel nodes // run CI nodes in parallel
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here is JavaScript example how to split Cypress tests. You can learn more about &lt;a href=&quot;/2018/run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;splitting E2E tests for Cypress test runner&lt;/a&gt; here.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;timeout(time: 60, unit: &apos;MINUTES&apos;) {
  node() {
    stage(&apos;Checkout&apos;) {
      checkout([/* checkout code from git */])

      // determine git commit hash because we need to pass it to Knapsack Pro
      COMMIT_HASH = sh(returnStdout: true, script: &apos;git rev-parse HEAD&apos;).trim()

      stash &apos;source&apos;
    }
  }

  def num_nodes = 4; // define your total number of CI nodes (how many parallel jobs will be executed)
  def nodes = [:]

  for (int i = 0; i &amp;lt; num_nodes; i++) {
    def index = i;
    nodes[&quot;ci_node_${i}&quot;] = {
      node() {
        stage(&apos;Setup&apos;) {
          unstash &apos;source&apos;
          // other setup steps
        }

        def knapsack_options = &quot;&quot;&quot;\
            KNAPSACK_PRO_CI_NODE_TOTAL=${num_nodes}\
            KNAPSACK_PRO_CI_NODE_INDEX=${index}\
            KNAPSACK_PRO_COMMIT_HASH=${COMMIT_HASH}\
            KNAPSACK_PRO_BRANCH=${env.BRANCH_NAME}\
            KNAPSACK_PRO_CI_NODE_BUILD_ID=${env.BUILD_TAG}\
        &quot;&quot;&quot;

        // example how to run tests with Knapsack Pro
        stage(&apos;Run tests&apos;) {
          sh &quot;&quot;&quot;${knapsack_options} npx @knapsack-pro/cypress&quot;&quot;&quot;
        }
      }
    }
  }

  parallel nodes // run CI nodes in parallel
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com/ci_servers/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;Jenkins&lt;/a&gt; Pipeline gets you a continuous delivery (CD) pipeline which is an automated expression of your process for getting software from version control right through to your users. It’s important to save the time of your engineering team by running CI build fast. One way of doing it is &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;test suite parallelisation that can be done in an optimal way with Knapsack Pro for Ruby and JavaScript tests&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;If you are currently considering the choice between Jenkins and other CI, check out our popular comparison pages: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/jenkins/vs/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;Jenkins vs Github Actions&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/drone/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;Drone vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/aws-codebuild/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;AWS CodeBuild vs Jenkins&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/google-cloud-build/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages&quot;&gt;Cloud Build vs Jenkins&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 16 Dec 2018 16:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/jenkins-pipeline-how-to-run-parallel-tests-in-your-workflow-stages</guid>
        
        
        <category>continuous_integration</category>
        
        <category>Jenkins</category>
        
        <category>Pipeline</category>
        
        <category>CI</category>
        
        <category>parallelisation</category>
        
        <category>RSpec</category>
        
        <category>Ruby</category>
        
        <category>Javascript</category>
        
        <category>Cypress</category>
        
      </item>
    
      <item>
        <title>How to run CodeShip Parallel Test Pipelines efficiently - optimal CI parallelization</title>
        <description>&lt;p&gt;When you use CodeShip as your CI server you can significantly increase the speed of your CI builds with Parallel Test Pipelines. Pipelines allow you to run multiple commands at the same time, for instance, you can split test suite across a few pipelines and complete the CI build much faster.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-codeship-parallel-test-pipelines-efficiently-optimal-ci-parallelization/codeship.jpg&quot; style=&quot;width:300px;margin-left: 15px;float:right;&quot; alt=&quot;CodeShip&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-to-run-parallel-commands-on-codeship&quot;&gt;How to run parallel commands on CodeShip&lt;/h2&gt;

&lt;h3 id=&quot;setup-via-codeship-interface&quot;&gt;Setup via CodeShip interface&lt;/h3&gt;

&lt;p&gt;One way is to define commands via the &lt;a href=&quot;https://docs.cloudbees.com/docs/cloudbees-codeship/latest/basic-builds-and-configuration/parallel-tests#using-parallel-test-pipelines&quot;&gt;CodeShip interface&lt;/a&gt;. Once parallel test pipelines are enabled, each project can have multiple test pipelines that run in parallel.&lt;/p&gt;

&lt;p&gt;In order to run CI build as fast as possible we need to ensure the parallel commands will run subset of the test suite in a way that all the commands complete at the same time to avoid slow pipeline bottleneck. To split test suite we will use &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-codeship-parallel-test-pipelines-efficiently-optimal-ci-parallelization&quot;&gt;Knapsack Pro with Queue Mode which does dynamic test suite split across pipelines for Ruby and JavaScript tests&lt;/a&gt; to keep running our tests in an optimal way across parallel pipelines (also known as CI nodes).&lt;/p&gt;

&lt;h4 id=&quot;example-for-test-suite-in-rspec-for-ruby-on-rails-project&quot;&gt;Example for test suite in RSpec for Ruby on Rails project&lt;/h4&gt;

&lt;p&gt;Configure test pipelines (1/2 used)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# first CI node running in parallel&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# RSpec tests in Knapsack Pro Queue Mode (dynamic test suite split)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2 KNAPSACK_PRO_CI_NODE_INDEX=0 bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Configure test pipelines (2/2 used)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# second CI node running in parallel&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# RSpec tests in Knapsack Pro Queue Mode (dynamic test suite split)&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2 KNAPSACK_PRO_CI_NODE_INDEX=1 bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;example-of-test-suite-split-for-cypress-test-runner-in-javascript&quot;&gt;Example of test suite split for Cypress test runner in JavaScript&lt;/h4&gt;

&lt;p&gt;Configure test pipelines (1/2 used)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# first CI node running in parallel&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2 KNAPSACK_PRO_CI_NODE_INDEX=0 npx @knapsack-pro/cypress&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Configure test pipelines (2/2 used)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# second CI node running in parallel&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2 KNAPSACK_PRO_CI_NODE_INDEX=1 npx @knapsack-pro/cypress&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can learn more about &lt;a href=&quot;/2018/run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;Cypress test runner for E2E tests in JavaScript&lt;/a&gt; in this article.&lt;/p&gt;

&lt;h3 id=&quot;setup-via-codeship-servicesyml&quot;&gt;Setup via codeship-services.yml&lt;/h3&gt;

&lt;p&gt;If you use CodeShip Pro then a parallel step group is defined by using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type: parallel&lt;/code&gt; header and then nesting all steps you want to be parallelized, as seen in this example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;parallel&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2 KNAPSACK_PRO_CI_NODE_INDEX=0 bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;app&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2 KNAPSACK_PRO_CI_NODE_INDEX=1 bundle exec rake knapsack_pro:queue:rspec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;More examples on how to configure &lt;a href=&quot;https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/steps#_parallelizing_steps_and_tests&quot;&gt;CodeShip Pro parallelism&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-dynamic-test-suite-split-works&quot;&gt;How dynamic test suite split works&lt;/h2&gt;

&lt;p&gt;If you would like to better understand how dynamic test suite split works and what problems it can solve check the video:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Running tests in parallel is a fast way to &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-codeship-parallel-test-pipelines-efficiently-optimal-ci-parallelization&quot;&gt;lower time of your CI build&lt;/a&gt;. To make it more efficient we can split test suite in an optimal way across CI nodes with &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-codeship-parallel-test-pipelines-efficiently-optimal-ci-parallelization&quot;&gt;Knapsack Pro&lt;/a&gt; to keep CI nodes auto balanced and run CI build as fast as possible.&lt;/p&gt;
</description>
        <pubDate>Sat, 08 Dec 2018 19:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/how-to-run-codeship-parallel-test-pipelines-efficiently-optimal-ci-parallelization</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/how-to-run-codeship-parallel-test-pipelines-efficiently-optimal-ci-parallelization</guid>
        
        
        <category>continuous_integration</category>
        
        <category>CodeShip</category>
        
        <category>parallel</category>
        
        <category>pipelines</category>
        
        <category>CI</category>
        
        <category>parallelisation</category>
        
        <category>RSpec</category>
        
        <category>Ruby</category>
        
        <category>Javascript</category>
        
        <category>Cypress</category>
        
      </item>
    
      <item>
        <title>CircleCI 2.0 cache Ruby gems or npm dependencies</title>
        <description>&lt;p&gt;CircleCI 2.0 allows us to cache specific files or folders. We can use that to cache ruby gems installed with bundler and restore them when we will run another CI build. This way new CI build could run faster by using cached files from the previous build. This article shows you how to cache npm dependencies as well.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/circleci-2-0-cache-ruby-gems-or-npm-dependencies/cache.jpg&quot; style=&quot;width:450px;margin-left: 15px;float:right;&quot; alt=&quot;cache&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;cache-ruby-gems&quot;&gt;Cache ruby gems&lt;/h2&gt;

&lt;p&gt;One of the first steps is to check if an available cache exists &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1-dependencies-bundler-{ { checksum &quot;Gemfile.lock&quot; }}&lt;/code&gt; for our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt;. That’s why we include as part of cache key the checksum of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; file. This way if you will have different content of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; for different git commits we will use proper cached files when a new CI build would have the same checksum of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When you will install new gems and push a new git commit with updated content of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; then the cache key for that new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.lock&lt;/code&gt; won’t exist. In such case, we want to use fallback cache key matching pattern &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1-dependencies-bundler-&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the cache is restored we can install gems with bundler to a specified directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vendor/bundle&lt;/code&gt; and then we can cache it under specific cache key &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1-dependencies-bundler-{ { checksum &quot;Gemfile.lock&quot; }}&lt;/code&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .circleci/config.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# other config&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Restore bundle cache&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache-restore&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# NOTE: remove space between { { here and in all below examples&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1-dependencies-bundler-{ { checksum &quot;Gemfile.lock&quot; }}&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# fallback to using the latest cache if no exact match is found&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1-dependencies-bundler-&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Bundle install dependencies&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle install --jobs=4 --retry=3 --path vendor/bundle&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Store bundle cache&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache-save&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1-dependencies-bundler-{ { checksum &quot;Gemfile.lock&quot; }}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vendor/bundle&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# other config&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you would like to see a full example of &lt;a href=&quot;/2017/circleci-2-0-capybara-feature-specs-selenium-webdriver-with-chrome-headless&quot;&gt;CircleCI 2.0 config file for Ruby on Rails project&lt;/a&gt; check this article.&lt;/p&gt;

&lt;h2 id=&quot;cache-npm-dependencies&quot;&gt;Cache npm dependencies&lt;/h2&gt;

&lt;p&gt;The flow for caching npm packages is similar. Here you can see an example.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .circleci/config.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# other config&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# other config&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;restore_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1-dependencies-{ { checksum &quot;package.json&quot; }}&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# fallback to using the latest cache if no exact match is found&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1-dependencies-&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm install&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;save_cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;node_modules&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1-dependencies-{ { checksum &quot;package.json&quot; }}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# other config&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Caching available for CircleCI 2.0 can be very helpful and helps us &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=circleci-2-0-cache-ruby-gems-or-npm-dependencies&quot;&gt;cut down the time of CI build&lt;/a&gt;. There are also more options to speed up our CI build. One of it is &lt;a href=&quot;/2018/improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;CI parallelization for CircleCI 2.0&lt;/a&gt;, you can learn more in the article or check demo on &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=circleci-2-0-cache-ruby-gems-or-npm-dependencies&quot;&gt;Knapsack Pro&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Sat, 08 Dec 2018 12:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/circleci-2-0-cache-ruby-gems-or-npm-dependencies</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/circleci-2-0-cache-ruby-gems-or-npm-dependencies</guid>
        
        
        <category>techtips</category>
        
        <category>CircleCI</category>
        
        <category>cache</category>
        
        <category>ruby</category>
        
        <category>gems</category>
        
        <category>npm</category>
        
        <category>dependencies</category>
        
        <category>packages</category>
        
      </item>
    
      <item>
        <title>How to run parallel dynos on Heroku CI to complete tests faster</title>
        <description>&lt;p&gt;Heroku provides a CI solution out of the box for teams. They can run tests in dyno instance for your project. What’s more interesting you can run parallel dynos as part of your CI build. This allows you to split tests on parallel dynos to complete CI build faster and save time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster/heroku.jpg&quot; style=&quot;width:450px;margin-left: 15px;float:right;&quot; alt=&quot;Heroku&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Heroku charges you for seconds spent in runtime for each dyno. It means instead of running your slow test suite on a single dyno you could split it across multiple dynos and pay more or less the same and significantly reduce the CI build time for your project.&lt;/p&gt;

&lt;h2 id=&quot;how-to-start-with-heroku-ci&quot;&gt;How to start with Heroku CI&lt;/h2&gt;

&lt;p&gt;If you or your company has already created a team in Heroku then you can use Heroku CI and run tests for your project there.&lt;/p&gt;

&lt;p&gt;In Heroku, you can open your team and particular pipeline for one of your projects. You will find there a &lt;i&gt;Tests&lt;/i&gt; tab where you can enable Heroku CI.&lt;/p&gt;

&lt;p&gt;You will also need an &lt;i&gt;app.json&lt;/i&gt; file in a repository of your project. The file contains information about what’s needed to run the project on Heroku. We will add to the &lt;i&gt;app.json&lt;/i&gt; file additional configuration needed for Heroku CI.&lt;/p&gt;

&lt;p&gt;In order to use Heroku CI parallel test runs, we need to have it enabled. You will have to ask Heroku support to activate it for your project. This feature allows to run up to 32 parallel dynos for your CI build.&lt;/p&gt;

&lt;p&gt;You can also watch all the steps on more detailed video or copy some examples from this article.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/4lJVzdA11OQ&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Below I will show you examples how to split tests on Heroku CI for Ruby and JavaScript projects.&lt;/p&gt;

&lt;h2 id=&quot;how-to-run-parallel-dynos-for-test-suite-in-ruby-on-rails-project&quot;&gt;How to run parallel dynos for test suite in Ruby on Rails project&lt;/h2&gt;

&lt;p&gt;We can split Ruby tests written in RSpec, Minitest or other tests runners across parallel dynos in a dynamic way where all dynos will finish work at similar time so we will get results about our test suite being green or red as soon as possible. To do it we will use &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster&quot;&gt;Knapsack Pro&lt;/a&gt; tool with its &lt;a href=&quot;https://youtu.be/hUEB1XDKEFY&quot;&gt;Queue Mode for dynamic test suite split&lt;/a&gt;. With &lt;i&gt;quantity&lt;/i&gt; key, we can set how many parallel dynos we want to use to run our &lt;i&gt;scripts test&lt;/i&gt; command.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;environments&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;test&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;formation&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;test&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;quantity&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;mi&quot;&gt;2&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;nl&quot;&gt;&quot;addons&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;s2&quot;&gt;&quot;heroku-postgresql:in-dyno&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;heroku-redis:in-dyno&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;nl&quot;&gt;&quot;scripts&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;test&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;bundle exec rake knapsack_pro:rspec&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;nl&quot;&gt;&quot;env&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;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&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;rspec-token&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;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We also can change dyno size for our CI build. If you run complex tests that need more CPU or memory you could add &lt;i&gt;size&lt;/i&gt; parameter to &lt;i&gt;app.json&lt;/i&gt; to define &lt;a href=&quot;https://devcenter.heroku.com/articles/dyno-types&quot;&gt;dyno type&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;environments&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;test&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;formation&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;test&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;quantity&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;mi&quot;&gt;1&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;size&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;performance-l&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;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;run-javascript-tests-across-parallel-heroku-ci-dynos-for-cypress-e2e-test-suite&quot;&gt;Run JavaScript tests across parallel Heroku CI dynos for Cypress E2E test suite&lt;/h2&gt;

&lt;p&gt;End-to-end tests (E2E) often takes a lot of time because clicking through multiple scenarios of your website is time-consuming. Splitting Cypress test suite on multiple dynos will help us save a lot of time and keep CI build fast.&lt;/p&gt;

&lt;p&gt;We can use &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress&quot;&gt;@knapsack-pro/cypress&lt;/a&gt; project for that. It uses &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster&quot;&gt;Knapsack Pro Queue Mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a config for your &lt;i&gt;app.json&lt;/i&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&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;environments&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;test&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;formation&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;test&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;quantity&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;mi&quot;&gt;2&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;nl&quot;&gt;&quot;addons&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;s2&quot;&gt;&quot;heroku-postgresql:in-dyno&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;heroku-redis:in-dyno&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;nl&quot;&gt;&quot;scripts&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;test&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;npx @knapsack-pro/cypress&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;nl&quot;&gt;&quot;env&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;KNAPSACK_PRO_TEST_SUITE_TOKEN_CYPRESS&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;example&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;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you would like to learn more about Cypress then check the video in an article about &lt;a href=&quot;/2018/run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;running javascript E2E tests with Cypress on parallel CI nodes&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Heroku CI is a great addition to Heroku. If you already use Heroku you can easily leverage dynos and keep low costs of your CI as you pay only for dyno usage. The great thing is that you can run many parallel CI dynos with a subset of your test suite to save a ton of time for your engineering team. You can learn more about how &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster&quot;&gt;Knapsack Pro can help you save time with faster CI builds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;If you’d like to compare Heroku CI with other solutions, check out our comparison pages: &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/heroku-ci/vs/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster&quot;&gt;Heroku CI vs Circle CI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/github-actions/vs/heroku-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster&quot;&gt;Github Actions vs Heroku CI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/netlify-build/vs/heroku-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster&quot;&gt;Netlify vs Heroku&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster#heroku-ci&quot;&gt;Heroku CI vs Other CIs&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 01 Dec 2018 19:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/how-to-run-parallel-dynos-on-heroku-ci-to-complete-tests-faster</guid>
        
        
        <category>continuous_integration</category>
        
        <category>Heroku</category>
        
        <category>CI</category>
        
        <category>parallel</category>
        
        <category>test</category>
        
        <category>parallelisation</category>
        
        <category>Ruby</category>
        
        <category>RSpec</category>
        
        <category>Minitest</category>
        
        <category>Javascript</category>
        
        <category>Cypress</category>
        
      </item>
    
      <item>
        <title>How to run Travis CI parallel jobs with build matrix feature fast</title>
        <description>&lt;p&gt;Travis CI allows you to run multiple concurrent jobs as part of the same CI build. They even allow for up to 200 parallel jobs for open source projects (the same for private repositories). You can leverage that using &lt;a href=&quot;https://knapsackpro.com/ci_servers/travis-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast&quot;&gt;Travis&lt;/a&gt; build matrix feature to run your project way faster by splitting tests into many smaller jobs that will run a subset of your test suite.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast/travis-ci.jpg&quot; style=&quot;width:450px;margin-left: 15px;float:right;&quot; alt=&quot;Travis CI&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-build-matrix-feature-works&quot;&gt;How build matrix feature works?&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.travis-ci.com/user/build-matrix/&quot;&gt;Build matrix feature&lt;/a&gt; allows to automatically create a matrix of all possible combinations of language and environment dependent set of configuration options. For instance, when you want to test your project on 2 different programming language versions and with 2 different browsers then Travis would generate 4 parallel jobs running your tests for each programming language and browser.&lt;/p&gt;

&lt;h2 id=&quot;split-tests-across-parallel-jobs&quot;&gt;Split tests across parallel jobs&lt;/h2&gt;

&lt;p&gt;We could use build matrix feature to split tests by adding to your project &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast&quot;&gt;Knapsack Pro&lt;/a&gt; that will split your &lt;b&gt;Ruby&lt;/b&gt; or &lt;b&gt;Javascript&lt;/b&gt; tests automatically across parallel jobs in a way that all concurrent jobs would run subset of test suite and finish work in similar time so you would get the result of your test suite passing or not as fast as possible without waiting for the slowest job.&lt;/p&gt;

&lt;p&gt;How to run Ruby tests on parallel jobs with &lt;a href=&quot;https://github.com/KnapsackPro/knapsack_pro-ruby&quot;&gt;knapsack_pro&lt;/a&gt; ruby gem:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .travis.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Step for RSpec&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rake&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;knapsack_pro:rspec&quot;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Step for Cucumber&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rake&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;knapsack_pro:cucumber&quot;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Step for Minitest&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rake&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;knapsack_pro:minitest&quot;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Step for test-unit&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rake&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;knapsack_pro:test_unit&quot;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Step for Spinach&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rake&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;knapsack_pro:spinach&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;global&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# tokens should be set in travis settings in web interface to avoid expose tokens in build logs&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC=rspec-token&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_CUCUMBER=cucumber-token&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_MINITEST=minitest-token&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_TEST_UNIT=test-unit-token&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_SPINACH=spinach-token&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX=0&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX=1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;How to run Cypress tests in JavaScript on concurrent jobs with &lt;a href=&quot;https://github.com/KnapsackPro/knapsack-pro-cypress&quot;&gt;@knapsack-pro/cypress&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .travis.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;npx&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@knapsack-pro/cypress&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;global&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_TOTAL=2&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# allows to be able to retry failed tests on one of parallel job (CI node)&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT=true&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX=0&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;KNAPSACK_PRO_CI_NODE_INDEX=1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;By doing test suite split in a dynamic way across &lt;a href=&quot;https://knapsackpro.com/ci_servers/travis-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast&quot;&gt;Travis&lt;/a&gt; parallel jobs we save more time and keep our CI build fast. I also call parallel jobs as CI nodes because they are part of a single CI build. Here on the video, I describe a few more problems that can be solved with dynamic test suite split.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;travis-ci-build-matrix-in-action&quot;&gt;Travis CI build matrix in action&lt;/h2&gt;

&lt;p&gt;The OSS &lt;a href=&quot;https://github.com/consuldemocracy/consuldemocracy/actions/workflows/tests.yml&quot;&gt;Consul - Open Government and E-Participation Web Software&lt;/a&gt; is a great example of how &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast&quot;&gt;Knapsack Pro helps split tests across parallel jobs&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I&amp;#39;ve got cup gift for GitHub contribution to &lt;a href=&quot;https://twitter.com/hashtag/Consul?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;#Consul&lt;/a&gt; - Open Government and E-Participation Web Software &lt;a href=&quot;https://t.co/NNIAgO3uXX&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;https://t.co/NNIAgO3uXX&lt;/a&gt; that empowers &lt;a href=&quot;https://t.co/eGO3aj0slM&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;https://t.co/eGO3aj0slM&lt;/a&gt; The Consul team uses &lt;a href=&quot;https://twitter.com/KnapsackPro?ref_src=twsrc%5Etfw&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@KnapsackPro&lt;/a&gt; to run &lt;a href=&quot;https://twitter.com/hashtag/ruby?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;#ruby&lt;/a&gt; tests faster :) Thanks &lt;a href=&quot;https://twitter.com/bertocq?ref_src=twsrc%5Etfw&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@bertocq&lt;/a&gt; &lt;a href=&quot;https://twitter.com/voodoorai2000?ref_src=twsrc%5Etfw&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@voodoorai2000&lt;/a&gt; &lt;a href=&quot;https://t.co/8sowbeXlAJ&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;pic.twitter.com/8sowbeXlAJ&lt;/a&gt;&lt;/p&gt;&amp;mdash; Artur Trzop (@ArturTrzop) &lt;a href=&quot;https://twitter.com/ArturTrzop/status/1012429838328754176?ref_src=twsrc%5Etfw&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;June 28, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;If you would like to learn more about testing with &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast&quot;&gt;Knapsack Pro&lt;/a&gt; you can check other articles on our &lt;a href=&quot;/&quot;&gt;blog&lt;/a&gt; like &lt;a href=&quot;/2018/run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;testing with Cypress test runner&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;In case you are contemplating using Travis CI, then this &lt;a href=&quot;https://knapsackpro.com/ci_comparisons?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast#travis-ci&quot;&gt;comparison of Travis CI with other solutions&lt;/a&gt; can be helpful to you. The &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/travis-ci/vs/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast&quot;&gt;comparison of Travis to Github Actions&lt;/a&gt; garners the most interest. Other popular pages include &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/travis-ci/vs/appveyor?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast&quot;&gt;Travis vs AppVeyor&lt;/a&gt; and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/travis-ci/vs/bitbucket-pipelines?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast&quot;&gt;Travis vs Bitbucket Pipelines&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 30 Nov 2018 18:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/how-to-run-travis-ci-parallel-jobs-with-build-matrix-feature-fast</guid>
        
        
        <category>continuous_integration</category>
        
        <category>Travis</category>
        
        <category>CI</category>
        
        <category>parallelisation</category>
        
        <category>Ruby</category>
        
        <category>RSpec</category>
        
        <category>Minitest</category>
        
        <category>Javascript</category>
        
        <category>Cypress</category>
        
      </item>
    
      <item>
        <title>Improve CircleCI parallelisation for RSpec, Minitest, Cypress</title>
        <description>&lt;p&gt;Maybe you use &lt;a href=&quot;https://knapsackpro.com/ci_servers/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;CircleCI&lt;/a&gt; parallelisation to run your test suite across multiple CI nodes but you noticed that some CI nodes take more time to complete tests than the others.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/improve-circleci-parallelisation-for-rspec-minitest-cypress/balanced-tests.jpg&quot; style=&quot;width:450px;margin-left: 15px;float:right;&quot; alt=&quot;parallel testing&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This can happen when your tests have random time. Often E2E (end-to-end) tests have a more random time of execution because the browser has to wait for some elements to be loaded on the website or maybe your app is depended on external API and processing requests have different duration. Other reason can be a delay with starting one of your parallel CI nodes.&lt;/p&gt;

&lt;h2 id=&quot;default-circleci-test-suite-split&quot;&gt;Default CircleCI test suite split&lt;/h2&gt;

&lt;p&gt;Here is an example of running tests with default CircleCI parallelisation. As you can see the whole CI build finished work in 29 minutes 37 seconds. Tests were executed on 15 parallel CI nodes and some of them run tests for 14 minutes and the slowest one for almost 30 minutes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/improve-circleci-parallelisation-for-rspec-minitest-cypress/circleci-before-knapsack_pro.png&quot; style=&quot;width:100%;&quot; alt=&quot;parallel tests CircleCI without knapsack&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If we could auto-balance the split of test suite across CI nodes in a way that all CI nodes do work in similar time then we could get shorter CI build.&lt;/p&gt;

&lt;h2 id=&quot;dynamic-test-suite-split&quot;&gt;Dynamic test suite split&lt;/h2&gt;

&lt;p&gt;We can &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;split tests in a dynamic way&lt;/a&gt; across CI nodes using &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;Knapsack Pro&lt;/a&gt;. For instance, we can split tests for RSpec or Minitest in Ruby. If you run E2E tests with Cypress test runner then you can split Javascript tests as well.&lt;/p&gt;

&lt;p&gt;Here is graph after adding &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;Knapsack Pro Queue Mode&lt;/a&gt;. Knapsack Pro Queue Mode keeps your tests auto-balanced across CI nodes in order to allow all CI nodes finish work in similar time. As you can see CI build took 22 minutes 50 seconds instead of almost 30 minutes. It means &lt;b&gt;we saved 7 minutes per each CI build&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/improve-circleci-parallelisation-for-rspec-minitest-cypress/circleci-after-knapsack_pro.png&quot; style=&quot;width:100%;&quot; alt=&quot;parallel tests CircleCI with Knapsack Pro&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When your team runs 20 CI builds per day you could save 2 hours 20 minutes every day. It’s &lt;b&gt;over 46 hours saved during a month&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;In below video I show what else problems can be solved with dynamic test suite split.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/gJdLQb83hho&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;If you use RSpec then you can even &lt;a href=&quot;https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it&quot;&gt;auto split slow RSpec test files by test examples on parallel jobs&lt;/a&gt; thanks to knapsack_pro gem. It means your single slow spec file can run in parallel!&lt;/p&gt;

&lt;p&gt;You can also read more about it at &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;Knapsack Pro&lt;/a&gt; and see support for more test runners there.&lt;/p&gt;

&lt;h2 id=&quot;other-tips&quot;&gt;Other tips&lt;/h2&gt;

&lt;p&gt;If you are curious about &lt;a href=&quot;/2018/run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;Cypress test suite split&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you would like to run your &lt;a href=&quot;/2017/circleci-2-0-capybara-feature-specs-selenium-webdriver-with-chrome-headless&quot;&gt;CircleCI 2.0 tests with Chrome headless&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tech details how to run tests in &lt;a href=&quot;/2018/how-to-run-tests-in-minitest-continuously-with-dynamic-test-files-loading&quot;&gt;Minitest continuously with dynamic test files loading&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are looking for an optimal CI solution for your project, check out our &lt;a href=&quot;https://knapsackpro.com/ci_comparisons?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress#circle-ci&quot;&gt;comparisons of Circle CI vs other CI providers&lt;/a&gt;. Most popular comparisons include &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/circle-ci/vs/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;Circle CI vs GitHub Actions&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/circle-ci/vs/codefresh-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;Circle CI vs CodeFresh CI&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/circle-ci/vs/gitlab-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;Circle CI vs Gitlab CI&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/circle-ci/vs/bitbucket-pipelines?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=improve-circleci-parallelisation-for-rspec-minitest-cypress&quot;&gt;Circle CI vs Bitbucket Pipelines&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Sat, 24 Nov 2018 11:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/improve-circleci-parallelisation-for-rspec-minitest-cypress</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/improve-circleci-parallelisation-for-rspec-minitest-cypress</guid>
        
        
        <category>continuous_integration</category>
        
        <category>CircleCI</category>
        
        <category>parallelisation</category>
        
        <category>RSpec</category>
        
        <category>Minitest</category>
        
        <category>Cypress</category>
        
        <category>Ruby</category>
        
        <category>Javascript</category>
        
      </item>
    
      <item>
        <title>Run javascript E2E tests faster with Cypress on parallel CI nodes</title>
        <description>&lt;p&gt;Cypress test runner is a great alternative to Selenium in end-to-end testing space. When it comes to E2E tests they tend to grow with time and running them is slow and becomes time wasting or just coffee break for developers. ;)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes/cypress-logo.jpg&quot; style=&quot;width:450px;margin-left: 15px;float:right;&quot; alt=&quot;Cypress.io&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To save the time and give quick feedback to developers about CI builds passing or not we could run tests across parallel CI nodes. Many &lt;a href=&quot;https://knapsackpro.com/ci_servers/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;CI providers&lt;/a&gt; allow you to set up how many parallel machines can be run within your CI build.&lt;/p&gt;

&lt;h2 id=&quot;problem-with-splitting-tests-on-parallel-machines&quot;&gt;Problem with splitting tests on parallel machines&lt;/h2&gt;

&lt;p&gt;If you want to run your test suite on multiple CI nodes at the same time you need to decide which test file goes to particular CI nodes.&lt;/p&gt;

&lt;p&gt;In a perfect scenario, we would like to run each parallel CI machine equal time. Thanks to that we won’t have to wait for slow CI node that runs too many tests and is the bottleneck for the whole CI build to complete.&lt;/p&gt;

&lt;p&gt;The test suite is a living animal, there are constant changes in it, new tests are added or removed. The time of running particular tests can change and sometimes even be random when for instance your tests need to wait for some data to be loaded from external API.&lt;/p&gt;

&lt;p&gt;Even CI provider can cause an additional problem. Sometimes boot time of your parallel CI nodes can vary. One CI node starts work later than the others or maybe it’s stuck in the queue due running some other CI build in your organization account.&lt;/p&gt;

&lt;p&gt;Some of &lt;a href=&quot;https://knapsackpro.com/ci_servers/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;CI solutions&lt;/a&gt; allows you to use a big amount of parallel CI nodes to speed up your tests by leveraging your own server infrastructure like AWS with very cheap preeemted resources - Amazon EC2 Spot Instances (in case of Google, you have Google Cloud Preemptible VMs). The downside of this is when the AWS or Google wants to take away from your the server during the running your tests. Suddenly you lose one of your parallel CI nodes and you have to retry it and it becomes your bottleneck.&lt;/p&gt;

&lt;h2 id=&quot;how-to-allocate-tests-in-a-dynamic-way-across-parallel-ci-nodes-to-save-time&quot;&gt;How to allocate tests in a dynamic way across parallel CI nodes to save time?&lt;/h2&gt;

&lt;p&gt;I’ve been working on the parallelization problem for last few years. With help and feedback from many people, we came up with a solution that helps us speed up our test suite thanks to &lt;a href=&quot;https://knapsackpro.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;allocating tests across parallel CI nodes in a dynamic way&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Basically, &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;Knapsack Pro&lt;/a&gt; orchestrate the parallelization of your test suite. On Knapsack Pro API you have Queue of test files in descending order of test file run duration. Your parallel CI nodes connect with the Queue and fetch set of test files to run them on CI node. The Knapsack Pro API is taking care of allocating the tests in proper order and to remembering the time execution of your test files so we could leverage that in future CI build runs.&lt;/p&gt;

&lt;p&gt;Knapsack Pro API can also remember tests assigned to each parallel CI node. When you lost one of parallel CI node during runtime due AWS or Google preempting your machine then you can just retry the killed CI node and run only those tests that were lost. The other parallel CI nodes would consume more tests from Queue before you retry killed CI node so you won’t waste additional time on running tests that were not lost.&lt;/p&gt;

&lt;h2 id=&quot;run-cypress-tests-with-knapsack-pro-queue-mode&quot;&gt;Run Cypress tests with Knapsack Pro Queue Mode&lt;/h2&gt;

&lt;p&gt;To start running your tests faster you can add to your project the &lt;i&gt;@knapsack-pro/cypress&lt;/i&gt; package. It works with many CI providers out of the box. Here you can find the &lt;a href=&quot;https://docs.knapsackpro.com/cypress/guide/&quot;&gt;installation guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Basically, we will run a single command on all parallel CI nodes and &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes&quot;&gt;Knapsack Pro&lt;/a&gt; will take care of running your tests fast.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;npx @knapsack-pro/cypress&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In below video, I show you an example Cypress project and how to run it on parallel CI nodes.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/G6ixK4IK-3Y&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;Hope this will help you. I’d like to hear feedback from you and exchange ideas about how we could run our tests even faster!&lt;/p&gt;
</description>
        <pubDate>Sat, 17 Nov 2018 18:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/run-javascript-e2e-tests-faster-with-cypress-on-parallel-ci-nodes</guid>
        
        
        <category>continuous_integration</category>
        
        <category>cypress</category>
        
        <category>javascript</category>
        
        <category>parallelisation</category>
        
        <category>CI</category>
        
      </item>
    
      <item>
        <title>Clean RSpec configuration directory structure for Ruby on Rails gems needed in testing</title>
        <description>&lt;p&gt;When your Ruby on Rails project is getting bigger your test suite as well. You need to test more of your business logic and sometimes you will use other gems that can help you with that. Most of the time you may need something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;database_cleaner&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;capybara&lt;/code&gt; for feature tests or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rspec-sidekiq&lt;/code&gt; to test your workers.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/clean-rspec-configuration-directory-structure-for-ruby-on-rails-gems-needed-in-testing/rspec.jpg&quot; style=&quot;width:250px;margin-left: 15px;float:right;&quot; alt=&quot;RSpec, Ruby&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Adding new gems needed for testing often requires changes in RSpec configuration. You add a new line of config here and there in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec_helper.rb&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails_helper.rb&lt;/code&gt; file and suddenly you have huge and hard to understand config file for RSpec.&lt;/p&gt;

&lt;p&gt;I will show you how I organize my RSpec configuration directory structure to easily add or modify the RSpec configuration in a clean way.&lt;/p&gt;

&lt;h2 id=&quot;prepare-rspec-config-directory-structure&quot;&gt;Prepare RSpec config directory structure&lt;/h2&gt;

&lt;p&gt;I keep all of my configuration code related to RSpec in directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/config&lt;/code&gt;. You can create it.&lt;/p&gt;

&lt;p&gt;Next step is to ensure the RSpec will read files from the config directory. In order to do it please ensure you have below line in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/rails_helper.rb&lt;/code&gt; file.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;spec/support/**/*.rb&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&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;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;By default, it is commented out so please uncomment it.&lt;/p&gt;

&lt;h2 id=&quot;separate-config-files-for-different-testing-gems&quot;&gt;Separate config files for different testing gems&lt;/h2&gt;

&lt;p&gt;Here I will show you examples of popular gems and how to keep their configuration clean. A few examples are on the video and many more code examples are in this article.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/M0-ZQkYoQmI&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;configuration-for-database-cleaner&quot;&gt;Configuration for Database Cleaner&lt;/h2&gt;

&lt;p&gt;For &lt;a href=&quot;https://github.com/DatabaseCleaner/database_cleaner&quot;&gt;database_cleaner&lt;/a&gt; gem you can just create config file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/config/database_cleaner.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/database_cleaner.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;database_cleaner&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:suite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;DatabaseCleaner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strategy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:deletion&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;DatabaseCleaner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clean_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:deletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;around&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;DatabaseCleaner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cleaning&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;configuration-for-capybara&quot;&gt;Configuration for Capybara&lt;/h2&gt;

&lt;p&gt;Here is my configuration of Capybara placed at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/config/capybara.rb&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/capybara.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;capybara/rspec&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;JS_DRIVER&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:selenium_chrome_headless&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;DEFAULT_MAX_WAIT_TIME&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;CI&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;mi&quot;&gt;5&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;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:rack_test&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;javascript_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JS_DRIVER&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_max_wait_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DEFAULT_MAX_WAIT_TIME&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JS_DRIVER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:selenium&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:selenium&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:selenium_chrome&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:selenium_chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_max_wait_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:max_wait_time&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;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:max_wait_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;use_default_driver&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:js&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;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:selenium&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_max_wait_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DEFAULT_MAX_WAIT_TIME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:max_wait_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I have a few custom things here like easy option to switch between different browsers executing my tests by just adding tag like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:selenium_chrome&lt;/code&gt; to test:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/features/example_feature_spec.rb&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;something&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:selenium_chrome&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;visit&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_content&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Welcome&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can learn more &lt;a href=&quot;/2017/circleci-2-0-capybara-feature-specs-selenium-webdriver-with-chrome-headless&quot;&gt;how to configure Capybara with Chrome headless&lt;/a&gt; here.&lt;/p&gt;

&lt;h2 id=&quot;configuration-for-sidekiq&quot;&gt;Configuration for Sidekiq&lt;/h2&gt;

&lt;p&gt;I like keep my Sidekiq configuration with other useful Sidekiq related gems in one place &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/config/sidekiq.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/sidekiq.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;rspec-sidekiq&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;sidekiq/testing&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;sidekiq_unique_jobs/testing&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Sidekiq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clear_all&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# https://github.com/sidekiq/sidekiq/wiki/Testing#testing-worker-queueing-fake&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:sidekiq_fake&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Sidekiq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Testing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fake!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# https://github.com/sidekiq/sidekiq/wiki/Testing#testing-workers-inline&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:sidekiq_inline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Sidekiq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Testing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inline!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:sidekiq_fake&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;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:sidekiq_inline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Sidekiq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Testing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;disable!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Sidekiq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;warn_when_jobs_not_processed_by_sidekiq&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I use gems like &lt;a href=&quot;https://github.com/mhenrixon/sidekiq-unique-jobs&quot;&gt;sidekiq-unique-jobs&lt;/a&gt; and &lt;a href=&quot;https://github.com/wspurgin/rspec-sidekiq&quot;&gt;rspec-sidekiq&lt;/a&gt;. Here you can read more about &lt;a href=&quot;https://github.com/sidekiq/sidekiq/wiki/Testing&quot;&gt;testing Sidekiq&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;configuration-for-factorybot-known-as-factorygirl&quot;&gt;Configuration for FactoryBot (known as FactoryGirl)&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/thoughtbot/factory_bot_rails&quot;&gt;FactoryBot&lt;/a&gt; config file for Ruby on Rails can be isolated at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/config/factory_bot.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/factory_bot.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FactoryBot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Syntax&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Methods&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;configuration-for-json-spec&quot;&gt;Configuration for JSON Spec&lt;/h2&gt;

&lt;p&gt;If you have API endpoints in your application you may like the &lt;a href=&quot;https://github.com/collectiveidea/json_spec&quot;&gt;json_spec&lt;/a&gt; gem that can help you test JSON responses. Here is my config at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/config/json_spec.rb&lt;/code&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/json_spec.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;json_spec&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JsonSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Helpers&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;configuration-for-rspec-retry&quot;&gt;Configuration for RSpec Retry&lt;/h2&gt;

&lt;p&gt;Sometimes big projects have painful tests that randomly fail. The last rescue when we cannot make them stable and always green is to retry them a few times before marking them as failed. This is one option how to deal with flaky tests and we can use for that &lt;a href=&quot;https://github.com/NoRedInk/rspec-retry&quot;&gt;rspec-retry&lt;/a&gt; gem.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/rspec_retry.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# show retry status in spec process&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;verbose_retry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# show exception that triggers a retry if verbose_retry is set to true&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;display_try_failure_messages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# run retry only on features&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;around&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:js&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# retry test 3 times on CI but do not retry when testing locally&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run_with_retry&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;retry: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;CI&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;mi&quot;&gt;3&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;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# callback to be run between retries&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;retry_callback&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# run some additional clean up task - can be filtered by example metadata&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reset!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here you can learn more about how to deal with flaky tests:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/Cg6GpEYfO9s&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;configuration-for-shoulda-matchers&quot;&gt;Configuration for Shoulda Matchers&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/thoughtbot/shoulda-matchers&quot;&gt;Shoulda Matchers&lt;/a&gt; provides RSpec and Minitest-compatible one-liners that test common Rails functionality. Here is my config &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/config/shoulda_matchers.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/shoulda_matchers.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;shoulda/matchers&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Shoulda&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Matchers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;integrate&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Choose a test framework:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;test_framework&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:rspec&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#with.test_framework :minitest&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#with.test_framework :minitest_4&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#with.test_framework :test_unit&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Choose one or more libraries:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#with.library :active_record&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#with.library :active_model&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#with.library :action_controller&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Or, choose the following (which implies all of the above):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;library&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:rails&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;configuration-for-vcr-and-webmock&quot;&gt;Configuration for VCR and WebMock&lt;/h2&gt;

&lt;p&gt;You can record your requests in testing with &lt;a href=&quot;https://github.com/vcr/vcr&quot;&gt;VCR&lt;/a&gt; or mock request with &lt;a href=&quot;https://github.com/bblimke/webmock&quot;&gt;WebMock&lt;/a&gt;. This is my config &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/config/vcr.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/vcr.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;vcr&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;VCR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cassette_library_dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;spec/fixtures/vcr_cassettes&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hook_into&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:webmock&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;allow_http_connections_when_no_cassette&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ignore_hosts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;localhost&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;127.0.0.1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;0.0.0.0&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;example.com&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;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;WebMock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;disable_net_connect!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;allow: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;example.com&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;/figure&gt;

&lt;h2 id=&quot;configuration-for-knapsack-pro-to-split-test-suite-across-parallel-ci-nodes&quot;&gt;Configuration for Knapsack Pro to split test suite across parallel CI nodes&lt;/h2&gt;

&lt;p&gt;If you have a large test suite taking a dozen of minutes or maybe even hours then you may want to &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=clean-rspec-configuration-directory-structure-for-ruby-on-rails-gems-needed-in-testing&quot;&gt;run tests in parallel across multiple CI node with Knapsack Pro&lt;/a&gt; to get the fastest CI build.&lt;/p&gt;

&lt;p&gt;This is example config file.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/knapsack_pro.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;knapsack_pro&apos;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;KnapsackPro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Adapters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;RSpecAdapter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bind&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here you can learn more about how it works.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/rF5dokNqsMA&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;configuration-for-page-objects&quot;&gt;Configuration for Page Objects&lt;/h2&gt;

&lt;p&gt;You can keep your page objects in a separate directory. Page objects can be later reused in multiple tests.
Create directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/page_objects&lt;/code&gt;. Here is example page object for billing page:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/page_objects/billing_page.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BillingPage&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_company_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;first_name: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Kayleigh&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;last_name: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Johnston&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;company: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Example Company&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;vat_id: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;UK1234567890&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;kayleigh.johnston@example.com&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;street_address: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;59 Botley Road&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;locality: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Midtown&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;region: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# can be blank&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;postal_code: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;IV27 5LL&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;country_name: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;United Kingdom&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;website: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;http://example.com&apos;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;first_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;last_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;company&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;vat_id&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vat_id&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;street_address&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;street_address&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;locality&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;locality&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;region&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;region&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;postal_code&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;postal_code&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;country_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;from: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;country_name&apos;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;website&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;website&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then you can use the page object in your feature spec.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/features/billing_spec.rb&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;fills company details on billing page&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;BillingPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fill_company_details&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;configuration-for-shared-examples&quot;&gt;Configuration for shared examples&lt;/h2&gt;

&lt;p&gt;You can organize your &lt;a href=&quot;https://rspec.info/features/3-13/rspec-core/example-groups/shared-examples/&quot;&gt;RSpec shared examples&lt;/a&gt; in directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support/shared_examples&lt;/code&gt; that will be autoloaded.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;As you can see there are a lot of different useful gems for testing in RSpec. If we would keep all their configuration just in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec_helper.rb&lt;/code&gt; we would quickly get a messy file. Separation of config files can help us keep it clean and easy to maintain. Let me know in comments how you keep your config files sane.&lt;/p&gt;
</description>
        <pubDate>Thu, 08 Nov 2018 11:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/clean-rspec-configuration-directory-structure-for-ruby-on-rails-gems-needed-in-testing</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/clean-rspec-configuration-directory-structure-for-ruby-on-rails-gems-needed-in-testing</guid>
        
        
        <category>techtips</category>
        
        <category>rspec</category>
        
        <category>ruby</category>
        
        <category>rails</category>
        
        <category>testing</category>
        
        <category>gems</category>
        
      </item>
    
      <item>
        <title>How to run tests in Minitest continuously with dynamic test files loading</title>
        <description>&lt;p&gt;Recently I’ve been looking into the source code of Minitest to find out if I can run some tests and then dynamically run another set of tests once the previous run is done. This would allow me to provide dynamically a list of tests to execute on my parallel CI nodes to run CI builds faster.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/how-to-run-tests-in-minitest-continuously-with-dynamic-test-files-loading/minitest_continuously.png&quot; style=&quot;width:250px;float:right;&quot; alt=&quot;Minitest&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Something similar exists in RSpec thanks to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RSpec::Core::Runner&lt;/code&gt; feature that allows running specs multiple times with different runner options in the same process.&lt;/p&gt;

&lt;p&gt;In RSpec flow looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;spec_helper&quot;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Core&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Runner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&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;some&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&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;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clear_examples&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Core&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Runner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&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;different&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see one of the steps is to clear examples with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RSpec.clear_examples&lt;/code&gt; for the previous run to ensure the executed tests won’t affect the next list of tests we will run.&lt;/p&gt;

&lt;p&gt;I was also looking if something similar exists in Minitest to ensure we have a pristine state of test runner before we run another set of test files. I step on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Minitest::Runnable.reset&lt;/code&gt; method that could do it.&lt;/p&gt;

&lt;h2 id=&quot;digging-into-minitest-source-code&quot;&gt;Digging into Minitest source code&lt;/h2&gt;

&lt;p&gt;I found out that Minitest comes with class method &lt;a href=&quot;https://github.com/minitest/minitest/blob/8a59450038f31f30fe591946bbb0418ac9f65617/lib/minitest.rb#L546&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;run&lt;/code&gt;&lt;/a&gt; that runs the loaded test files.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# minitest/lib/minitest.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Minitest&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;##&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# This is the top-level run method. Everything starts from here. It&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# tells each Runnable sub-class to run, and each of those are&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# responsible for doing whatever they do.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# The overall structure of a run looks like this:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#   Minitest.autorun&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#     Minitest.run(args)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#       Minitest.__run(reporter, options)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#         Runnable.runnables.each&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#           runnable.run(reporter, options)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#             self.runnable_methods.each&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#               self.run_one_method(self, runnable_method, reporter)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#                 Minitest.run_one_method(klass, runnable_method)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#                   klass.new(runnable_method).run&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Knowing that I could run tests with it. The first step thou was to ensure we will be able to load test files but I realized at the top of each of test file I have a line like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;test_helper&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test_helper.rb&lt;/code&gt; file was not found while I attempt to load test file so I had to first add a directory with my tests to load path to make above require work.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# add test directory to load path to make require &apos;test_helper&apos; work&lt;/span&gt;
&lt;span class=&quot;vg&quot;&gt;$LOAD_PATH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unshift&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;test&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# now load test files&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;./test/models/user_test.rb&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;./test/models/article_test.rb&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# if all tests pass we want to exit process with 0 exit code&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;final_exit_code&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;c1&quot;&gt;# run tests loaded into memory&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;args&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;s1&quot;&gt;&apos;--verbose&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# We need to duplicate the args because the run method will change the Array object.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# We will reuse args later.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tests_passed?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Minitest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# now the tests will be executed&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# the variable tests_passed? will be true if tests passed. Otherwise would be false&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;final_exit_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tests_passed?&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Before we run another set of test files we need to reset the test runner state&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Minitest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Runnable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reset&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;# Let&apos;s load another set of test files&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;./test/controllers/users_controller_test.rb&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;./test/controllers/articles_controller_test.rb&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# we can run new set of test files&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tests_passed?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Minitest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;final_exit_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tests_passed?&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# now the second set of test files will be executed&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# once the tests files finished run then we can exit process with proper exit code&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 0 - when all tests are green&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 1 - when at least one test failed. Exit code 1 tells our CI provider&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#     that process running tests failed.&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;final_exit_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;running-minitest-continuously-and-fetching-test-files-from-the-queue-in-a-dynamic-way&quot;&gt;Running Minitest continuously and fetching test files from the Queue in a dynamic way&lt;/h2&gt;

&lt;p&gt;Digging into the source code of Minitest helped me to find out a way to run my tests in a more efficient way. I applied this to the &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=run-tests-in-minitest-continuously&quot;&gt;knapsack_pro gem&lt;/a&gt; I’m working on.&lt;/p&gt;

&lt;div class=&quot;video-container&quot;&gt;
  &lt;iframe src=&quot;https://www.youtube.com/embed/hUEB1XDKEFY&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
</description>
        <pubDate>Sun, 27 May 2018 10:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/how-to-run-tests-in-minitest-continuously-with-dynamic-test-files-loading</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/how-to-run-tests-in-minitest-continuously-with-dynamic-test-files-loading</guid>
        
        
        <category>techtips</category>
        
        <category>Minitest</category>
        
        <category>&quot;test</category>
        
        <category>suite&quot;</category>
        
        <category>&quot;queue&quot;</category>
        
      </item>
    
      <item>
        <title>How to export test suite timing data from Knapsack Pro API</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://knapsackpro.com&quot;&gt;Knapsack Pro&lt;/a&gt; tracks your CI builds and how long each test files took to run on one of parallel CI nodes. Thanks to that Knapsack Pro can prepare the optimal test suite split for your future test suite runs on your CI provider.&lt;/p&gt;

&lt;p&gt;You may find useful to export data about your CI builds and timing for your test files in case you would like to analyze it and observe trends how your test files change over time.&lt;/p&gt;

&lt;h2 id=&quot;how-to-get-a-list-of-all-ci-builds&quot;&gt;How to get a list of all CI builds&lt;/h2&gt;

&lt;p&gt;You can get the list of all CI builds recorded by Knapsack Pro tool. You need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test suite API token&lt;/code&gt;. You can get it from &lt;a href=&quot;https://knapsackpro.com/sessions&quot;&gt;user dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;curl -X GET \
  https://api.knapsackpro.com/v1/builds \
  -H &apos;cache-control: no-cache&apos; \
  -H &apos;KNAPSACK-PRO-TEST-SUITE-TOKEN: e5311882cbba506223ee8036fa68dc13&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You will get a response from API with the list of CI builds.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;{
    &quot;meta&quot;: {
        &quot;records_per_page&quot;: 100,
        &quot;total_pages&quot;: 1,
        &quot;prev_page&quot;: null,
        &quot;current_page&quot;: 1,
        &quot;next_page&quot;: null,
        &quot;is_first_page&quot;: true,
        &quot;is_last_page&quot;: true,
        &quot;is_page_out_of_range&quot;: false
    },
    &quot;links&quot;: {
        &quot;first&quot;: &quot;https://api.knapsackpro.com/v1/builds?page=1&quot;,
        &quot;prev&quot;: null,
        &quot;self&quot;: &quot;https://api.knapsackpro.com/v1/builds?page=1&quot;,
        &quot;next&quot;: null,
        &quot;last&quot;: &quot;https://api.knapsackpro.com/v1/builds?page=1&quot;
    },
    &quot;data&quot;: [
        {
          &quot;id&quot;: &quot;651efcce-cc5f-4cfc-b8fa-f49b4e5fb4af&quot;,
          &quot;commit_hash&quot;: &quot;347f33f598e5c66727e36b6f0c13b034f6a057f0&quot;,
          &quot;branch&quot;: &quot;main&quot;,
          &quot;node_total&quot;: 3,
          &quot;created_at&quot;: &quot;2018-04-21T10:57:42.439Z&quot;,
          &quot;updated_at&quot;: &quot;2018-04-21T10:57:54.989Z&quot;
        },
        {
          &quot;id&quot;: &quot;ba2190af-1bb1-4e3a-8ce1-37303549a4c3&quot;,
          &quot;commit_hash&quot;: &quot;347f33f598e5c66727e36b6f0c13b034f6a057f0&quot;,
          &quot;branch&quot;: &quot;main&quot;,
          &quot;node_total&quot;: 2,
          &quot;created_at&quot;: &quot;2018-04-02T10:00:44.741Z&quot;,
          &quot;updated_at&quot;: &quot;2018-04-02T10:00:44.763Z&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note the CI build from Knapsack Pro API perspective means a unique combination of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commit_hash&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;branch&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_total&lt;/code&gt;. Even when you run multiple CI builds in your CI provider for the same combination of fields there will be only single CI build on Knapsack Pro API side.&lt;/p&gt;

&lt;p&gt;You can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; of the build to fetch detailed info about tests recorded for that CI build by doing request to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://api.knapsackpro.com/v1/builds/:id&lt;/code&gt; endpoint. You will learn about it in next section.&lt;/p&gt;

&lt;p&gt;You can see &lt;a href=&quot;/api/v1/#builds_get&quot;&gt;summary of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET https://api.knapsackpro.com/v1/builds&lt;/code&gt; endpoint here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;filtering-ci-build-list&quot;&gt;Filtering CI build list&lt;/h3&gt;

&lt;p&gt;You can use optional query params like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commit_hash&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;branch&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_total&lt;/code&gt; to filter list of CI builds. See example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;curl -X GET \
  https://api.knapsackpro.com/v1/builds?page=1&amp;amp;branch=main&amp;amp;node_total=2&amp;amp;commit_hash=d1acb81ac1bead703eb6de64d1af24104d5d4b2c \
  -H &apos;cache-control: no-cache&apos; \
  -H &apos;KNAPSACK-PRO-TEST-SUITE-TOKEN: e5311882cbba506223ee8036fa68dc13&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;how-to-get-the-timing-of-test-files-for-ci-build&quot;&gt;How to get the timing of test files for CI build&lt;/h2&gt;

&lt;p&gt;You can get detailed info about CI build with all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build subsets&lt;/code&gt; containing the test files recorded timing for that CI build.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;curl -X GET \
  https://api.knapsackpro.com/v1/builds/651efcce-cc5f-4cfc-b8fa-f49b4e5fb4af \
  -H &apos;cache-control: no-cache&apos; \
  -H &apos;KNAPSACK-PRO-TEST-SUITE-TOKEN: e5311882cbba506223ee8036fa68dc13&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note the CI build means unique combination of &lt;strong&gt;commit_hash&lt;/strong&gt;, &lt;strong&gt;branch&lt;/strong&gt;, &lt;strong&gt;node_total&lt;/strong&gt;.
If you will run your tests multiple times for that combination then you will get multiple recorded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build subset&lt;/code&gt; records with test files timing for particular CI node indexes.&lt;/p&gt;

&lt;p&gt;For instance, you can get a response from Knapsack Pro API:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;{
  &quot;id&quot;: &quot;651efcce-cc5f-4cfc-b8fa-f49b4e5fb4af&quot;,
  &quot;commit_hash&quot;: &quot;347f33f598e5c66727e36b6f0c13b034f6a057f0&quot;,
  &quot;branch&quot;: &quot;main&quot;,
  &quot;node_total&quot;: 2,
  &quot;created_at&quot;: &quot;2018-04-02T10:00:44.741Z&quot;,
  &quot;updated_at&quot;: &quot;2018-04-02T10:00:44.763Z&quot;,
  &quot;build_subsets&quot;: [
    {
      &quot;id&quot;: &quot;b9f09921-939d-43a6-ae5d-7c95866b4dec&quot;,
      &quot;node_index&quot;: 0,
      &quot;created_at&quot;: &quot;2018-04-21T11:18:34.539Z&quot;,
      &quot;updated_at&quot;: &quot;2018-04-21T11:18:34.539Z&quot;,
      &quot;test_files&quot;: [
        {
          &quot;path&quot;: &quot;spec/a_spec.rb&quot;,
          &quot;time_execution&quot;: 0.0008809566497802734
        },
        {
          &quot;path&quot;: &quot;spec/b_spec.rb&quot;,
          &quot;time_execution&quot;: 0.019934892654418945
        }
      ]
    },
    {
      &quot;id&quot;: &quot;2fa57fa8-0ff3-4af8-980f-5788c5ab6d21&quot;,
      &quot;node_index&quot;: 0,
      &quot;created_at&quot;: &quot;2018-04-21T11:18:23.750Z&quot;,
      &quot;updated_at&quot;: &quot;2018-04-21T11:18:23.750Z&quot;,
      &quot;test_files&quot;: [
        {
          &quot;path&quot;: &quot;spec/a_spec.rb&quot;,
          &quot;time_execution&quot;: 0.011754035949707031
        },
        {
          &quot;path&quot;: &quot;spec/b_spec.rb&quot;,
          &quot;time_execution&quot;: 0.22170114517211914
        }
      ]
    },
    {
      &quot;id&quot;: &quot;f7a0bdda-54d6-48b2-965a-4b1a4153043d&quot;,
      &quot;node_index&quot;: 1,
      &quot;created_at&quot;: &quot;2018-04-21T11:18:39.506Z&quot;,
      &quot;updated_at&quot;: &quot;2018-04-21T11:18:39.506Z&quot;,
      &quot;test_files&quot;: [
        {
          &quot;path&quot;: &quot;spec/c_spec.rb&quot;,
          &quot;time_execution&quot;: 0.013020992279052734
        },
        {
          &quot;path&quot;: &quot;spec/d_spec.rb&quot;,
          &quot;time_execution&quot;: 0.0174105167388916
        },
        {
          &quot;path&quot;: &quot;spec/e_spec.rb&quot;,
          &quot;time_execution&quot;: 0.23191308975219727
        }
      ]
    },
    {
      &quot;id&quot;: &quot;cd424b01-9dfc-4b6d-be5f-e8acbea6c6f3&quot;,
      &quot;node_index&quot;: 1,
      &quot;created_at&quot;: &quot;2018-04-21T11:18:29.156Z&quot;,
      &quot;updated_at&quot;: &quot;2018-04-21T11:18:29.156Z&quot;,
      &quot;test_files&quot;: [
        {
          &quot;path&quot;: &quot;spec/c_spec.rb&quot;,
          &quot;time_execution&quot;: 0.011027812957763672
        },
        {
          &quot;path&quot;: &quot;spec/d_spec.rb&quot;,
          &quot;time_execution&quot;: 0.019047812957763879
        },
        {
          &quot;path&quot;: &quot;spec/e_spec.rb&quot;,
          &quot;time_execution&quot;: 0.038027712957363670
        }
      ]
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see in the response you have two records for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_index=0&lt;/code&gt; and two for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_index=1&lt;/code&gt; because test suite was executed twice on CI provider.&lt;/p&gt;

&lt;p&gt;You can see &lt;a href=&quot;/api/v1/#builds__id_get&quot;&gt;summary of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET https://api.knapsackpro.com/v1/builds/:id&lt;/code&gt; endpoint here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hope this helps you to do whatever you like to do with your data. We keep data for last 90 days. If you need the data for a longer period please store them on your side. :)&lt;/p&gt;

&lt;p&gt;Manuel, one of our users did great job creating &lt;a href=&quot;https://gist.github.com/manuelpuyol/9e1502cba67fa22c8b5e92b7382bab5a&quot;&gt;ruby script to fetch data from Knapsack Pro API and calculate popular slow tests across multiple CI builds&lt;/a&gt;. Note the script is based on old version of &lt;a href=&quot;/api/v1/#builds_get&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/v1/builds&lt;/code&gt;&lt;/a&gt; API and needs to be adjusted to work with pagination.&lt;/p&gt;
</description>
        <pubDate>Sat, 28 Apr 2018 15:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2018/how-to-export-test-suite-timing-data-from-knapsack-pro-api</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2018/how-to-export-test-suite-timing-data-from-knapsack-pro-api</guid>
        
        
        <category>techtips</category>
        
        <category>API</category>
        
        <category>&quot;test</category>
        
        <category>suite&quot;</category>
        
        <category>&quot;export</category>
        
        <category>data&quot;</category>
        
      </item>
    
      <item>
        <title>CircleCI 2.0 Capybara feature specs - Selenium webdriver with Chrome headless</title>
        <description>&lt;p&gt;I’ve been using Capybara-WebKit for a long time but while switching from CircleCI 1.0 to CircleCI 2.0 I had some problems to use it on the CI.&lt;/p&gt;

&lt;p&gt;This triggered to try Chrome Headless with Selenium Webdriver.  I will show you how to configure Circle CI 2.0 and your Ruby on Rails project to use capybara/selenium/chrome headless together.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/circleci-2-0-capybara-feature-specs-selenium-webdriver-with-chrome-headless/circle.png&quot; style=&quot;width:250px;margin-left: 15px;float:right;&quot; alt=&quot;CircleCI&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;add-capybara-and-selenium-webdriver&quot;&gt;Add capybara and selenium-webdriver&lt;/h2&gt;

&lt;p&gt;Let’s add &lt;a href=&quot;https://github.com/teamcapybara/capybara&quot;&gt;capybara&lt;/a&gt; and &lt;a href=&quot;https://github.com/seleniumhq/selenium&quot;&gt;selenium-webdriver&lt;/a&gt; gems to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# Gemfile&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:development&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;capybara&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;selenium-webdriver&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you already had the gems in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; then ensure you have latest version with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle update capybara selenium-webdriver&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to make sure Capybara feature specs will work on your development machine:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;chromedriver&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If your feature specs fail then upgrade the driver because you may have installed old one.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew upgrade chromedriver&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;configure-capybara&quot;&gt;Configure Capybara&lt;/h2&gt;

&lt;p&gt;Add config file for Capybara:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/config/capybara.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;JS_DRIVER&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:selenium_chrome_headless&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:rack_test&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;javascript_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JS_DRIVER&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_max_wait_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JS_DRIVER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:selenium&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:selenium&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_driver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:selenium_chrome&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:selenium_chrome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;use_default_driver&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ensure you load config files from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/support&lt;/code&gt; directory:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/rails_helper.rb&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# The following line is provided for convenience purposes. It has the downside&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# of increasing the boot-up time by auto-requiring all files in the support&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# directory. Alternatively, in the individual `*_spec.rb` files, manually&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# require only the support files necessary.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;spec/support/**/*.rb&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&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;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;example-feature-spec&quot;&gt;Example feature spec&lt;/h2&gt;

&lt;p&gt;We can create example feature spec to test if everything works:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# spec/features/home_spec.rb&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;feature&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Homepage Features&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root_path&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# it won&apos;t run js code but it is fast&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_content&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Hello World&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# it will run js code&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:js&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_content&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Hello World&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# it will open Firefox&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# remove x from xit to run the test in Firefox on your machine to preview&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;xit&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:selenium&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_content&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Hello World&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# it will open Chrome&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# remove x from xit to run the test in Chrome on your machine to preview&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;xit&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:selenium_chrome&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_content&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Hello World&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And run tests on your development machine with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/rspec spec/features/home_spec.rb&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle exec rspec spec/features/home_spec.rb&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;configure-circleci-20-to-run-chrome-headless&quot;&gt;Configure CircleCI 2.0 to run Chrome headless&lt;/h2&gt;

&lt;p&gt;Here is example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.circleci/config.yml&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .circleci/config.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parallelism&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;working_directory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;~/project-name&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# this is important to use proper image with browsers support&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/ruby:2.4.2-node-browsers&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGHOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;127.0.0.1&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;PGUSER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;project-name&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;circleci/postgres:9.4.12-alpine&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;project-name_test&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;project-name&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;redis:3.2.7&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;checkout&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Restore bundle cache&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache-restore&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# remove space between { {&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;project-name-{ { checksum &quot;Gemfile.lock&quot; }}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Bundle install dependencies&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle install --path vendor/bundle&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Store bundle cache&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cache-save&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# remove space between { {&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;project-name-{ { checksum &quot;Gemfile.lock&quot; }}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vendor/bundle&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Prepare .env, useful if you use dotenv gem&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cp .env.example .env&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Database setup&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake db:create&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake db:schema:load&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Run rspec in parallel&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;shell&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;bundle exec rspec --profile 10 \&lt;/span&gt;
                            &lt;span class=&quot;s&quot;&gt;--format RspecJunitFormatter \&lt;/span&gt;
                            &lt;span class=&quot;s&quot;&gt;--out /tmp/test-results/rspec.xml \&lt;/span&gt;
                            &lt;span class=&quot;s&quot;&gt;--format progress&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Save artifacts&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;store_test_results&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/tmp/test-results&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;speed-up-your-tests-with-circle-ci-parallelisation&quot;&gt;Speed up your tests with Circle CI parallelisation&lt;/h2&gt;

&lt;p&gt;If your feature specs are very long you can save some time by running multiple parallel CI nodes. For instance set it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.circleci/config.yml&lt;/code&gt; and use dynamic RSpec specs allocation across CI nodes with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knapsack_pro&lt;/code&gt; gem and Queue Mode to get optimal test suite split to save as much time as possible.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# .circleci/config.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;parallelism&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;6&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# some tests that are not balanced and executed only on first CI node&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;case $CIRCLE_NODE_INDEX in 0) npm test ;; esac&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# auto-balancing CI build time execution to be flat and optimal (as fast as possible).&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Queue Mode does dynamic tests allocation so the previous not balanced run command won&apos;t&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# create a bottleneck on the CI node&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RSpec via knapsack_pro Queue Mode&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;# export word is important here!&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;export RAILS_ENV=test&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;bundle exec rake &quot;knapsack_pro:queue:rspec[--format documentation]&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can learn &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=circleci-2-0-capybara-feature-specs-selenium-webdriver-with-chrome-headless#x-play-how-it-works-video&quot;&gt;how RSpec test suite parallelisation works&lt;/a&gt; in 1 minute video.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Now you are good to push your code to a repository and see how your Capybara feature specs work with Chrome Headless on CircleCI 2.0.&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Oct 2017 19:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2017/circleci-2-0-capybara-feature-specs-selenium-webdriver-with-chrome-headless</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2017/circleci-2-0-capybara-feature-specs-selenium-webdriver-with-chrome-headless</guid>
        
        
        <category>techtips</category>
        
        <category>ruby</category>
        
        <category>rails</category>
        
        <category>&quot;Circle</category>
        
        <category>CI</category>
        
        <category>2.0&quot;</category>
        
        <category>capybara</category>
        
        <category>&quot;Chrome</category>
        
        <category>headless&quot;</category>
        
      </item>
    
      <item>
        <title>When distributed locks might be helpful in Ruby on Rails application</title>
        <description>&lt;p&gt;During this year I noticed 2 similar concurrency problems with my Ruby on Rails application and I solved them with distributed locks. I’m going to show you how to detect if your application might have a concurrency problem and how to solve it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/when-distributed-locks-might-be-helpful-in-ruby-on-rails-application/distributed_lock.jpg&quot; style=&quot;width:250px;float:right;&quot; alt=&quot;Distributed lock&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Let me start with a bit of context before we discuss the problem. I’m running small SaaS application &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=distributed-locks&quot;&gt;KnapsackPro.com&lt;/a&gt; and the application provides API for the gem &lt;a href=&quot;https://github.com/KnapsackPro/knapsack_pro-ruby&quot;&gt;knapsack_pro&lt;/a&gt;. The whole point of the tool is to optimize time execution of your RSpec, Cucumber etc test suite by splitting tests across CI nodes running in parallel.&lt;/p&gt;

&lt;p&gt;Imagine a scenario where you have 20 minutes long RSpec test suite and you would like to split it across 2 parallel CI nodes. In the perfect case, you should run half of RSpec tests on the first CI node and the second half on the second CI node. In result, your test suite would run only 10 minutes.&lt;/p&gt;

&lt;h2 id=&quot;but-where-is-the-potential-risk-of-the-concurrency-problem&quot;&gt;But where is the potential risk of the concurrency problem?&lt;/h2&gt;

&lt;p&gt;On the Knapsack Pro API side, there is test file queue generated for your CI build. Each CI node periodically requests the Knapsack Pro API via knapsack_pro gem for test files that should be executed next. Thanks to that each CI node will finish tests at the same time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example/queue_mode.jpg&quot; style=&quot;width:150px;&quot; alt=&quot;Knapsack Pro Regular Mode API&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When both CI nodes start work at the same time to execute your test suite then the knapsack_pro gem does a request to Knapsack Pro API to get the list of test files that should be executed on the particular CI node. The first request coming to the Knapsack Pro API is responsible for creating a test file work queue.&lt;/p&gt;

&lt;p&gt;Take a look how code responsible for creating the queue looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_files&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;create_queue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queue_exists?&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;test_files_from_top_of_the_queue&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;At some point, I started getting emails from a customer that they notice that sometimes their test files are executed twice. At first, I could not reproduce the problem and I was wondering what the root problem might be. When I got the second email from one of the customers I was sure that it was not a single case. I spotted that probably something is wrong with the part of the code you see above. I started asking myself, maybe the checking &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create_queue unless queue_exists?&lt;/code&gt; is not enough?&lt;/p&gt;

&lt;p&gt;I knew the production server uses multiple unicorn processes and I was curious what if the checking if the test file queue exists is happening exactly at the same time.&lt;/p&gt;

&lt;p&gt;My idea to reproduce this situation was to write a script that would do concurrent requests in separate ruby threads against my API server. I was hoping if the problem is related to concurrency then this way I should be able to reveal it.&lt;/p&gt;

&lt;p&gt;I prepared the unicorn configuration that I could use in development to run multiple concurrent unicorn processes.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# bin/api_test/unicorn.rb&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;worker_processes&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;preload_app&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;before_fork&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;trap&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;TERM&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Unicorn main worker intercepting TERM and sending myself QUIT instead&apos;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;QUIT&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pid&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;disconnect!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;after_fork&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;trap&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;TERM&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Unicorn worker intercepting TERM and doing nothing. Wait for main worker to send QUIT&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;establish_connection&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Below is my script to run concurrent requests. As you can see I use RSpec here to do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect&lt;/code&gt; and ensure there is no problem with API. When the test files in the work queue are duplicated then the test fails and that means the concurrency problem exists and the test file work queue is created twice instead of just once.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env ruby&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# bin/api_test/initialize_queue&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;knapsack_pro&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;rspec&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Start rails server in development with unicorn&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# to test concurrent requests&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#  $  bundle exec unicorn -p 3000 -c bin/api_test/unicorn.rb&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Run this file with rspec&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#  $ rspec bin/api_test/initialize_queue&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;node_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# use development API&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;KNAPSACK_PRO_ENDPOINT&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;s1&quot;&gt;&apos;http://api.knapsackpro.localhost:3000&apos;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;KNAPSACK_PRO_TEST_SUITE_TOKEN&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;s1&quot;&gt;&apos;333e7b8d1b64fd6447df34a77e3662eb&apos;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;KNAPSACK_PRO_LOG_LEVEL&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;s1&quot;&gt;&apos;warn&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;test_files&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/bar_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/controllers/articles_controller_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/controllers/welcome_controller_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/dir with spaces/foobar_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/features/calculator_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/features/homepage_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/foo_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/services/calculator_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/services/meme_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/timecop_spec.rb&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;s2&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/vcr_spec.rb&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;n&quot;&gt;expected_test_files&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KnapsackPro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TestFilePresenter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;test_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_files_from_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;can_initialize_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commit_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;branch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node_total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KnapsackPro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;API&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;V1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Queues&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;can_initialize_queue: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;can_initialize_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;commit_hash: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;branch: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;branch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;node_total: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node_total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;node_index: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;node_build_id: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;missing-build-id&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;test_files: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;test_files&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;connection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;KnapsackPro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ArgumentError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&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;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors?&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;KnapsackPro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TestFilePresenter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;test_files&apos;&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;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ArgumentError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Couldn&apos;t connect with Knapsack Pro API. Response: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;commit_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SecureRandom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hex&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;all_test_files&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;n&quot;&gt;threads&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;n&quot;&gt;node_total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node_index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;threads&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;can_initialize_queue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;node_all_test_files&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;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;node_subset_test_files&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test_files_from_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;can_initialize_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;commit_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;&apos;api_test&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;node_total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;node_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;test_files&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;node_all_test_files&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node_subset_test_files&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;can_initialize_queue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;

      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;CI node: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node_index&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node_subset_test_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inspect&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node_subset_test_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;node_all_test_files&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;threads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;thr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;all_test_files&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;thr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Ensure queue API returns all test files without duplicates&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;all_test_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected_test_files&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This way I was able to reproduce the problem in development and I had a script thanks to that I could verify if a future fix will be working.&lt;/p&gt;

&lt;h2 id=&quot;how-to-deal-with-concurrency-problem&quot;&gt;How to deal with concurrency problem&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/when-distributed-locks-might-be-helpful-in-ruby-on-rails-application/dining_philosophers_problem.jpg&quot; style=&quot;width:250px;float:right;&quot; alt=&quot;concurrency problem table&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The first thing that came to my mind was that maybe I should write myself some sort of solution. The test file work queue is stored in Redis so I was wondering maybe I could do something on the Redis level to ensure the work queue is created only once.&lt;/p&gt;

&lt;p&gt;I quickly realized that none of my ideas sound reasonable to solve the problem then I looked for options how people deal with the concurrency problem.&lt;/p&gt;

&lt;p&gt;Basically, the conclusion after my research was:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;don’t reinvent the wheel&lt;/li&gt;
  &lt;li&gt;look for proven solutions because distributed problems are complex&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and I found that distributed lock can help to ensure the part of my code will be execute only once at a time.&lt;/p&gt;

&lt;h2 id=&quot;distributed-locking-for-the-rescue&quot;&gt;Distributed locking for the rescue&lt;/h2&gt;

&lt;h3 id=&quot;what-distributed-lock-does&quot;&gt;What distributed lock does?&lt;/h3&gt;

&lt;p&gt;Simply speaking it synchronize access to shared resources in our case the code responsible for creating the test file work queue.
It means different processes (unicorn processes) must operate with shared resources in a mutually exclusive way.&lt;/p&gt;

&lt;h3 id=&quot;why-you-want-a-lock-in-a-distributed-application&quot;&gt;Why you want a lock in a distributed application?&lt;/h3&gt;

&lt;p&gt;There are 2 main reasons why you want to introduce lock:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Efficiency - because you want to avoid expensive computation and save time and money&lt;/li&gt;
  &lt;li&gt;Correctness - you would like to prevent data loss or data corruption. You want to avoid data inconsistency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I found a few gems like &lt;a href=&quot;https://github.com/kenn/redis-mutex&quot;&gt;redis-mutex&lt;/a&gt;, &lt;a href=&quot;https://github.com/mlanett/redis-lock&quot;&gt;redis-lock&lt;/a&gt;, &lt;a href=&quot;https://github.com/leandromoreira/redlock-rb&quot;&gt;redlock-rb&lt;/a&gt;, &lt;a href=&quot;https://github.com/dv/redis-semaphore&quot;&gt;redis-semaphore&lt;/a&gt;. While I was reading about all of those gems and checking their issues and pull requests I learned that distributed problems are even more complex than I thought at first.&lt;/p&gt;

&lt;p&gt;The most reasonable tool seemed to be &lt;a href=&quot;https://github.com/dv/redis-semaphore&quot;&gt;redis-semaphore&lt;/a&gt; gem so I picked that. Here is the sample solution I did.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_files&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;semaphore_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ci_build_id&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# unique ID of CI build&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;expire_lock_after&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# seconds&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;semaphore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Redis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Semaphore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;semaphore_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;host: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;semaphore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expire_lock_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_queue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queue_exists?&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;test_files_from_top_of_the_queue&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see I define semaphore name based on CI build ID because in my business context the concurrency error happened only for the CI build so the ID of the build seems to be the perfect candidate to use it as a semaphore name.&lt;/p&gt;

&lt;p&gt;I also set lock timeout to 5 seconds because I assume in most cases creating the work queue takes milliseconds and if something goes wrong and suddenly it will take seconds then it would be better to expire lock and allow the app to fail rather than lock unicorn process forever.&lt;/p&gt;

&lt;p&gt;When I had the working fix then I validated if it actually solves the problem by using my script to do concurrent requests in ruby threads. Indeed it solved the problem!&lt;/p&gt;

&lt;h2 id=&quot;concurrency-problem-you-most-likely-have-too&quot;&gt;Concurrency problem you most likely have too&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/when-distributed-locks-might-be-helpful-in-ruby-on-rails-application/ruby_on_rails.jpg&quot; style=&quot;width:250px;float:right;&quot; alt=&quot;RoR - Ruby on Rails&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A month ago or so I found another concurrency issue in my API and the problematic code looked like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;find_build&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_build&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# similar to find_or_initialize_by&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;do_something_complex_with_build&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This may look familiar to you because it’s similar to what Rails method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find_or_initialize_by&lt;/code&gt; does. If you have code like that in your codebase then ask yourself what if my code will be executed twice at the same time? Will this cause any problem?&lt;/p&gt;

&lt;h2 id=&quot;what-should-you-remember&quot;&gt;What should you remember?&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Locks are hard&lt;/li&gt;
  &lt;li&gt;Distributed locks are even harder&lt;/li&gt;
  &lt;li&gt;Don’t reinvent the wheel, use proven solutions&lt;/li&gt;
  &lt;li&gt;Most web apps are not thread-safe due to missing locks&lt;/li&gt;
  &lt;li&gt;Expect edge cases while you grow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;final-tips&quot;&gt;Final tips&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Be aware of trade-off. Do you care about efficiency or correctness?&lt;/li&gt;
  &lt;li&gt;Test your endpoints with concurrent requests to reproduce problem and to validate fix will work&lt;/li&gt;
  &lt;li&gt;Remember to use transactions when changing many records but don’t forget DB transactions won’t help with a concurrency problem due to the fact transaction works per DB connection.&lt;/li&gt;
  &lt;li&gt;If possible use unique index to ensure data consistency in DB. When concurrency problem will happen then at least DB will ensure data correctness.&lt;/li&gt;
  &lt;li&gt;Use tested distribution lock solutions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also recommend reading the following article: &lt;a href=&quot;https://makandracards.com/makandra/31937-differences-transactions-locking&quot;&gt;Differences between transactions and locking&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Mon, 17 Jul 2017 05:00:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2017/when-distributed-locks-might-be-helpful-in-ruby-on-rails-application</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2017/when-distributed-locks-might-be-helpful-in-ruby-on-rails-application</guid>
        
        
        <category>techtips</category>
        
        <category>ruby</category>
        
        <category>rails</category>
        
        <category>locks</category>
        
      </item>
    
      <item>
        <title>Auto balancing 7 hours tests between 100 parallel jobs on CI - Buildkite example</title>
        <description>&lt;p&gt;I like tests. You may like them too because thanks to them you are not afraid to change the code. Tests can help you catch errors earlier. I like tests, even more, when they are fast.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example/buildkite.jpg&quot; style=&quot;width:250px;float:right;&quot; alt=&quot;Buildkite&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In large projects tests tend to get bigger and bigger, your test suite starts taking dozens of minutes, then hours. We want to have feedback loop during development and testing short so with big test suites, we go for parallelism.&lt;/p&gt;

&lt;h1 id=&quot;test-suite-parallelism&quot;&gt;Test suite parallelism&lt;/h1&gt;

&lt;p&gt;We can split tests across multiple CI nodes to get faster feedback. There are a few ways how to do it, most common are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple methods of distributing tests across CI nodes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Split tests based on type. For instance, we run unit tests on first CI node and feature tests on second CI node.&lt;/li&gt;
  &lt;li&gt;Split tests based on directory or file names, or number of test files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advanced methods of distributing tests across CI nodes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Split tests based on time execution and running predetermine subset of tests per CI node.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example/regular_mode.jpg&quot; style=&quot;width:150px;&quot; alt=&quot;Knapsack Pro Regular Mode API&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;This way we should get similar time execution on each CI node. But there are cons of this approach. Sometimes tests took different time because of bad CI node performance or the nature of the test like testing external API or feature tests clicking on the website that could take random time.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Dynamic tests split is a way based on recorded tests time execution and work queue.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example/queue_mode.jpg&quot; style=&quot;width:150px;&quot; alt=&quot;Knapsack Pro Queue Mode API&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;Each CI node gets subsets of the test suite from the work queue until the queue is empty. This way we have faster, more efficient tests distribution across CI nodes. There is no bottleneck when one of our CI nodes has worse performance. It will just do less work than others but in the end, we developers get feedback about test suite passing or not as soon as it is possible.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;the-story-of-knapsack&quot;&gt;The story of knapsack&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example/knapsack.jpg&quot; style=&quot;width:150px;float:right;&quot; alt=&quot;Knapsack&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In 2014 I started working on predetermine test suite split solution based on tests time execution. In 2015 I developed more advanced solution called Regular Mode built into the &lt;a href=&quot;https://github.com/KnapsackPro/knapsack_pro-ruby&quot;&gt;gem knapsack_pro&lt;/a&gt; to track tests time execution across commits and branches.&lt;/p&gt;

&lt;p&gt;In late 2016 I created the early version of dynamic test suite split solution based on tests time execution and work queue. Early 2017 I started testing it on large projects with a few companies. It happens the largest projects were using the &lt;a href=&quot;https://buildkite.com/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-balancing-buildkite&quot;&gt;Buildkite.com CI provider&lt;/a&gt;. I’m going to show you why and how to run insanely fast test suite there.&lt;/p&gt;

&lt;h1 id=&quot;what-is-buildkitecom&quot;&gt;What is Buildkite.com&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/posts/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example/buildkite.jpg&quot; style=&quot;width:150px;float:left;margin-right:20px;&quot; alt=&quot;Buildkite&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Bulidkite gives you separation between CI web interface and the build infrastructure. Basically, you can run your tests on any machine with an installed buildkite agent. It can be cloud like AWS or your VPS or even your Mac or Windows. You can save a lot of time of booting your CI nodes with Buildkite because you can keep artifacts like installed gems, migrated DB etc on your machine unlike other CI provider do by uploading them to external store and booting CI node from scratch with every new run.&lt;/p&gt;

&lt;p&gt;Another nice thing about Buildkite is the fact that buildkite agent (CI node) starts work as soon as something is available. There are no locked CI nodes that just finished subset of CI build and waiting until whole CI build is completed.&lt;/p&gt;

&lt;p&gt;There is one more useful thing which is retry failed CI node feature. You can just retry only single CI node with failed tests instead of scheduling a completely new build and rerunning what already passed.&lt;/p&gt;

&lt;h1 id=&quot;how-to-run-7-hours-build-across-100-parallel-jobs-with-knapsack_pro-gem&quot;&gt;How to run 7 hours build across 100 parallel jobs with knapsack_pro gem?&lt;/h1&gt;

&lt;p&gt;We are going to use &lt;a href=&quot;https://docs.knapsackpro.com/overview/#queue-mode-dynamic-split&quot;&gt;knapsack_pro gem with Queue Mode for RSpec&lt;/a&gt;.
You can find here info how to add it to your Gemfile and &lt;a href=&quot;https://docs.knapsackpro.com/knapsack_pro-ruby/guide/&quot;&gt;install knapsack_pro for your project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Knapsack Pro supports buildkite environment variables to determine git commit, branch name and number of parallel jobs. The only thing you need to do is to configure the parallelism parameter in your pipeline step and run the appropriate knapsack_pro command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create pipeline:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you have an account in Buildkite then you need to create a new pipeline for your project.&lt;/p&gt;

&lt;p&gt;You will have to provide a project name and git repository for the code checkout.&lt;/p&gt;

&lt;p&gt;In environment variables section you should set:&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;RACK_ENV=test
RAILS_ENV=test
KNAPSACK_PRO_FIXED_QUEUE_SPLIT=true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.knapsackpro.com/ruby/queue-mode/#dynamic-split-vs-fixed-split&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_FIXED_QUEUE_SPLIT=true&lt;/code&gt;&lt;/a&gt; flag allows us to use retry failed CI node feature on Buildkite.com. When we retry the CI node then we want to run what was run there previously instead of dynamically allocate tests again hence the fixed queue split flag is true.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set step command:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Commands to run: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install &amp;amp;&amp;amp; bundle exec rake db:reset &amp;amp;&amp;amp; bundle exec rake knapsack_pro:queue:rspec&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Label: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RSpec in Queue Mode&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Environment Variables: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC=462c48d886ab38ddcdb81d379379e639&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Parallelism: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;100&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In environment variables, we set Knapsack Pro API key &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/code&gt; per step command. You can &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-balancing-buildkite&quot;&gt;obtain API key here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When everything is filled you can save your pipeline and run your buildkite agents across your CI nodes.
Push a new commit to your repository and allow it to pass. The first CI build run will record time execution of your tests and saves it to Knapsack Pro API.&lt;/p&gt;

&lt;p&gt;Go to user &lt;a href=&quot;https://knapsackpro.com/sessions&quot;&gt;dashboard&lt;/a&gt; and click build metrics link next to your API token. Click show link on the recent build and ensure the time execution data were recorded for all your CI nodes. You should see info that build subsets were collected.&lt;/p&gt;

&lt;p&gt;From now on you can run your tests with optimal test suite split thanks to dynamic tests allocation across CI nodes. The second commit pushed to repo should have better auto balancing time because Knapsack Pro API can use time execution recorded in the previous run to prepare better work queue.&lt;/p&gt;

&lt;p&gt;I tested this with a company who has a large test suite ~7 hours and the average time execution per CI node was ~4 minutes. That is pretty nice and insanely fast pipeline for such large test suite. I’m curious how it will work with your project.&lt;/p&gt;

&lt;h1 id=&quot;what-else-can-i-learn&quot;&gt;What else can I learn?&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;See an example repository of how to run Rails CI with &lt;a href=&quot;https://github.com/KnapsackPro/buildkite-rails-parallel-example-with-knapsack_pro&quot;&gt;Knapsack Pro and test steps in parallel with Buildkite&lt;/a&gt;. Here is an example for &lt;a href=&quot;https://github.com/KnapsackPro/buildkite-rails-docker-parallel-example-with-knapsack_pro&quot;&gt;Docker, Knapsack Pro and Buildkite&lt;/a&gt;.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;What is &lt;a href=&quot;https://knapsackpro.com/faq/question/what-is-optimal-order-of-test-commands&quot;&gt;optimal order of test commands&lt;/a&gt;?&lt;/li&gt;
  &lt;li&gt;Learn more about &lt;a href=&quot;https://knapsackpro.com?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-balancing-buildkite&quot;&gt;Knapsack Pro parallelisation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://knapsackpro.com/ci_comparisons/?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-balancing-buildkite#buildkite&quot;&gt;Compare Buildkite to other CI solutions&lt;/a&gt;, including &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/buildkite/vs/jenkins?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-balancing-buildkite&quot;&gt;Buildkite vs Jenkins&lt;/a&gt;, &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/buildkite/vs/circle-ci?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-balancing-buildkite&quot;&gt;Buildkite vs CircleCI&lt;/a&gt;, and &lt;a href=&quot;https://knapsackpro.com/ci_comparisons/buildkite/vs/github-actions?utm_source=docs_knapsackpro&amp;amp;utm_medium=blog_post&amp;amp;utm_campaign=auto-balancing-buildkite&quot;&gt;Buildkite vs GitHub Actions&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 26 Mar 2017 11:20:00 +0000</pubDate>
        <link>https://docs.knapsackpro.com/2017/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example</link>
        <guid isPermaLink="true">https://docs.knapsackpro.com/2017/auto-balancing-7-hours-tests-between-100-parallel-jobs-on-ci-buildkite-example</guid>
        
        
        <category>continuous_integration</category>
        
      </item>
    
  </channel>
</rss>
