<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Wilco van Esch</title>
  <subtitle>Test Strategy, Defect Prevention, and Faster Feedback Loops by Wilco van Esch.</subtitle>
  <link href="https://wilcovanes.ch/feed.xml" rel="self"/>
  <link href="https://wilcovanes.ch"/>
  <updated>2026-06-09T00:00:00Z</updated>
  <id>https://wilcovanes.ch</id>
  <author>
    <name>Wilco van Esch</name>
  </author>
  
  <entry>
    <title>The European beewolf</title>
    <link href="https://wilcovanes.ch/articles/the-european-beewolf/"/>
    <updated>2026-06-09T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/the-european-beewolf/</id>
    <content type="html">&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/bijenwolf.png&quot; alt=&quot;The beewolf carrying a honey bee victim&quot;&gt;&lt;/figure&gt;
&lt;p&gt;This is a European beewolf.&lt;/p&gt;
&lt;p&gt;If you saw it making low passes around your garden, you might hurry out of its way, or think &amp;quot;is that a fat wasp or two mating ones?&amp;quot; before moving on with your life.&lt;/p&gt;
&lt;p&gt;But there&#39;s a lot of drama happening.&lt;/p&gt;
&lt;p&gt;This beewolf happened to be carrying a paralysed honey bee. The bee had been on its way to drink nectar from flowering oregano plants, where its friends were already gorging themselves. It didn&#39;t notice the beewolf, who had gotten close enough to smell the bee&#39;s cuticular hydrocarbins and confirm that the target was indeed a bee. As the bee went to settle on one of the pink flowers, the beewolf launched itself down at the bee and grabbed it before it even had a chance to land.&lt;/p&gt;
&lt;p&gt;As the beewolf wrapped its legs around the honey bee, the bee tried to frantically stab at her assailant with her venomous stinger. Bees have been flying out in the open for millions of years, they can do so because they can defend themselves. But the beewolf has her own defence, a thick and smooth chitinous exoskeleton. They honey bee struggles in vain, while the beewolf slips her stinger in the articular membranes behind the bee&#39;s front legs, paralysing it.&lt;/p&gt;
&lt;p&gt;At this point, the bee becomes a passive observer. Physically passive, anyway. We have a hint of what may be going on in the tiny bee&#39;s brain. In lab settings, honey bees exposed to simulated predatory attacks show neurochemical shifts which mirror that of mammals when they are experiencing acute anxiety or depression. It may look dead, but it&#39;s still seeing, smelling, detecting touch. Only its muscles are paralysed.&lt;/p&gt;
&lt;p&gt;The bee is brought to the beewolf&#39;s burrows. Not a bustling wasp nest. The beewolf is a solitary wasp, and digs burrows in sandy soil. You could mistake these for ant tunnels. For a little while, the beewolf hovers over the entrance. It takes a bit longer than usual, because someone has moved the bucket that was four inches to the west of it. The beewolf is lining up the visual orientation of objects near the nest to how they were arranged when she left.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/bijenwolf-en-bij.png&quot; alt=&quot;The beewolf and her prey near the entrance to the burrows&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Minutes later, she drops the bee nearby and searches the entrance by close inspection. The bee might sense it&#39;s free, but it&#39;s still immobile. The beewolf finds the entrance, recognising the shape of the sand plug she&#39;d used to close it. Returning with the bee, she opens the entrance and slowly drags the bee down the dark tunnel to the branching burrows where her larvae are wriggling in their humid brood cells. This is where the bee&#39;s journey continues for up to two weeks in lonely, unseen horror.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Inspecting input field placeholder CSS in DevTools</title>
    <link href="https://wilcovanes.ch/articles/inspecting-input-field-placeholder-css-in-devtools/"/>
    <updated>2026-06-02T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/inspecting-input-field-placeholder-css-in-devtools/</id>
    <content type="html">&lt;p&gt;Placeholder text is not shown in the standard DOM. To find it through DevTools&#39;s element inspection, you have to enable the Shadow DOM.&lt;/p&gt;
&lt;p&gt;To do so:&lt;/p&gt;
&lt;h2&gt;Chrome &amp;amp; Safari&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open DevTools&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Settings&lt;/strong&gt; icon (at time of writing: a cog icon in the top right)&lt;/li&gt;
&lt;li&gt;On the &lt;strong&gt;Preferences&lt;/strong&gt; tab (at time of writing this is open by default), scroll to &lt;strong&gt;Elements&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;User agent shadow DOM&lt;/strong&gt; checkbox&lt;/li&gt;
&lt;li&gt;Close the settings pane&lt;/li&gt;
&lt;li&gt;Inspect the input field&lt;/li&gt;
&lt;li&gt;Expand the input field and expand the &lt;code&gt;#shadow-root&lt;/code&gt; node below it&lt;/li&gt;
&lt;li&gt;Click the pseudo-attribute inside it&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Firefox&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open DevTools&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Settings&lt;/strong&gt; icon (at time of writing: triple dots in the top right)&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Settings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Inspector&lt;/strong&gt;, check the checkbox for &lt;strong&gt;Show Browser Shadow DOM&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Inspect the input field&lt;/li&gt;
&lt;li&gt;Expand the input field and expand the &lt;code&gt;#shadow-root&lt;/code&gt; node below it&lt;/li&gt;
&lt;li&gt;Click the pseudo-attribute inside it&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Resolving a unique id constraint violation in PostgreSQL</title>
    <link href="https://wilcovanes.ch/articles/resolving-a-unique-id-constraint-violation-in-postgresql/"/>
    <updated>2024-07-11T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/resolving-a-unique-id-constraint-violation-in-postgresql/</id>
    <content type="html">&lt;p&gt;If you&#39;re working with PostgreSQL and get a unique constraint violation, the first question you have to ask yourself is whether you&#39;ve executed any SQL queries outside of your application.&lt;/p&gt;
&lt;h2&gt;Possible error messages&lt;/h2&gt;
&lt;p&gt;Express app using prisma as ORM:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{&amp;quot;message&amp;quot;:&amp;quot;Failed to create category&amp;quot;,&amp;quot;error&amp;quot;:&amp;quot;&#92;nInvalid &#39;prisma.category.create()&#39; invocation:&#92;n&#92;n&#92;nUnique constraint failed on the fields: (&lt;/code&gt;id&lt;code&gt;)&amp;quot;}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Or directly in PostgreSQL:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ERROR:  duplicate key violates unique constraint&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;You have a table with existing rows of data.&lt;/p&gt;
&lt;p&gt;You&#39;re trying to add 1 or more additional rows.&lt;/p&gt;
&lt;p&gt;PostgreSQL thinks the next available row has id=#, let&#39;s say id=4.&lt;/p&gt;
&lt;p&gt;However, you already have a row with id=4. Perhaps you earlier manually updated the database via &lt;code&gt;psql&lt;/code&gt; or the PgAdmin UI or some batch job ran, but now you&#39;re trying to add data through an application you&#39;re building.&lt;/p&gt;
&lt;p&gt;Therefore, conflict!&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Update the &lt;em&gt;sequence&lt;/em&gt;. The sequence is a number generator, providing unique identifiers for your table rows.&lt;/p&gt;
&lt;p&gt;Begin by starting a shell with &lt;code&gt;psql&lt;/code&gt; for your database and then let&#39;s update the sequence for the affected table.&lt;/p&gt;
&lt;p&gt;First, figure out what the highest id currently is, so we know where the sequence &lt;em&gt;should&lt;/em&gt; be starting.&lt;/p&gt;
&lt;p&gt;Example for a Category table:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SELECT MAX(id) FROM &amp;quot;Category&amp;quot;;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If we get a 4 from this, we&#39;ll want to start the sequence at 5:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ALTER SEQUENCE &amp;quot;Category_id_seq&amp;quot; RESTART WITH 5;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Next time you&#39;re inserting data, it will start at row id=5.&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;This is assuming you&#39;re auto-incrementing your ids. Example for a prisma schema:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;model Category &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt;        Int        @id @default&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;autoincrement&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;You&#39;ll want to do all your future updates via the application you&#39;re building.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Differences in test automation for desktop versus web</title>
    <link href="https://wilcovanes.ch/articles/differences-in-test-automation-for-desktop-versus-web/"/>
    <updated>2024-06-17T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/differences-in-test-automation-for-desktop-versus-web/</id>
    <content type="html">&lt;p&gt;What can you expect when you&#39;re used to writing test automation for web applications and may now be involved in writing tests for desktop applications?&lt;/p&gt;
&lt;h2&gt;Desktop automation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You may have to deal with a splash screen. Solution: poll until you can access the main application window.&lt;/li&gt;
&lt;li&gt;Windows versus macOS. Different UI controls and ability to access them. Test frameworks may only support one OS.&lt;/li&gt;
&lt;li&gt;Custom UI controls and third-party UI controls. Test frameworks may offer partial or no support.&lt;/li&gt;
&lt;li&gt;No headless mode. For headless execution you could use a virtual machine or a display server.&lt;/li&gt;
&lt;li&gt;No implicit wait between application controls, you likely will have to implement your own polling.&lt;/li&gt;
&lt;li&gt;For element selection, you&#39;d be looking for AccessibilityId or element type + name rather than data-test attributes.&lt;/li&gt;
&lt;li&gt;There may be web controls within the desktop application, with data exchanged between desktop and web.&lt;/li&gt;
&lt;li&gt;The installer may have many permutations, for different languages, OSes, and different package contents.&lt;/li&gt;
&lt;li&gt;Desktop UI automation frameworks are very limited compared to web frameworks, and the most maintained ones are rather pricey.&lt;/li&gt;
&lt;li&gt;Slower pipeline execution, due to needing to render all the actual resource-intensive UI and go through native OS APIs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that for many of these considerations, the solution might not involve the desktop UI test framework itself, but rather a separate test harness or set of unit or integration tests.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The economic reality in Lego themes</title>
    <link href="https://wilcovanes.ch/articles/the-economic-reality-in-lego-themes/"/>
    <updated>2024-06-08T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/the-economic-reality-in-lego-themes/</id>
    <content type="html">&lt;p&gt;Some Lego sets (70 from my count) contain a price list of some kind, indicating how much Lego currency a minifig can expect to pay for an item.&lt;/p&gt;
&lt;p&gt;Let&#39;s call this Lego currency &amp;quot;Legu&amp;quot; for convenience, faintly referring to the &lt;a href=&quot;https://en.wikipedia.org/wiki/European_Currency_Unit&quot;&gt;ecu&lt;/a&gt; (Wikipedia), the pre-cursor of the euro. In the Lego games, the currency is called &amp;quot;Studs&amp;quot;, but the bills and coins in actual Lego sets do not resemble these Lego game studs, so we&#39;ll invent &amp;quot;Legu&amp;quot; for the purposes of this article.&lt;/p&gt;
&lt;p&gt;I&#39;ve identified all in-universe pricing in Lego sets from 1949 to June 2024 and made the &lt;a href=&quot;https://www.kaggle.com/datasets/wilcovanesch/item-prices-within-lego-sets&quot;&gt;full list of in-universe prices available on Kaggle&lt;/a&gt;, the data science platform.&lt;/p&gt;
&lt;p&gt;What can we learn from these prices?&lt;/p&gt;
&lt;p&gt;We will discover that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Lego themes interact with each other through trade&lt;/li&gt;
&lt;li&gt;The Duplo theme dominates the agricultural sector&lt;/li&gt;
&lt;li&gt;Hidden poverty is rampant in the City theme&lt;/li&gt;
&lt;li&gt;Most City residents sleep in a prison cell or hospital ward&lt;/li&gt;
&lt;li&gt;The Castle theme is a Mad Max-like dystopia&lt;/li&gt;
&lt;li&gt;Talent shows are all-important in the Friends theme&lt;/li&gt;
&lt;li&gt;Ice lollies are everything to ninjas&lt;/li&gt;
&lt;li&gt;Non-perishables only arrive with new sets&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/lego-set-image-41704.webp&quot; alt=&quot;Lego set image for set 41704&quot;&gt;&lt;/figure&gt;
&lt;sup&gt;&lt;sub&gt;Lego set 41704 Main Street Building by The Lego Group, used under Lego’s Fair Play Policy for non-commercial purposes.&lt;/sub&gt;&lt;/sup&gt;&lt;br&gt;&lt;br&gt;
&lt;h2&gt;Lego themes interact with each other through trade&lt;/h2&gt;
&lt;p&gt;Prices of items that are sold (with price list) in &lt;em&gt;multiple&lt;/em&gt; Lego themes:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;5.00&lt;/td&gt;
&lt;td&gt;Elves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;Castle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;Duplo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;1.99&lt;/td&gt;
&lt;td&gt;Juniors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrots&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrots&lt;/td&gt;
&lt;td&gt;1.99&lt;/td&gt;
&lt;td&gt;Juniors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Croissant&lt;/td&gt;
&lt;td&gt;7.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Croissant&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;City&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Croissant&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;Elves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cupcake&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;Elves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cupcake&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice cream (one scoop)&lt;/td&gt;
&lt;td&gt;10.00&lt;/td&gt;
&lt;td&gt;Ninjago&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice cream (one scoop)&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice cream (three scoops)&lt;/td&gt;
&lt;td&gt;20.00&lt;/td&gt;
&lt;td&gt;Ninjago&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice cream (three scoops)&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (green)&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;City&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (green)&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There&#39;s too much variance within a very small set of items to definitively determine the most expensive themes to live in, but if we take this list of items that have prices in multiple themes, and we make the available data more meaningful by excluding themes that only have a price for one item (Fabuland, Disney, Creator) and by excluding the uncommon non-food items, and we rank the themes by whether they&#39;re more expensive or less expensive than other themes for the same item, we end up with this ranking:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ninjago (most expensive)&lt;/li&gt;
&lt;li&gt;Castle&lt;/li&gt;
&lt;li&gt;City&lt;/li&gt;
&lt;li&gt;Elves&lt;/li&gt;
&lt;li&gt;Juniors&lt;/li&gt;
&lt;li&gt;Friends (least expensive)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If we now compare that to the number of agricultural production sets (livestock farming, poultry farming, fruit &amp;amp; vegetable production) in each theme, we get:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;th&gt;Cost ranking&lt;/th&gt;
&lt;th&gt;Number of &amp;quot;farms&amp;quot;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ninjago&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elves&lt;/td&gt;
&lt;td&gt;1.33&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;City&lt;/td&gt;
&lt;td&gt;1.50&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;td&gt;1.71&lt;/td&gt;
&lt;td&gt;4&lt;a class=&quot;Footnotes__ref&quot; href=&quot;https://wilcovanes.ch/articles/the-economic-reality-in-lego-themes/#mill-footnote-note&quot; id=&quot;mill-footnote-ref&quot; aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot;&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Castle&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;0&lt;a class=&quot;Footnotes__ref&quot; href=&quot;https://wilcovanes.ch/articles/the-economic-reality-in-lego-themes/#friends-footnote-note&quot; id=&quot;friends-footnote-ref&quot; aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot;&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplo&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Juniors&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Why are food prices low in the Elves and Juniors sets despite having no agricultural production?&lt;/h3&gt;
&lt;p&gt;The Elves have more than meets the eye. They grow pumpkins, they grow candycanes, their magical trees grow bizarre fruits we humans don&#39;t even have names for, and they have magic. The Elves will not grow hungry. What they don&#39;t have is regular apples, for which they&#39;re willing to pay a high premium.&lt;/p&gt;
&lt;p&gt;The Juniors theme is a different story. They are a mess. They have pirates, dinosaurs, Spiderman, all sorts of wacky characters. All of them eat regular food, but there is no food production. Instead, the Juniors theme has a close relation to the Duplo theme, in that both are targeted at younger children. That means bigger blocks. But most importantly, it means anarchic creativity. Take a look at set 10422 3-in-1 Space Shuttle Adventure.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/lego-set-image-10422.png&quot; alt=&quot;Lego set image for set 10422&quot;&gt;&lt;/figure&gt;
&lt;sup&gt;&lt;sub&gt;Lego set 10422 3-in-1 Space Shuttle Adventure by The Lego Group, used under Lego’s Fair Play Policy for non-commercial purposes.&lt;/sub&gt;&lt;/sup&gt;&lt;br&gt;&lt;br&gt;
&lt;p&gt;The space shuttle has a winch and a propeller on the outside. It is not skyworthy. But, like the &lt;a href=&quot;https://scifi.stackexchange.com/questions/168982/warhammer-40k-origins-of-ork-psychic-gestalt&quot;&gt;Warhammer 40k Orks&lt;/a&gt; (StackExchange), there is a cheerful playful belief that things will work regardless and — for Duplo figures and Orks alike — that looks to be true.&lt;/p&gt;
&lt;p&gt;Meanwhile in Juniors, you have &lt;a href=&quot;https://brickset.com/sets/10676-1/Knights-Castle&quot;&gt;10676 Knights&#39; Castle&lt;/a&gt; (Brickset), where a catapult is brought to a castle siege despite the castle having an open gate that cannot be closed, and you have &lt;a href=&quot;https://brickset.com/sets/10764-1/City-Central-Airport&quot;&gt;10764 City Central Airport&lt;/a&gt; (Brickset), featuring a jet without a pressurised cabin. Duplo and Juniors are aligned, friendly, and thus trade comes easy.&lt;/p&gt;
&lt;h2&gt;The Duplo theme dominates the agricultural sector&lt;/h2&gt;
&lt;p&gt;The Duplo theme has many, many farms. Much of that production goes towards their own massive world, including the many zoos&lt;a class=&quot;Footnotes__ref&quot; href=&quot;https://wilcovanes.ch/articles/the-economic-reality-in-lego-themes/#zoo-footnote-note&quot; id=&quot;zoo-footnote-ref&quot; aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot;&gt;&lt;/a&gt;, but some of it goes towards Juniors. Duplo and Juniors are united against other themes in preserving their psychic gestalt of bringing imagination to life, as opposed to the more straight-faced and dangerous themes (especially Ninjago, with its relentlessly training ninjas and many barely contained threats), so Duplo limits trade with themes like Ninjago, City, and Castle as much as possible.&lt;/p&gt;
&lt;p&gt;This necessarily gives Duplo huge power over those themes that are dependent on food imports.&lt;/p&gt;
&lt;h2&gt;A different way of looking at the Lego themes&lt;/h2&gt;
&lt;p&gt;But is this how it works?&lt;/p&gt;
&lt;p&gt;So far we&#39;ve considered each theme as comprising a collection of a single instance of each set in its theme. However, in the wild, Lego sets usually appear in people&#39;s homes, in all sorts of configurations. There might be only 1 or several sets from a theme, and sets from other themes, or there might be bricks from multiple themes all mixed together and forming new creations.&lt;/p&gt;
&lt;p&gt;That means a price sign showing a croissant costing 1 Legu might be placed in a space bakery with no grain-supplying planet to be seen anywhere. If The Lego Movie (2014) is canon, then the Lego world is a manifestation of the imagination of whoever plays with the Lego. Anything could be real. The amount of agricultural production is irrelevant, trade agreements are irrelevant, inflation is irrelevant, prices are just numbers.&lt;/p&gt;
&lt;p&gt;That would mean these in-universe price lists I&#39;ve collected are irrelevant. A child (or adult!) can decide whether someone needs to pay 1 Legu for a bouquet of flowers, or 1,000,000 Legu, or whether they can pay with a hug, or can trade for a live crocodile, or whether no-one has to pay for anything.&lt;/p&gt;
&lt;p&gt;In that case it would still be interesting to think about where the prices come from. In the Platonic ideal of the Lego sets as designed and placed in various Lego themes, why are the prices what they are?&lt;/p&gt;
&lt;p&gt;So, let&#39;s think about what else must or could be true in the Lego universe as designed.&lt;/p&gt;
&lt;h2&gt;Hidden poverty is rampant in the City theme&lt;/h2&gt;
&lt;p&gt;City&lt;a class=&quot;Footnotes__ref&quot; href=&quot;https://wilcovanes.ch/articles/the-economic-reality-in-lego-themes/#town-footnote-note&quot; id=&quot;town-footnote-ref&quot; aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot;&gt;&lt;/a&gt; sets have no panhandling, no dilapidated buildings, no vacant storefronts. There are no obvious signs of poverty. However, City has rampant crime. There are 2 prisons, 3 prisoner transports, 4 police stations, 5 mobile police labs, and... &lt;strong&gt;3 houses&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/city-prisoners-60419.png&quot; alt=&quot;Part of Lego set 60419&quot;&gt;&lt;/figure&gt;
&lt;sup&gt;&lt;sub&gt;Lego set 60419 Police Prison Island by The Lego Group, used under Lego’s Fair Play Policy for non-commercial purposes.&lt;/sub&gt;&lt;/sup&gt;&lt;br&gt;&lt;br&gt;
&lt;p&gt;There are also 4 hospitals, suggesting high levels of serious and/or chronic illnesses. There are also no schools at all, except for — notably — the Police Training Academy. There is low education and low chance of advancement in the City theme, with the vast majority of residents working jobs that — judging by the human world — have low pay.&lt;/p&gt;
&lt;p&gt;All signs point towards a world where poverty is a huge problem, but enormous amounts of public funds are put towards suppressing the symptoms instead of improving the minifigs&#39; circumstances.&lt;/p&gt;
&lt;h2&gt;Most City residents sleep in a prison cell or hospital ward&lt;/h2&gt;
&lt;p&gt;As of June 2024, there are a total of 547 City residents. There are 3 houses. That clearly cannot fit, or it would get extremely uncomfortable. So where do the other residents sleep, once they&#39;re done with their jobs and crimes? The only sleeping arrangements available for them are prisons and hospital wards. Surely they can&#39;t all live in prisons and hospitals, since there are so many roadworkers, mechanics, binmen, etc. doing work all across the Lego world? Well, the enormous fleet of police vehicles and ambulances suggests a reality where City residents are brought to their jobs in the morning and returned to prison and — for as far as they&#39;re ambulant — the hospital in the evening.&lt;/p&gt;
&lt;p&gt;It&#39;s a sad reality. Of course, if The Lego Movie is correct, the minifigs only need to sleep when a child (or adult) is playing with them and decides that they need to sleep. If no-one is playing with them, they do not sleep where they are, they simply are lifeless. Therefore, no sleeping arrangements are needed.&lt;/p&gt;
&lt;h2&gt;The Castle theme is a Mad Max-like dystopia&lt;/h2&gt;
&lt;p&gt;Food is extremely scarce in the Castle theme due to low agricultural production. Powerful food hoarders vigorously defend their castles against constant attacks by famished groups. The market village, with a small amount of food available, requires armed guards. Where the Duplo and Friends themes are the safest, the Castle theme appears to portray class inequality in its most extreme form, a fever dream of unending violence between the haves and have-nots.&lt;/p&gt;
&lt;h2&gt;Talent shows are all-important in the Friends theme&lt;/h2&gt;
&lt;p&gt;Why do the Friends characters seem so intensely dedicated to music (Andrea) or sports (Stephanie) or art (Emma)? The answer is talent shows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Set&lt;/th&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;th&gt;Subtheme&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baking show prize&lt;/td&gt;
&lt;td&gt;100,000.00&lt;/td&gt;
&lt;td&gt;Prize&lt;/td&gt;
&lt;td&gt;41393&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;td&gt;Baking&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Talent show prize&lt;/td&gt;
&lt;td&gt;100,000.00&lt;/td&gt;
&lt;td&gt;Prize&lt;/td&gt;
&lt;td&gt;41368&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;td&gt;Music&lt;/td&gt;
&lt;td&gt;2019&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That&#39;s 100,000 Legu. What can you buy in the Friends theme for 100,000 Legu?&lt;/p&gt;
&lt;p&gt;You can buy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;33,333 pizzas with tomato, cheese, olives, and extra mozzarella&lt;/li&gt;
&lt;li&gt;25,000 flower bouquets&lt;/li&gt;
&lt;li&gt;20,000 vintage handbags&lt;/li&gt;
&lt;li&gt;10,000 jet ski rides&lt;/li&gt;
&lt;li&gt;2,000 sunglasses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&#39;s more than 90 years of having pizza every single day.&lt;/p&gt;
&lt;p&gt;Now the obsessive pursuits of Lego mini-dolls make more sense.&lt;/p&gt;
&lt;h2&gt;Ice lollies are everything to ninjas&lt;/h2&gt;
&lt;p&gt;For that half of the 198 items that were easiest to compare to real-life equivalents, I&#39;ve compared the in-universe prices to the prices in Billund, Denmark (or, when not available, in nearby towns or in Copenhagen), based on their local supermarkets, the local sushi shop, the local pizzeria, and so on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; an avocado costs 8.17 krone (DKK) in Billund, and 5 Legu (LGU) in HeartLake City.&lt;/p&gt;
&lt;p&gt;Let&#39;s first look at the top 5 items which are cheapest in the Lego world, relative to the real world:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;th&gt;Price (Legu)&lt;/th&gt;
&lt;th&gt;Price (DKK)&lt;/th&gt;
&lt;th&gt;% of real price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flowers&lt;/td&gt;
&lt;td&gt;Home &amp;amp; Garden&lt;/td&gt;
&lt;td&gt;Castle&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;200.00&lt;/td&gt;
&lt;td&gt;1.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flowers&lt;/td&gt;
&lt;td&gt;Home &amp;amp; Garden&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;td&gt;4.00&lt;/td&gt;
&lt;td&gt;200.00&lt;/td&gt;
&lt;td&gt;2.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yacht ride&lt;/td&gt;
&lt;td&gt;Recreation&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;td&gt;15.00&lt;/td&gt;
&lt;td&gt;750.00&lt;/td&gt;
&lt;td&gt;2.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bicycle&lt;/td&gt;
&lt;td&gt;Recreation&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;4,079.00&lt;/td&gt;
&lt;td&gt;2.45%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chips (en-US: fries)&lt;/td&gt;
&lt;td&gt;Food&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;40.00&lt;/td&gt;
&lt;td&gt;2.50%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Flowers are dirt cheap. Friends has loads of them available, but Castle does not. Perhaps it was a one-time sale in the Medieval Market Village, due to the danger of having anything worth having outside of an actual castle. Boats and bicycles are also very common in the Friends theme. Chips are a bit more puzzling. The potatoes likely come from the farms, but potatoes are rarely seen in Lego sets. So far, from analysing every food item that has ever occurred in any Lego set up to 2015 so far, I know that potatoes have appeared twice, both times in 2002 on a plate that also features cauliflower and peas.&lt;/p&gt;
&lt;p&gt;Now let&#39;s look at the top 5 items which are most expensive in the Lego world, realtive to the real world:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;th&gt;Price (Legu)&lt;/th&gt;
&lt;th&gt;Price (DKK)&lt;/th&gt;
&lt;th&gt;% of real price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (red with chocolate dip)&lt;/td&gt;
&lt;td&gt;Food&lt;/td&gt;
&lt;td&gt;Ninjago&lt;/td&gt;
&lt;td&gt;10.00&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;333.89%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (orange or purple)&lt;/td&gt;
&lt;td&gt;Food&lt;/td&gt;
&lt;td&gt;Ninjago&lt;/td&gt;
&lt;td&gt;7.00&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;233.72%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrot&lt;/td&gt;
&lt;td&gt;Food&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;200.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;Food&lt;/td&gt;
&lt;td&gt;Elves&lt;/td&gt;
&lt;td&gt;5.00&lt;/td&gt;
&lt;td&gt;2.75&lt;/td&gt;
&lt;td&gt;181.82%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ferris wheel ticket&lt;/td&gt;
&lt;td&gt;Recreation&lt;/td&gt;
&lt;td&gt;Creator Expert&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;60.00&lt;/td&gt;
&lt;td&gt;166.67%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We&#39;ve already talked about apples in the Elves theme. They can&#39;t grow them themselves and import them from Duplo at a premium. The price of carrots in Friends is an odd outlier. Carrots are in fact produced in the Friends theme itself, there&#39;s no reason they should be this expensive. It&#39;s possible that they&#39;re just incredibly delicious to Friends mini-dolls and that production can&#39;t be increased to meet demand since that would require a new farm set to be introduced to the theme. Another possibility is that carrots have become part of a speculative frenzy akin to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Tulip_mania&quot;&gt;Dutch tulip mania&lt;/a&gt; (Wikipedia) of the 1630s.&lt;/p&gt;
&lt;p&gt;Next we have the ferris wheel. Either it&#39;s a rare joy the Lego minifigs are happy to pay a premium for, or there&#39;s a taxation on fun (unlikely in a Lego world), or Creator Expert has higher prices overall. Let&#39;s check that.&lt;/p&gt;
&lt;p&gt;Prices in the Creator Expert theme:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Set&lt;/th&gt;
&lt;th&gt;Subtheme&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sushi (hosomaki)&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;Food&lt;/td&gt;
&lt;td&gt;31131&lt;/td&gt;
&lt;td&gt;5 In 1&lt;/td&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sushi (onigiri)&lt;/td&gt;
&lt;td&gt;6.00&lt;/td&gt;
&lt;td&gt;Food&lt;/td&gt;
&lt;td&gt;31131&lt;/td&gt;
&lt;td&gt;4 In 1&lt;/td&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Noodles with egg, onion, and leek&lt;/td&gt;
&lt;td&gt;8.00&lt;/td&gt;
&lt;td&gt;Food&lt;/td&gt;
&lt;td&gt;31131&lt;/td&gt;
&lt;td&gt;3 In 1&lt;/td&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One ferris wheel ticket&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;Recreation&lt;/td&gt;
&lt;td&gt;10247&lt;/td&gt;
&lt;td&gt;Fairground Collection&lt;/td&gt;
&lt;td&gt;2014&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two ferris wheel tickets&lt;/td&gt;
&lt;td&gt;200.00&lt;/td&gt;
&lt;td&gt;Recreation&lt;/td&gt;
&lt;td&gt;10247&lt;/td&gt;
&lt;td&gt;Fairground Collection&lt;/td&gt;
&lt;td&gt;2014&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;To compare this to other themes, sushi also appears in Friends and Ninjago. The prices are slightly cheaper than in Ninjago, and slightly more expensive than in Friends. Creator Expert does not appear to be unusually expensive.&lt;/p&gt;
&lt;p&gt;Now let&#39;s compare the ferris wheel ticket price to other attraction prices across the Lego world.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jet ski (rent)&lt;/td&gt;
&lt;td&gt;8.00&lt;/td&gt;
&lt;td&gt;City&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jet ski ride&lt;/td&gt;
&lt;td&gt;10.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Movie ticket&lt;/td&gt;
&lt;td&gt;50.00&lt;/td&gt;
&lt;td&gt;Advanced Models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ferris wheel ride&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;Creator expert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollercoaster ride&lt;/td&gt;
&lt;td&gt;30.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speedboat (rent)&lt;/td&gt;
&lt;td&gt;6.00&lt;/td&gt;
&lt;td&gt;City&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surfboard (rent)&lt;/td&gt;
&lt;td&gt;4.00&lt;/td&gt;
&lt;td&gt;City&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ticket (amusement park)&lt;/td&gt;
&lt;td&gt;5.00&lt;/td&gt;
&lt;td&gt;Fabuland&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yacht ride&lt;/td&gt;
&lt;td&gt;15.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For the price of a ferris wheel ride in Creator Expert costs you could go on a rollercoaster 3 times in Friends, or have 10 jet ski rides.&lt;/p&gt;
&lt;p&gt;It&#39;s a nice ferris wheel, to be fair, and tall! According to Lego, the &lt;a href=&quot;https://www.lego.com/en-gb/product/ferris-wheel-10247&quot;&gt;ferris wheel&lt;/a&gt; is 60 cm tall, whereas a minifig is 4 cm tall. That means it&#39;s as high as 15 minifigs stacked on top of each other.&lt;/p&gt;
&lt;p&gt;For comparison, the &lt;a href=&quot;https://www.tivoli.dk/en/haven-og-forlystelser/forlystelser/ballongyngen&quot;&gt;classic ferris wheel in Tivoli, Copenhagen&lt;/a&gt; is 18.7 m high and the average height of a Dane (male or female) is 1.74 m. That means the ferris wheel is as tall as ten and three quarter Danes (the people, not the dog) stacked on top of each other. What an image.&lt;/p&gt;
&lt;p&gt;So the Lego ferris wheel is relatively higher. Perhaps the ferris wheel is just worth the price. It&#39;s also possible that it&#39;s just convenient to charge 100 Legu, since 100 Legu bills are the &lt;strong&gt;only&lt;/strong&gt; Lego bills&lt;a class=&quot;Footnotes__ref&quot; href=&quot;https://wilcovanes.ch/articles/the-economic-reality-in-lego-themes/#denominations-footnote-note&quot; id=&quot;denominations-footnote-ref&quot; aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot;&gt;&lt;/a&gt; and the coins of 10, 20, 30, 40 Legu were discontinued, but not many items are actually charged at 100 Legu. The only ones are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bike&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camera&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Christmas tree (small)&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;Advanced Models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One ferris wheel ticket&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;Creator Expert&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In the case of the Friends theme, the sets show that mini-dolls can actually pay by phone through contactless payment, so bills aren&#39;t that relevant for them.&lt;/p&gt;
&lt;p&gt;Now let&#39;s get back to the ice lollies. These are, relatively, the most expensive. We can see that&#39;s the case in the Ninjago theme, but what about themes that are not dependent on food imports?&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (blue)&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (champagnebrus or pink)&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (champagnebrus, lime with chocolate dip)&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (champagnebrus, lime with chocolate dip)&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (green)&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;City&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (green)&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (pink)&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ice lolly (pink)&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;Friends&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;With an average price of exactly 2 Legu, that means with a typical Danish price of 3 krone, it seems like ice lollies are sought-after in the Lego world, but absolutely life-affirming in the Ninjago theme. Ninjas love ice lollies, who knew?&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/ninja-champagnebrus-ispind.png&quot; alt=&quot;Very basic image of a ninja with an ice lolly&quot;&gt;&lt;/figure&gt;
&lt;sup&gt;&lt;sub&gt;I asked ChatGPT to generate an image of a ninja eating a typically Danish champagnebrus ice lolly.&lt;/sub&gt;&lt;/sup&gt;&lt;br&gt;&lt;br&gt;
&lt;h2&gt;Non-perishables only arrive with new sets&lt;/h2&gt;
&lt;p&gt;There are farms, but no factories. Where do the cameras (100 Legu) come from? What about the nail polish (12 Legu)? Or the hairspray (3 Legu)? There is only one possible explanation. Perishable items have to be produced, but non-perishables arrive in the Lego world with new Lego sets. The items that are introduced are the only ones the Lego residents will have at their disposal. You can imagine how delighted the existing residents are when new items arrive.&lt;/p&gt;
&lt;p&gt;The problem is that this also means that new characters can arrive that are identical to existing characters. For example, Friends sets have a cast of characters which appear in many sets. This would be less of a problem in a child&#39;s way of playing, where either the clones get thrown aside or the clones become twins or friends that simply look alike. But in the Lego world&#39;s platonic ideal it wouldn&#39;t make sense.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It looks, more and more, like The Lego Movie is indeed the reality of how the Lego world works. Price lists are created to roughly reflect real life, enough to engage the imagination, nothing more.&lt;/p&gt;
&lt;p&gt;What matters is not that a Danish champagnebrus ice lolly costs 1 or 2 Legu depending on whether you&#39;re at a water park or a funfair, what matters is what crazy shenanigans happen at the water park or funfair.&lt;/p&gt;
&lt;p&gt;This entire exercise of categorising and analysing price lists was a President Business activity. The full dataset is &lt;a href=&quot;https://www.kaggle.com/datasets/wilcovanesch/item-prices-within-lego-sets&quot;&gt;publicly available on Kaggle&lt;/a&gt;, but this President Business is going to build something with his nearest tiny human.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The paradox of efficiency in risk-based testing</title>
    <link href="https://wilcovanes.ch/articles/the-paradox-of-efficiency-in-risk-based-testing/"/>
    <updated>2024-05-10T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/the-paradox-of-efficiency-in-risk-based-testing/</id>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;But Wilco, how can risk-based testing be more efficient when it requires an analysis of what areas of functionality could have high risk or high impact?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a very reasonable question.&lt;/p&gt;
&lt;h2&gt;Risk-based testing&lt;/h2&gt;
&lt;p&gt;Risk-based testing means you focus the testing effort where it&#39;s most likely to pay off, to identify issues we&#39;re going to care about most, since they&#39;re most likely to occur and most impactful when they do.&lt;/p&gt;
&lt;h2&gt;Is it really efficient?&lt;/h2&gt;
&lt;p&gt;Risk-based testing seems to imply a comprehensive upfront analysis of where issues are likely to occur and what impact they are likely to have when they do occur. Indeed it&#39;s not an uncommon approach and ends up with a satisfyingly thorough and structured overview which, however, undoes the efficiency the risk-based testing could have had.&lt;/p&gt;
&lt;h2&gt;Alternatives&lt;/h2&gt;
&lt;p&gt;It doesn&#39;t have to be that way. You could of course try and reduce the thoroughness of the risk assessment by - for example - identifying all scenarios you can think of ahead of time and get the person most intimately acquainted with the area of functionality to give a simple Low/Medium/High/Critical estimation of risk and the person most intimiately acquainted with the users to give their simple Low/Medium/High/Critical estimation of impact, but in the dynamic environments I like to work in it gives more return on investment to be less formal and use the strengths of experienced test specialists and collaborative teams:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In conversation with the team (whether it be a mobbing/ensembling/teaming/pairing session or a Scrum ceremony or something else), ask questions like &lt;em&gt;&amp;quot;what absolutely must not fail?&amp;quot;&lt;/em&gt; (impact) and &lt;em&gt;&amp;quot;what do we feel least confident about?&amp;quot;&lt;/em&gt; (risk).&lt;/li&gt;
&lt;li&gt;Use your prior experiences and intuition as a test specialist to gain suspicions that you can then investigate.&lt;/li&gt;
&lt;li&gt;Use the increasing real information about the system under test while testing to refine your informal risk and impact assessment and guide your continued testing.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Add a second occlusion area in a tileset tile in Godot</title>
    <link href="https://wilcovanes.ch/articles/add-a-second-occlusion-area-in-a-tileset-tile-in-godot/"/>
    <updated>2024-04-27T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/add-a-second-occlusion-area-in-a-tileset-tile-in-godot/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;Tested with Godot 4.2.1.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In a collision layer, you can paint a second collision area with the &#39;Add polygon tool&#39;. However, in an &lt;em&gt;occlusion&lt;/em&gt; layer adding a second collision area removes the first one.&lt;/p&gt;
&lt;p&gt;To work around this, you can add a second occlusion layer:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In &lt;strong&gt;TileSet&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Paint&lt;/strong&gt;, paint the first occlusion area on the tile on Occlusion Layer 0&lt;/li&gt;
&lt;li&gt;Save the changes to the scene&lt;/li&gt;
&lt;li&gt;Under the &lt;strong&gt;TileMap&lt;/strong&gt; node&#39;s properties, open the &lt;strong&gt;Tile Set&lt;/strong&gt; properties&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Rendering&lt;/strong&gt;, click &lt;strong&gt;Add Element&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;TileSet&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Paint&lt;/strong&gt;, paint the second occlusion area on the tile on Occlusion Layer 1&lt;/li&gt;
&lt;li&gt;Save the changes to the scene&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>The early modern period of European history</title>
    <link href="https://wilcovanes.ch/articles/the-early-modern-period-of-european-history/"/>
    <updated>2023-09-03T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/the-early-modern-period-of-european-history/</id>
    <content type="html">&lt;p&gt;There is no weirder, wilder time and place than Europe from about 1400 to 1800 CE.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Poland-Lithuania&lt;/strong&gt; has an &lt;em&gt;elective monarchy&lt;/em&gt;. They choose their king, and not just from a single family. The high drama of inheritance is subverted by gathering thousands of nobles together (who themselves did inherit their titles) and having them play out lots of small dramas until they select one of the candidates to rule them. I imagine myself sitting on an earthen wall watching the wheeling and dealing, the outfits, the tents and wagons and horses. Much more fun than watching a king&#39;s sour-faced response to having sired another daughter.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Holy Roman Empire&lt;/strong&gt; contains an absolutely dizzying number (roughly 1800 at one point) of bishoprics, principalities, duchies, free cities, and other semi-sovereign authorities. Many are non-contiguous, with enclaves and exclaves littering the empire. Microscopic states with different measurements of weight and distance, different times, different coinage, different laws, different privileges. Somehow it keeps working for centuries. It&#39;s a bizarre, wonderful, and powerful mess. With so many noble families, it&#39;s also no surprise you find many Germans in foreign dynasties. Georg Ludwig, ruler of the &lt;strong&gt;Electorate of Hanover&lt;/strong&gt;, becomes George I, King of &lt;strong&gt;Great Britain and Ireland&lt;/strong&gt;. Sophie von Anhalt-Zerbst, from the ruling family of the &lt;strong&gt;Principality of Anhalt&lt;/strong&gt;, becomes Catherine the Great, Empress of &lt;strong&gt;Russia&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Castile&lt;/strong&gt; and &lt;strong&gt;Aragon&lt;/strong&gt; band together under the power couple Ferdinand &amp;amp; Isabella, who complete the &lt;em&gt;Reconquista&lt;/em&gt; - pushing the last Caliphate out of the Iberian peninsula - and fund Columbus&#39; expedition to find a western trade route to Asia, which will fail &amp;quot;happily&amp;quot;, soon leading to the Pope dividing the world into land that&#39;s known, land that belongs to the Portuguese, and land that belongs to the Spanish. The age of colonisation and vast trade empires starts here.&lt;/p&gt;
&lt;p&gt;Instead of a united &lt;strong&gt;Italy&lt;/strong&gt;, there are the rich city states like &lt;strong&gt;Florence&lt;/strong&gt;, &lt;strong&gt;Milan&lt;/strong&gt;, and &lt;strong&gt;Bologna&lt;/strong&gt;, as well as the pragmatic and hypercapitalist maritime trade republics of &lt;strong&gt;Genoa&lt;/strong&gt; and &lt;strong&gt;Venice&lt;/strong&gt;, along with the influential &lt;strong&gt;Papal States&lt;/strong&gt;, and the contested &lt;strong&gt;Kingdom of Naples&lt;/strong&gt;, all huddled together south of the Alps. Art, literature, and science flourish. So does political warring and scheming.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Reformation&lt;/em&gt; focuses Europe&#39;s lively religious debate, including a &lt;em&gt;Thirty Years War&lt;/em&gt; during which an estimated 20% of the European population dies prematurely (though mostly to disease and famine). Henry VIII invents the Anglican Church so he can divorce his wife.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Mongol Empire&lt;/strong&gt; has shrunk back and disintegrated into smaller independent hordes, with the Russian principalities regaining independence only to get absorbed by the Grand Duchy of Moscow into what will become almost a reverse Mongol expansion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Denmark&lt;/strong&gt;, &lt;strong&gt;Sweden&lt;/strong&gt;, and &lt;strong&gt;Norway&lt;/strong&gt; have an on-again, off-again relationship in the form of the &lt;em&gt;Kalmar Union&lt;/em&gt;, with the balance of power shifting increasingly away from Denmark and towards Sweden except for a (for Sweden) disastrous &lt;em&gt;Great Northern War&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Everywhere in Europe, medieval kingdoms and other small territories combine into larger entities which become global powers. The Lowlands revolt against Spain and form the &lt;strong&gt;United Provinces of the Netherlands&lt;/strong&gt;. &lt;strong&gt;England&lt;/strong&gt;, &lt;strong&gt;Wales&lt;/strong&gt;, and &lt;strong&gt;Scotland&lt;/strong&gt; from the &lt;strong&gt;Kingdom of Great Britain&lt;/strong&gt;. The &lt;strong&gt;Kingdom of France&lt;/strong&gt; expels the English from the continent and incorporates its last feudal territories. None of them are satisfied with this success and start covering the world&#39;s oceans with cannons on wooden platforms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;France&lt;/strong&gt; and &lt;strong&gt;England&lt;/strong&gt; fight the &lt;em&gt;Hundred Years War&lt;/em&gt; for hundred-and-sixteen years, with chivalrous knights fighting chivalrous knights, archers fighting men-at-arms, light cavalry fighting buildings, and England&#39;s &lt;em&gt;War of the Roses&lt;/em&gt; would end up inspiring a brutal and unsatisfying hit TV show. However, the real game of thrones is played by &lt;strong&gt;Austria&lt;/strong&gt;&#39;s Habsburg dynasty, who use every trick in the book to expand their dominions.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Ottoman Empire&lt;/strong&gt; moves from dramatic siege to dramatic siege, puts the final nails in the coffin of the &lt;strong&gt;Byzantine Empire&lt;/strong&gt; (the last remnant of ancient Rome, though Greek), gets on the bad side of Vlad Dracula, assimilates Christian child slaves into its armies as its elite household troops, and in a relentless drive to the heart of Europe knocks on the doors of Vienna, where the wave finally breaks and rolls back.&lt;/p&gt;
&lt;p&gt;That&#39;s not even mentioning the &lt;strong&gt;Hanseatic League&lt;/strong&gt;, the &lt;em&gt;Burgundian Succession&lt;/em&gt;, the &lt;em&gt;French Revolution&lt;/em&gt;, the &lt;em&gt;Enlightenment&lt;/em&gt;, and so much more. These are dull words hiding volumes of personal and collective tragedies and successes, the long term consequences of which we face every day. You could throw a dart at a map of Europe, pick a random year between 1400 and 1800, and you&#39;re guaranteed to land on a town or region at a time when they&#39;re going through a decisive and fascinating part of their history.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>No, you shouldn&#39;t NOT use unit tests</title>
    <link href="https://wilcovanes.ch/articles/no-you-shouldnt-not-use-unit-tests/"/>
    <updated>2023-08-18T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/no-you-shouldnt-not-use-unit-tests/</id>
    <content type="html">&lt;p&gt;How valuable are unit tests?&lt;/p&gt;
&lt;p&gt;After more than a decade of &amp;quot;they&#39;re useless!&amp;quot; and &amp;quot;they&#39;re essential!&amp;quot; articles and discussions, we&#39;re no closer to an answer. That&#39;s not because it&#39;s one of those nuanced practices where we need to consider the context of the application, the codebase, the specific functionality, the team&#39;s skill, the business objectives, the stage the business is in, and so on. We should, but that&#39;s not where people disagree. Rather they seem to disagree due to miscommunication.&lt;/p&gt;
&lt;h2&gt;Semantics&lt;/h2&gt;
&lt;p&gt;A unit does not &lt;strong&gt;necessarily&lt;/strong&gt; mean a class or a function or method. In a &lt;a href=&quot;https://www.martinfowler.com/bliki/UnitTest.html&quot;&gt;sociable unit test&lt;/a&gt;, you can test a functional unit that might be a class method which depends on several methods in other classes instead of mocking them out.&lt;/p&gt;
&lt;p&gt;In some discussions, two people have been arguing for that exact kind of test that&#39;s fast and meaningful and maintainable and easy to debug while one of them calls it integration testing and the other calls it unit testing.&lt;/p&gt;
&lt;h2&gt;Purpose&lt;/h2&gt;
&lt;p&gt;Sometimes the argument against unit tests is that they don&#39;t give sufficient confidence about the &lt;strong&gt;system&lt;/strong&gt;. That&#39;s not what unit tests are for. That&#39;s why you don&#39;t &lt;em&gt;just&lt;/em&gt; have unit tests, but tests at all levels depending on the balance of how much information they give you about the system versus how costly it is to gain that information and act on it.&lt;/p&gt;
&lt;h2&gt;Return on investment&lt;/h2&gt;
&lt;p&gt;One person will claim unit tests are a waste of time and another that they&#39;ve saved them so much time. Who is right? Well, both of them.&lt;/p&gt;
&lt;p&gt;You can easily make unit tests a maintenance horror if you write as many of them as you can. You can also make them save time by writing them for functional logic that has to work and for coverage of many permutations of input.&lt;/p&gt;
&lt;p&gt;You can make unit tests a waste of time if you don&#39;t know what a good test looks like. You can make them save time by writing fast, isolated, deterministic, highly readable tests for loosely coupled, testable code.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I see unit tests as valuable and I think anyone would if they shared an understanding that unit tests can be light integration tests, if they understood the comparative value versus other types of testing, and if they&#39;ve come to understand the difference between a good unit test and a bad unit test.&lt;/p&gt;
&lt;p&gt;However, I suspect that in another decade the same argument will still be had.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How to disable spell check in Word online (Office 365)</title>
    <link href="https://wilcovanes.ch/articles/how-to-disable-spell-check-in-word-online-office-365/"/>
    <updated>2023-08-11T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-to-disable-spell-check-in-word-online-office-365/</id>
    <content type="html">&lt;p&gt;Disabling spell check in Word online is not very intuitive.&lt;/p&gt;
&lt;p&gt;If the display language is English:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sign in to Office 365&lt;/li&gt;
&lt;li&gt;Open any Word document&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Review&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Hit the arrow next to the &lt;strong&gt;Editor&lt;/strong&gt; icon* to open the dropdown menu&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Set Proofing Language...&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Check &lt;strong&gt;Don&#39;t check spelling or grammar&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;OK&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;*The icon looks like an &amp;quot;abc&amp;quot; with checkmark, previously looked like a pen, and may have changed again when you read this.&lt;/p&gt;
&lt;p&gt;Als de weergavetaal Nederlands is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log in bij Office 365&lt;/li&gt;
&lt;li&gt;Open een Word document&lt;/li&gt;
&lt;li&gt;Ga naar de &lt;strong&gt;Controleren&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Druk het pijltje naast het &lt;strong&gt;Spelling en grammatica&lt;/strong&gt; icoon* om het menu te openen&lt;/li&gt;
&lt;li&gt;Druk &lt;strong&gt;Controletaal instellen...&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Vink &lt;strong&gt;Geen spelling- of grammaticacontrole uitvoeren&lt;/strong&gt; aan&lt;/li&gt;
&lt;li&gt;Druk &lt;strong&gt;OK&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;*Het icoon ziet eruit als een &amp;quot;abc&amp;quot; met vinkje, en was daarvoor een pen, maar zou ondertussen weer veranderd kunnen zijn.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Collaboration techniques in test &amp; development</title>
    <link href="https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/"/>
    <updated>2023-08-04T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#3-amigos&quot;&gt;3 Amigos&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#bug-bash&quot;&gt;Bug bash&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#ensemble-programming&quot;&gt;Ensemble programming&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#ensemble-testing&quot;&gt;Ensemble testing&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#feature-kick-off&quot;&gt;Feature kick-off&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#mob-programming&quot;&gt;Mob programming&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#pair-programming&quot;&gt;Pair programming&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#pair-testing&quot;&gt;Pair testing&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#software-teaming&quot;&gt;Software teaming&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#swarming&quot;&gt;Swarming&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#variations&quot;&gt;Variations&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;3 Amigos&lt;a name=&quot;3-amigos&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Product + Dev + QA discuss work increments&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does not involve the whole team&lt;/li&gt;
&lt;li&gt;Make the acceptance criteria &lt;a href=&quot;https://en.wikipedia.org/wiki/SMART_criteria&quot;&gt;SMART&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Uncover missing or extraneous acceptance criteria&lt;/li&gt;
&lt;li&gt;Make risks explicit which are not covered by the acceptance criteria&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bug bash&lt;a name=&quot;bug-bash&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;The whole team performs exploratory testing together&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Could choose to do one before a major release&lt;/li&gt;
&lt;li&gt;Should not be done on untested code or code with many known issues&lt;/li&gt;
&lt;li&gt;Clearly communicate the scope of the bug bash&lt;/li&gt;
&lt;li&gt;Assign people to areas of functionality or type of risk&lt;/li&gt;
&lt;li&gt;Make it double as a fun, cross-functional bonding experience&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a variation, see &lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#ensemble-testing&quot;&gt;Ensemble testing&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Ensemble programming&lt;a name=&quot;ensemble-programming&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;The whole team working on one thing at one keyboard&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Advantages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Best of all your abilities&lt;/li&gt;
&lt;li&gt;Better code quality&lt;/li&gt;
&lt;li&gt;More diverse design options&lt;/li&gt;
&lt;li&gt;Issues resolved more quickly&lt;/li&gt;
&lt;li&gt;Knowledge transfer&lt;/li&gt;
&lt;li&gt;Shared course of action&lt;/li&gt;
&lt;li&gt;Continuous code review&lt;/li&gt;
&lt;li&gt;Higher satisfaction&lt;/li&gt;
&lt;li&gt;Builds better communicators&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Disadvantages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Can be slower than the whole team working in parallel&lt;/li&gt;
&lt;li&gt;Requires interaction with many others
&lt;ul&gt;
&lt;li&gt;active involvement&lt;/li&gt;
&lt;li&gt;good communication&lt;/li&gt;
&lt;li&gt;empathy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Doing it remotely is difficult&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What a Driver can do&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Ask to hear from voices they’re not hearing much from&lt;/li&gt;
&lt;li&gt;Point out code that’s long / complex / misnamed / duplicated&lt;/li&gt;
&lt;li&gt;Ask for more (or less) detail about what to type&lt;/li&gt;
&lt;li&gt;Suggest an improvement for the ensemble’s consideration&lt;/li&gt;
&lt;li&gt;Try to use a new IDE feature, shortcut, or tool that could help&lt;/li&gt;
&lt;li&gt;Celebrate a good result, elegant solution, or helpful contribution&lt;/li&gt;
&lt;li&gt;Give extra support to contributions of more junior team members&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What a Navigator can do&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Filter ideas and suggestions into concrete things to type&lt;/li&gt;
&lt;li&gt;Suggest unit test scenarios and using Red, Green, Refactor&lt;/li&gt;
&lt;li&gt;Find &amp;amp; share relevant information from documentation &amp;amp; web&lt;/li&gt;
&lt;li&gt;Point out repetitive tasks that could be automated&lt;/li&gt;
&lt;li&gt;Get ideas from the group, especially from quieter voices&lt;/li&gt;
&lt;li&gt;Amplify the perspectives of non-programmers&lt;/li&gt;
&lt;li&gt;Move a stalled discussion forward by making a bad suggestion&lt;/li&gt;
&lt;li&gt;Listen actively, ready to pitch in when needed&lt;/li&gt;
&lt;li&gt;Ask questions to make sure you know exactly what’s happening&lt;/li&gt;
&lt;li&gt;Draw out a possible solution or alternatives on a whiteboard&lt;/li&gt;
&lt;li&gt;Record notes about things to remember or investigate&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=p_pvslS4gEI&quot;&gt;A day of Mob Programming - Woody Zuill&lt;/a&gt; (YouTube)&lt;br&gt;
&lt;a href=&quot;https://www.remotemobprogramming.org/&quot;&gt;Remote mob programming guide&lt;/a&gt; (external website)&lt;br&gt;
&lt;a href=&quot;https://underthehood.meltwater.com/blog/2016/06/01/mob-programming/&quot;&gt;Mob Programming - the Good, the Bad and the Great.&lt;/a&gt; (external website)&lt;/p&gt;
&lt;h2&gt;Ensemble testing&lt;a name=&quot;ensemble-testing&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;The whole team joins in a test activity&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Compared to ensemble programming&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Also involves the whole team&lt;/li&gt;
&lt;li&gt;Involves the same techniques&lt;/li&gt;
&lt;li&gt;Can be used as an exploratory bug bash&lt;/li&gt;
&lt;li&gt;Can be used to improve or write test automation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Feature kick-off&lt;a name=&quot;feature-kick-off&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Whiteboard technical solution / discuss how to test&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Compared to ensemble programming&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Does not involve the whole team&lt;/li&gt;
&lt;li&gt;May whiteboard possible solutions&lt;/li&gt;
&lt;li&gt;May define a back-end/front-end contract&lt;/li&gt;
&lt;li&gt;Generate test scenarios and how to test them&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Mob programming&lt;a name=&quot;mob-programming&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See &lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#ensemble-programming&quot;&gt;Ensemble programming&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Pair programming&lt;a name=&quot;pair-programming&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Two developers working on one thing at one keyboard&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Advantages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Best of both your abilities&lt;/li&gt;
&lt;li&gt;Better code quality&lt;/li&gt;
&lt;li&gt;More diverse design options&lt;/li&gt;
&lt;li&gt;Issues resolved more quickly&lt;/li&gt;
&lt;li&gt;Knowledge transfer&lt;/li&gt;
&lt;li&gt;Shared course of action&lt;/li&gt;
&lt;li&gt;Continuous code review&lt;/li&gt;
&lt;li&gt;Higher satisfaction&lt;/li&gt;
&lt;li&gt;Builds better communicators&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Disadvantages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Can be slower than working on two things in parallel&lt;/li&gt;
&lt;li&gt;Requires interaction with another human being (active involvement, good communication, empathy)&lt;/li&gt;
&lt;li&gt;Synchronous, requires a call or sit-together&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;How to start&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Set expectations low. It’s like paddling a tandem canoe. It’s easier to keep a canoe straight with two people paddling, but it will take time to learn to work well with another person.&lt;/li&gt;
&lt;li&gt;Be physically at the same desk. You can pair program remotely just fine, but preferably not on the first try.&lt;/li&gt;
&lt;li&gt;Agree on how you’re going to do it before you start doing it. Which pairing style? Switch roles? Stop when? Pause when? Split tasks and return to pairing when?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=aItVJprLYkg&quot;&gt;You Must Be CRAZY To Do Pair Programming - Dave Farley&lt;/a&gt; (YouTube)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Pair testing&lt;a name=&quot;pair-testing&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;A developer and a tester testing at one keyboard&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Advantages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Efficient test-fix-retest cycle&lt;/li&gt;
&lt;li&gt;Creates developer-tester rapport&lt;/li&gt;
&lt;li&gt;Spreads test techniques &amp;amp; thinking&lt;/li&gt;
&lt;li&gt;Knowledge sharing (development)&lt;/li&gt;
&lt;li&gt;Knowledge sharing (product)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Disadvantages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Requires interaction with another human being
&lt;ul&gt;
&lt;li&gt;active involvement&lt;/li&gt;
&lt;li&gt;effective communication&lt;/li&gt;
&lt;li&gt;empathy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Synchronous, requires a call or sit-together&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;How to start&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Try the strong pairing technique first, where the person most skilled in testing provides test scenarios, ideas, questions to the person at the keyboard.&lt;/li&gt;
&lt;li&gt;Pair early, before even code reviews have been started - it will make up for potential retesting time after code review comments&lt;/li&gt;
&lt;li&gt;Measure cycle time for similarly sized tickets when they are versus when they are not pair tested&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ctRD2KBUYSI&quot;&gt;Styles of Pair Testing - Maaret Pyhäjärvi&lt;/a&gt; (YouTube)&lt;/p&gt;
&lt;h2&gt;Software teaming&lt;a name=&quot;software-teaming&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See &lt;a href=&quot;https://wilcovanes.ch/articles/collaboration-techniques-in-test-and-development/#ensemble-programming&quot;&gt;Ensemble programming&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Swarming&lt;a name=&quot;swarming&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Temporarily have the whole team working on one urgent item&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Compared to ensemble programming&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Also involves the whole team&lt;/li&gt;
&lt;li&gt;Temporary&lt;/li&gt;
&lt;li&gt;Less structured&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Variations&lt;a name=&quot;variations&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;For pairing on large tickets&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You can go async, for example for bigger bug fixes or compatibility testing, and return to working together.&lt;/li&gt;
&lt;li&gt;You can loosely pair, keeping audio open (or sitting at adjacent desks) until converging again.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;For pair programming&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The Navigator telling the Driver exactly what to type &lt;strong&gt;versus&lt;/strong&gt; the Driver explaining what they’ll do next&lt;/li&gt;
&lt;li&gt;Regularly switching roles &lt;strong&gt;versus&lt;/strong&gt; not switching roles&lt;/li&gt;
&lt;li&gt;“For an idea to go from your head into the computer, it MUST go through someone else&#39;s hands” &lt;strong&gt;versus&lt;/strong&gt; 2 keyboards, 1 problem&lt;/li&gt;
&lt;li&gt;Fixed schedule of breaks and rotation &lt;strong&gt;versus&lt;/strong&gt; freestyle&lt;/li&gt;
&lt;li&gt;One person writes tests, the other makes them pass (using the Ping Pong approach or with fixed roles)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;For ensemble programming regularly&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You can do it sometimes, such as one day per Sprint or for specific tickets that involve the whole team.&lt;/li&gt;
&lt;li&gt;You can do it always, with individuals stepping out regularly for relief from so much interaction.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Getting time to add unit and integration tests retroactively</title>
    <link href="https://wilcovanes.ch/articles/getting-time-to-add-unit-and-integration-tests-retroactively/"/>
    <updated>2023-06-23T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/getting-time-to-add-unit-and-integration-tests-retroactively/</id>
    <content type="html">&lt;p&gt;In &lt;a href=&quot;https://wilcovanes.ch/articles/how-to-handle-technical-debt/&quot;&gt;How to handle technical debt&lt;/a&gt; I explained how teams that have a handle on repaying technical debt actually make it work. Now I want to go into more detail specifically on getting the following done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Move time-consuming or frustrating &lt;strong&gt;manual regression checks&lt;/strong&gt; to &lt;strong&gt;unit and integration tests&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Move time-consuming or unreliable &lt;strong&gt;end-to-end UI-level checks&lt;/strong&gt; to &lt;strong&gt;unit and integration tests&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Context&lt;/h2&gt;
&lt;p&gt;A cross-functional product team that has been active for some time and spends a non-trivial amount of time in their development cycles running UI-level checks and/or performing manual regression checks. The team wants to be more efficient and replace these with earlier, faster feedback. However, &lt;strong&gt;they are not able to just go and do it&lt;/strong&gt;. Some teams are and don&#39;t need any of this help. Others do, such as teams that spend 100% of their capacity on feature work.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Here&#39;s what you do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Identify only the &lt;em&gt;worst&lt;/em&gt; check you want to move to a lower level of testing.&lt;/li&gt;
&lt;li&gt;Describe the technical task in your issue/project management system.&lt;/li&gt;
&lt;li&gt;Justify it by comparing time saving to the time investment.&lt;/li&gt;
&lt;li&gt;Chase the task&#39;s planning/inclusion into the team&#39;s development cycle.&lt;/li&gt;
&lt;li&gt;Rejoice, because now you do have the time to move tests to lower levels.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Details&lt;/h2&gt;
&lt;h3&gt;Identify it&lt;/h3&gt;
&lt;p&gt;Here&#39;s what a team might be tempted to do: document &amp;amp; evaluate all their current manual regression checks and/or end-to-end UI-level checks for how long they take to execute versus the value they bring, discuss for each check whether they can be adequately covered by unit and/or integration tests, create a plan that specifies when and how each test should be moved to what level, create a workgroup to start handling all that work, and plan regular status meetings. If you have all the time in the world and you enjoy bureaucracy, do this.&lt;/p&gt;
&lt;p&gt;Or, do the worst check first, enjoy a slight improvement to everyone&#39;s lives, then do the next worst check, repeat.&lt;/p&gt;
&lt;p&gt;You also don&#39;t have to fully analyse the test suites to find out what that worst check is. For manual regression checks, the person doing the checks will have a keen feeling for which one is the most time-consuming or the most frustrating to set up the test conditions for compared to how likely it is to lead to meaningful issues. For end-to-end UI-level checks, simply check the pipelines and look for the test scenario that takes the longest.&lt;/p&gt;
&lt;p&gt;You &lt;strong&gt;do&lt;/strong&gt; want to give a little thought to whether the tests you&#39;re moving to lower levels of testing are actually covering meaningful risks, whether there are more meaningful risks that are currently uncovered, and whether the tests you&#39;re moving are in fact &lt;em&gt;already&lt;/em&gt; covered adequately at lower levels. That should inform which ones you actually move on to the next step for.&lt;/p&gt;
&lt;h3&gt;Describe it&lt;/h3&gt;
&lt;p&gt;Create a task. Name it something like &amp;quot;Create unit/integration tests for {functionality}&amp;quot;.&lt;/p&gt;
&lt;h3&gt;Justify it&lt;/h3&gt;
&lt;p&gt;In the task you can add the justification. This will be useful when promoting it with the team, such as in a backlog grooming / refinement session. So what is the justification?&lt;/p&gt;
&lt;p&gt;The most persuasive argument is going to be time saving.&lt;/p&gt;
&lt;p&gt;Time saving is obviously attractive to a business. Time is money. It&#39;s also easy to wrap your head around and a value that can be expressed in numbers. In your company you might even &lt;em&gt;need&lt;/em&gt; to express it in numbers. So that would mean estimating - over some period of time such as a year - the amount of time writing and maintaining the new unit and/or integration test(s) and subtracting it from the amount of time you currently spend running and maintaining the checks that will be replaced. Don&#39;t spend a lot of time on this or you&#39;re just reducing the time saved. All you need is a rough estimate.&lt;/p&gt;
&lt;p&gt;Other major benefits of moving tests to lower levels of testing, such as earlier feedback and more reliable test results, are less easily estimated and less persuasive when you produce the estimates for a single task. If anything, you&#39;re going to take away the persuasive force of your justification, so only mention these benefits by name or leave them unsaid until you need them.&lt;/p&gt;
&lt;h3&gt;Chase it&lt;/h3&gt;
&lt;p&gt;Once the task has been agreed to be valuable enough, you just have to ensure you or someone else in your team grab it from the backlog, or formally plan it into a sprint if that&#39;s how you work.&lt;/p&gt;
&lt;h3&gt;Rejoice&lt;/h3&gt;
&lt;p&gt;The team didn&#39;t have time! The team wanted the time! Now the team has the time!&lt;/p&gt;
&lt;h2&gt;The buts&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;But these new unit or integration tests don&#39;t test everything a tester picks up on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We&#39;re only taking explicitly defined test scenarios and automating those. Those already didn&#39;t fall under the other things a tester picks up on. Those more interesting risks are the ones the team can continue to focus their brainpower on.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But the time saving can never weigh up against the value of feature work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&#39;s true &lt;strong&gt;if&lt;/strong&gt; the team works on validated customer requests and on fast market tests of new ideas. There are 2 ways out: 1) You could be wrong and the task is more valuable, because the rest of the work isn&#39;t, 2) Promote the long term instead, as not doing &lt;strong&gt;one&lt;/strong&gt; of these tasks might not make a great difference, but &lt;strong&gt;never&lt;/strong&gt; doing them will keep bottlenecks in place and cost the company real opportunities due to a slower time to market. If the effort as a whole is valuable enough, we must do the parts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But I don&#39;t know what unit or integration test should be written instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&#39;s enough to know what test scenario is currently covered by a UI-level regression check and/or manual regression check that deserves replacing. Describe that scenario. Then the team can discuss how to adequately cover it at lower levels, or you get ahead of it by sitting with a developer and thinking it out together. You could even rubber duck it with an LLM.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But I care more about a UI-level check that&#39;s unreliable than one that&#39;s slow.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&#39;d hope you&#39;d be in a position where immediate problems like a flaky test that makes the pipeline fail when it shouldn&#39;t can be tackled immediately by the team. If not, then sure, create a task for the check that would have most return on investment for being moved to lower levels rather than necessarily the slowest one.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Questions to ask during Refinement sessions</title>
    <link href="https://wilcovanes.ch/articles/questions-to-ask-during-refinement-sessions/"/>
    <updated>2023-05-06T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/questions-to-ask-during-refinement-sessions/</id>
    <content type="html">&lt;p&gt;Let&#39;s assume a common context of Story tickets in Jira being discussed in a Refinement session.&lt;/p&gt;
&lt;p&gt;What kind of input can you, as a member of the team, give to make this ceremony worth the time?&lt;/p&gt;
&lt;p&gt;If you can&#39;t surmise the answers to these questions from the ticket, ask them aloud:&lt;/p&gt;
&lt;h2&gt;Justification&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;What value will users get from this?&lt;/li&gt;
&lt;li&gt;Is it worth the opportunity cost?&lt;/li&gt;
&lt;li&gt;Is this an idea we want to prototype or a request from paying customers?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Measuring success&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;After release, how will we know we achieved the expected value?&lt;/li&gt;
&lt;li&gt;Will we beta test this change through a dark launch?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Specifications&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;What does [unclear acceptance criterion] mean?&lt;/li&gt;
&lt;li&gt;Could we rewrite [unclear acceptance criterion] as [better wording]?&lt;/li&gt;
&lt;li&gt;Could we clarify [free text specifications] through a scenario outline table, through Gherkin DSL, and/or through representative examples (Specification by Example)?&lt;/li&gt;
&lt;li&gt;Have we covered all relevant happy path scenarios?&lt;/li&gt;
&lt;li&gt;Have we covered all relevant unhappy path scenarios?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Readiness&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;If we tried to start this today:
&lt;ul&gt;
&lt;li&gt;would there be any dependencies or blockers?&lt;/li&gt;
&lt;li&gt;would we need to complete any investigation (spike) first?&lt;/li&gt;
&lt;li&gt;do we need any alignment with:
&lt;ul&gt;
&lt;li&gt;the support team who will write help centre articles for this?&lt;/li&gt;
&lt;li&gt;the marketing team who will update marketing materials for this?&lt;/li&gt;
&lt;li&gt;the translation team who will provide translations for this?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;could we benefit from whiteboaring and deciding a technical solution?&lt;/li&gt;
&lt;li&gt;would we have all supporting documentation (e.g. lofi UI/UX designs in combination with a UI component library) available?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Testability&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;To test this, do we need:
&lt;ul&gt;
&lt;li&gt;a special test environment and/or mocking solution for a third-party integration?&lt;/li&gt;
&lt;li&gt;production-like test data which we do not currently have?&lt;/li&gt;
&lt;li&gt;instrumentation which we do not currently have?&lt;/li&gt;
&lt;li&gt;to provide the ability to change the application&#39;s state?&lt;/li&gt;
&lt;li&gt;to expose one or more custom data attributes?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Which scenarios are we going to cover at the level of an isolated unit test, which at the level of a sociable unit, which at the level of an integration test, and which through a UI-level test?&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Why and how to apply pair testing</title>
    <link href="https://wilcovanes.ch/articles/why-and-how-to-apply-pair-testing/"/>
    <updated>2023-04-25T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/why-and-how-to-apply-pair-testing/</id>
    <content type="html">&lt;h2&gt;What&lt;/h2&gt;
&lt;p&gt;Two people (such as a developer and a test specialist) working together (at one desk or in a call) to perform exploratory testing (not just verification of requirements) on a code change together before the developer picks up a new ticket.&lt;/p&gt;
&lt;h2&gt;Why&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Removes waste in the test-fix-retest cycle&lt;/li&gt;
&lt;li&gt;Uncovers implicit requirements more quickly&lt;/li&gt;
&lt;li&gt;Reduces context switching&lt;/li&gt;
&lt;li&gt;Creates developer-tester understanding and rapport&lt;/li&gt;
&lt;li&gt;Spreads test techniques &amp;amp; thinking&lt;/li&gt;
&lt;li&gt;Knowledge sharing on the development process&lt;/li&gt;
&lt;li&gt;Knowledge sharing on the product&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How&lt;/h2&gt;
&lt;p&gt;Once a code change is ready to be reviewed (and it even pays to do this before code reviews or during coding), the developer should invite a test or product specialist to join them in a call or at their desk. Then the developer (the Driver) demos the change and the test specialist (the Navigator) offers test scenarios and asks questions. The developer executes the tests. Together they fix and retest, with the developer typing and the test specialist guiding.&lt;/p&gt;
&lt;p&gt;The Driver and Navigator roles can be switched regularly on a timer. I don&#39;t say &lt;strong&gt;should&lt;/strong&gt;, because when teams deliver small chunks of work that can be reviewed in quick sessions, you may not need role switches. You should, as ever, fit your approach to what works for your context.&lt;/p&gt;
&lt;h3&gt;Traditional pairing&lt;/h3&gt;
&lt;p&gt;The Driver is on the keyboard.&lt;br&gt;
&lt;strong&gt;The Driver generates test ideas.&lt;/strong&gt;&lt;br&gt;
The Navigator reviews and asks questions.&lt;/p&gt;
&lt;h3&gt;Strong-style pairing&lt;/h3&gt;
&lt;p&gt;The Driver is on the keyboard.&lt;br&gt;
&lt;strong&gt;The Navigator generates test ideas.&lt;/strong&gt;&lt;br&gt;
The Navigator reviews and asks questions.&lt;/p&gt;
&lt;p&gt;When in doubt, try strong-style pairing. In most cases that will give you more alert and productive sessions.&lt;/p&gt;
&lt;h2&gt;Practical example&lt;/h2&gt;
&lt;p&gt;Developer on Slack: &lt;em&gt;&amp;quot;hey man :) want to look at my changes?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tester on Slack: &lt;em&gt;(opens a call with the developer)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Developer on Zoom: &lt;em&gt;&amp;quot;As you can see here, if you select service X and press Connect you go into a connection flow through Y.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tester on Zoom: &lt;em&gt;&amp;quot;What if I already connected X through Y?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Developer on Zoom: &lt;em&gt;&amp;quot;Then the Connect button should be disabled.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tester on Zoom: &lt;em&gt;&amp;quot;Should it not change to a Disconnect button?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Developer on Zoom: &lt;em&gt;&amp;quot;When no service is selected yet, we don&#39;t know which service to disconnect.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tester on Zoom: &lt;em&gt;&amp;quot;But when we select a service, could we not show a Disconnect button for that service?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Developer on Zoom: &lt;em&gt;&amp;quot;That&#39;s possible.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This potentially leads to a brief calling in of a Product Owner or UX specialist, and to further tests and learning. Just the short fictional exchange above might already have been several rounds of playing Jira ticket tennis, perhaps for several days, if it wasn&#39;t done through pair testing.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Speech recognition could not start</title>
    <link href="https://wilcovanes.ch/articles/speech-recognition-could-not-start/"/>
    <updated>2023-02-04T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/speech-recognition-could-not-start/</id>
    <content type="html">&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;When you activate speech recognition in Windows, you get an error like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Speech Recognition could not start. Please change the recognizer language in the speech recognition control panel under advanced options&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Cause&lt;/h2&gt;
&lt;p&gt;Having the right language pack installed and selected does not mean you also have its speech features.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;For your display language, install the speech pack:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt; (not &lt;strong&gt;Control Panel&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Language&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select the language you&#39;re using as your Windows display language&lt;/li&gt;
&lt;li&gt;Scroll to the &lt;strong&gt;Speech&lt;/strong&gt; section&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Download&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once downloaded and installed, activate speech recognition again. You may have to completely shut it down and restart it a second time before it works.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Peer dependency conflict from the rollup-plugin-terser package</title>
    <link href="https://wilcovanes.ch/articles/peer-dependency-conflict-from-the-rollup-plugin-terser-package/"/>
    <updated>2023-02-04T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/peer-dependency-conflict-from-the-rollup-plugin-terser-package/</id>
    <content type="html">&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;You get an error like this during your build step&#39;s &lt;code&gt;npm install&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; code ERESOLVE
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; ERESOLVE could not resolve
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; While resolving: rollup-plugin-terser@7.0.2
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; Found: rollup@3.2.2
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; node_modules/rollup
Failed during stage &lt;span class=&quot;token string&quot;&gt;&#39;building site&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; Build script returned non-zero &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; code: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;https://ntl.fyi/exit-code-1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;   dev rollup@&lt;span class=&quot;token string&quot;&gt;&quot;3.2.2&quot;&lt;/span&gt; from the root project
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;   peerOptional rollup@&lt;span class=&quot;token string&quot;&gt;&quot;^2.78.0||^3.0.0&quot;&lt;/span&gt; from @rollup/plugin-node-resolve@15.0.0
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;   node_modules/@rollup/plugin-node-resolve
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;     dev @rollup/plugin-node-resolve@&lt;span class=&quot;token string&quot;&gt;&quot;15.0.0&quot;&lt;/span&gt; from the root project
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; Could not resolve dependency:
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; peer rollup@&lt;span class=&quot;token string&quot;&gt;&quot;^2.0.0&quot;&lt;/span&gt; from rollup-plugin-terser@7.0.2
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; node_modules/rollup-plugin-terser
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;   dev rollup-plugin-terser@&lt;span class=&quot;token string&quot;&gt;&quot;7.0.2&quot;&lt;/span&gt; from the root project
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; Conflicting peer dependency: rollup@2.79.1
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; node_modules/rollup
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;   peer rollup@&lt;span class=&quot;token string&quot;&gt;&quot;^2.0.0&quot;&lt;/span&gt; from rollup-plugin-terser@7.0.2
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;   node_modules/rollup-plugin-terser
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;     dev rollup-plugin-terser@&lt;span class=&quot;token string&quot;&gt;&quot;7.0.2&quot;&lt;/span&gt; from the root project
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; Fix the upstream dependency conflict, or retry
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; this &lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; with &lt;span class=&quot;token parameter variable&quot;&gt;--force&lt;/span&gt; or --legacy-peer-deps
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; to accept an incorrect &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;and potentially broken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; dependency resolution.
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; For a full report see:
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; /opt/buildhome/.npm/_logs/2023-02-04T13_38_32_938Z-eresolve-report.txt
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; A complete log of this run can be found in:
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ERR&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;     /opt/buildhome/.npm/_logs/2023-02-04T13_38_32_938Z-debug-0.log&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Cause&lt;/h2&gt;
&lt;p&gt;In your project you&#39;re requiring the latest version of &lt;code&gt;rollup&lt;/code&gt; (3.2.2 at the time of writing), whereas one of your other dependencies (the library &lt;code&gt;rollup-plugin-terser&lt;/code&gt;) requires a &lt;code&gt;rollup&lt;/code&gt; major version of 2.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Replace the no longer maintained &lt;code&gt;rollup-plugin-terser&lt;/code&gt; with &lt;code&gt;@rollup/plugin-terser&lt;/code&gt;. Specifically, create a new branch with the following changes and merge the changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;npm uninstall rollup-plugin-terser&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm i @rollup/plugin-terser --save-dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;In your rollup config, replace &lt;code&gt;import { terser } from &amp;quot;rollup-plugin-terser&amp;quot;;&lt;/code&gt; with &lt;code&gt;import terser from &amp;quot;@rollup/plugin-terser&amp;quot;;&lt;/code&gt; (removing the curly brackets matters, as the new package uses a default export).&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>What are the differences between load, stress, spike, and soak testing?</title>
    <link href="https://wilcovanes.ch/articles/what-are-the-differences-between-load-stress-spike-and-soak-testing/"/>
    <updated>2023-01-23T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/what-are-the-differences-between-load-stress-spike-and-soak-testing/</id>
    <content type="html">&lt;h2&gt;What do you want to learn about the application?&lt;/h2&gt;
&lt;p&gt;Each of these questions has a performance test type associated with it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is the maximum load the application can handle? ➡ stress test&lt;/li&gt;
&lt;li&gt;What are the performance bottlenecks? ➡ load test&lt;/li&gt;
&lt;li&gt;How does the application respond to a sudden peak? ➡ spike test&lt;/li&gt;
&lt;li&gt;Does performance degrade under continuous usage? ➡ soak test&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These terms are not used consistently from one company to the next. You might have someone do a shorter duration soak test and refer to it as stress testing or load testing interchangeably. You&#39;re more than welcome to treat the below explanations as the only correct way to refer to these types of performance testing. 😊&lt;/p&gt;
&lt;h2&gt;Test types&lt;/h2&gt;
&lt;p&gt;All these tests have in common that you&#39;ll need to identify common user flows and simulate them through test scripts.&lt;/p&gt;
&lt;h3&gt;Stress test&lt;/h3&gt;
&lt;p&gt;In a stress test you don&#39;t just burden the application with a high load, you drive the application to failure.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decide how you will measure when the application has reached its limit, for example: &amp;gt;= 5% of HTTP requests fail&lt;/li&gt;
&lt;li&gt;Ramp up the number of concurrent users until you reach that failure threshold&lt;/li&gt;
&lt;li&gt;Note the number of concurrent users at that point&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now you know what load the application can handle before things start going terribly wrong.&lt;/p&gt;
&lt;h3&gt;Load test&lt;/h3&gt;
&lt;p&gt;Example approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Calculate (see the formula further down the page) the number of concurrent users / virtual users&lt;/li&gt;
&lt;li&gt;Set ramping to gently go up to concurrent normal usage and gently down again after 1 hour&lt;/li&gt;
&lt;li&gt;Monitor throughput during the test to identify any degradation&lt;/li&gt;
&lt;li&gt;Head to observability tools to determine the cause of bottlenecks (repeated transactions? slow SQL queries?)&lt;/li&gt;
&lt;li&gt;Run the test again, but this time configure it for concurrent peak usage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now you&#39;re aware of specific bottlenecks you can resolve.&lt;/p&gt;
&lt;h3&gt;Spike test&lt;/h3&gt;
&lt;p&gt;Example approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Base the spike test configuration on the load test scenario configured for normal usage&lt;/li&gt;
&lt;li&gt;Edit it so that after 10 minutes of testing it will go to peak usage with a 5 minute ramp-up, duration, and ramp-down&lt;/li&gt;
&lt;li&gt;Edit it so that after 30 minutes, it will go to peak usage with a 2 minute ramp-up, 5 minute duration, and 2 minute ramp-down&lt;/li&gt;
&lt;li&gt;Edit it so that after 45 minutes, it will go to peak usage with a 1 minute ramp-up, 5 minute duration, and 1 minute ramp-down&lt;/li&gt;
&lt;li&gt;Start the test and monitor error rates, response times, and throughput during those spikes&lt;/li&gt;
&lt;li&gt;Head to observability tools to determine the cause of hiccups&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now you know how well the application handles sudden spikes in activity.&lt;/p&gt;
&lt;h3&gt;Soak test&lt;/h3&gt;
&lt;p&gt;Example approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Base the soak test configuration on the load test scenario configured for normal usage&lt;/li&gt;
&lt;li&gt;Extend the test duration to 12 hours, and add in a realistic day/night gentle ramping up and down&lt;/li&gt;
&lt;li&gt;When the test has concluded, determine whether any errors or bottlenecks appear later in the test which didn’t occur in the other tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now you know whether there are any performance issues which you&#39;d otherwise only discover once a release has been in production for a day.&lt;/p&gt;
&lt;h3&gt;Concurrent usage calculation&lt;/h3&gt;
&lt;p&gt;People often overestimate the number of concurrent users they need, because they don&#39;t take into account that if you have thousands of users per hour, they don&#39;t all spend that full hour actively operating the application.&lt;/p&gt;
&lt;p&gt;This is what you need from your user analytics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For your most representative usage level and peak usage level, what were the number of unique sessions per hour?&lt;/li&gt;
&lt;li&gt;Potentially add another level for an optimistic upcoming peak usage level, such as twice last year&#39;s peak usage.&lt;/li&gt;
&lt;li&gt;The average session duration for the same time period you got the unique sessions per hour from.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The formula:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Concurrent users = (Unique Sessions Per Hour * Average Session Duration in seconds) / 3600&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I was taught that multiplication has priority over division, but I&#39;ve added parenthesis for clarity.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;You don&#39;t necessarily have to use all of these test types. Simply said they can teach you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The number of concurrent users at which you expect HTTP requests to start failing for 5% or more of your users&lt;/li&gt;
&lt;li&gt;Hardware and software bottlenecks which keep you from performing better at normal and peak usage&lt;/li&gt;
&lt;li&gt;How well you’re prepared for sudden peaks in usage (e.g. Black Friday)&lt;/li&gt;
&lt;li&gt;What kind of issues you could expect to pop up when the excitement of going live dies down after a few hours&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Have a production-like environment that includes all the expected infrastructure
&lt;ul&gt;
&lt;li&gt;Should take into account the potential cost of any quotas you might exceed&lt;/li&gt;
&lt;li&gt;Your infrastructure &amp;amp; InfoSec teams should be warned in advance of the tests (especially stress tests)&lt;/li&gt;
&lt;li&gt;For stress testing, you (or the responsible team) should be ready to restart a failing environment&lt;/li&gt;
&lt;li&gt;The tests shouldn’t be run when anyone else is using the environment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Have a tool which allows you to configure load scenarios AND drive virtual users (e.g. k6)&lt;/li&gt;
&lt;li&gt;Design and configure the load scenarios well ahead of time and (safely) test the configuration&lt;/li&gt;
&lt;li&gt;Have the load scenarios reflect user flow(s) through the application which you expect to be the most common
&lt;ul&gt;
&lt;li&gt;This should include (semi-random) waiting times to reflect real user behaviour, e.g. &lt;code&gt;sleep(math.random(2,4))&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;You should be realistic (real flows, realistic behaviour), but not obsessive (all flows, detailed user personas)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Have the ability to monitor (and review logs of) hardware and software metrics
&lt;ul&gt;
&lt;li&gt;The performance testing tool used will typically already report the response times, throughput, etc.&lt;/li&gt;
&lt;li&gt;However, you will also want to know what happens to the server (CPU usage, RAM usage, disk space)&lt;/li&gt;
&lt;li&gt;You will also want to understand response times at a deeper level (what’s slow: db queries? frontend rendering?)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Unable to start ssh-agent, error 1058</title>
    <link href="https://wilcovanes.ch/articles/unable-to-start-ssh-agent-error-1058/"/>
    <updated>2023-01-16T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/unable-to-start-ssh-agent-error-1058/</id>
    <content type="html">&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;When attempting to run &lt;code&gt;eval $(ssh-agent)&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;unable to start ssh-agent service, error :1058&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Cause&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;ssh-agent&lt;/code&gt; service is disabled.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Start PowerShell as an Administrator.&lt;/p&gt;
&lt;p&gt;Then run &lt;code&gt;Set-Service ssh-agent -StartupType Manual&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now try &lt;code&gt;eval $(ssh-agent)&lt;/code&gt; again.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>What are mutation testing, approval testing, and property-based testing?</title>
    <link href="https://wilcovanes.ch/articles/what-are-mutation-testing-approval-testing-and-property-based-testing/"/>
    <updated>2022-10-07T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/what-are-mutation-testing-approval-testing-and-property-based-testing/</id>
    <content type="html">&lt;p&gt;To-the-point definitions of some less-talked-about types of testing.&lt;/p&gt;
&lt;h2&gt;Mutation testing&lt;/h2&gt;
&lt;p&gt;Automatically generate &amp;quot;mutations&amp;quot; of an application under test and run your test suite against it in order to evaluate how capable your test suite is of identifying resulting issues. It serves to test the quality of the test suite (specifically its fault detection and code coverage), not the quality of the application under test.&lt;/p&gt;
&lt;p&gt;Notable tool: &lt;a href=&quot;https://stryker-mutator.io/&quot;&gt;Stryker&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Approval testing&lt;/h2&gt;
&lt;p&gt;Instead of asserting against expected behaviour, capture any differences between the current output and the previous output and if there are any differences have them evaluated by a human reviewer. An example would be Percy visual regression checks. Think of approval testing as blacklisting changes and assertion-based testing as whitelisting changes.&lt;/p&gt;
&lt;p&gt;Notable tool: &lt;a href=&quot;https://github.com/approvals&quot;&gt;ApprovalTests&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Property-based testing&lt;/h2&gt;
&lt;p&gt;Instead of writing a specific test case asserting that the application under test has a certain property for a certain input, assert that the property remains valid for a wide variety of inputs. An example of a property in this context could be that the result of a particular calculation should never be a negative number. The test would check that this is true for a wide variety of automatically generated inputs to the calculation.&lt;/p&gt;
&lt;p&gt;Notable tool: &lt;a href=&quot;https://hypothesis.works/&quot;&gt;Hypothesis&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Mistakes to avoid when scaling up an engineering discipline</title>
    <link href="https://wilcovanes.ch/articles/mistakes-to-avoid-when-scaling-up-an-engineering-discipline/"/>
    <updated>2022-09-30T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/mistakes-to-avoid-when-scaling-up-an-engineering-discipline/</id>
    <content type="html">&lt;p&gt;When you&#39;re a successful tech start-up experiencing rapid growth, it&#39;s tempting to think of scalability as hiring more people, renting more office space, and maturing your HR processes. Where scale-ups often go wrong, understandably, is to not make any changes to the things that made them successful as a start-up. That can cause some problems, sometimes fatal problems.&lt;/p&gt;
&lt;h2&gt;Not changing leadership roles&lt;/h2&gt;
&lt;p&gt;The CTO fails to switch from technical excellence to technical leadership and the CEO or COO fails to switch from being a product &amp;amp; sales lead to providing a strong and market-savvy direction and ensuring they take along the right people for that journey.&lt;/p&gt;
&lt;p&gt;What a company needs from its senior management changes dramatically as it scales up and so do the skills that match those new requirements.&lt;/p&gt;
&lt;p&gt;As a founder team, that either means learning skills you don&#39;t yet have or putting a new C-suite in place. Both very difficult things that make it tempting to double down on what worked in the startup phase and stumble along hoping your competitors don&#39;t do any better.&lt;/p&gt;
&lt;h2&gt;Hiring too carelessly&lt;/h2&gt;
&lt;p&gt;Hiring people primarily on technical skill is not much of an issue until you start working with cross-functional product teams. Then the way in which people communicate, the ways they do (or do not) collaborate, and the way they adjust their approach to the context of a fast-moving scaleup, all become crucial to whether the teams are able to swiftly deliver high value work.&lt;/p&gt;
&lt;p&gt;That means the hiring process will need to include people who are especially capable of asking and evaluating questions which will identify the right fit for a highly collaborative working environment. Asking &amp;quot;can you collaborate well?&amp;quot; won&#39;t do it.&lt;/p&gt;
&lt;p&gt;It also means not hiring too many permanent employees at once, even if you think you need a lot of capacity fast, as a large influx of new employees can have a disproportionately detrimental effect on productivity and the organisation&#39;s culture.&lt;/p&gt;
&lt;h2&gt;Neglecting infrastructure &amp;amp; DevEx&lt;/h2&gt;
&lt;p&gt;When you move fast and add more and more developers, small pains in the local development environment, CI/CD pipeline, and reliability are going to become huge headaches that also become increasingly more difficult to resolve.&lt;/p&gt;
&lt;p&gt;More employees come in, they all suffer the same problems, they all try to be helpful and create workarounds or suggest solutions, and both the problems and the discussions around solutions are unending and involve more and more people. Structural solutions barely have a chance to gain momentum as they have to compete with delivering value to the customer.&lt;/p&gt;
&lt;p&gt;You need technical &amp;amp; product leadership to agree and make a firm choice that internal quality also matters and guarantee a continuous margin to address it. Insufficient expertise should be hired for. Then, these small problems can be addressed before they turn into large ones.&lt;/p&gt;
&lt;h2&gt;Blindly applying a scaled Agile framework&lt;/h2&gt;
&lt;p&gt;Using the rulebook of a scaled Agile framework like SAFe can be disastrous in a dynamic, fast-changing environment, especially if the practitioners are convinced a priori that any possible friction or failures would come from not following the rules closely enough.&lt;/p&gt;
&lt;p&gt;Sure, if you have multiple cross-functional product teams, dependencies are going to need to be negotiated. Having everyone sit in a room for a two day planning meeting is one way to do that. It demonstrates how people can be made to do wicked, unnatural things if there&#39;s enough peer pressure. There are other ways, however. Reducing dependencies is one. A simple Scrum of Scrums is another.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How to approach load and stress testing</title>
    <link href="https://wilcovanes.ch/articles/how-to-approach-load-and-stress-testing/"/>
    <updated>2022-07-16T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-to-approach-load-and-stress-testing/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;Update&lt;/em&gt;: In 2023, I&#39;ve written &lt;a href=&quot;https://wilcovanes.ch/articles/what-are-the-differences-between-load-stress-spike-and-soak-testing/&quot;&gt;a more up-to-date article&lt;/a&gt; which complements this one.&lt;/p&gt;
&lt;h2&gt;Definitions&lt;/h2&gt;
&lt;h3&gt;Load test&lt;/h3&gt;
&lt;p&gt;Examining behaviour of the system under sustained typical or high load.&lt;/p&gt;
&lt;h3&gt;Stress test&lt;/h3&gt;
&lt;p&gt;Determining the point at which the application (or a % of requests) fails.&lt;/p&gt;
&lt;h2&gt;Deciding the environment&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Never use:&lt;/strong&gt; any environment whose owner is not expecting the extra load.&lt;br&gt;
&lt;strong&gt;May use:&lt;/strong&gt; a staging environment matching the production environment sufficiently for your purposes, which may include auto-balancing and auto-scaling configurations.&lt;br&gt;
&lt;strong&gt;Ideally use:&lt;/strong&gt; the actual production environment at a time of consistent low usage.&lt;/p&gt;
&lt;h2&gt;Deciding concurrent usage&lt;/h2&gt;
&lt;h3&gt;Load testing&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sessions Per Hour&lt;/strong&gt; x &lt;strong&gt;Average Visit Duration&lt;/strong&gt; (seconds) / &lt;strong&gt;3600&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Make this calculation for normal load (your most typical business day) and peak load (highest traffic in the last year, but add a margin on top depending on what you&#39;re expecting to gain from any extra spend on marketing and paid search).&lt;/p&gt;
&lt;h3&gt;Stress testing&lt;/h3&gt;
&lt;p&gt;You don&#39;t need to know concurrent usage. You will find out the maximum concurrent usage your application and its infrastructure can handle.&lt;/p&gt;
&lt;h2&gt;Deciding what to test&lt;/h2&gt;
&lt;p&gt;If you already have baseline results from a previous test which you want to compare against for new changes to the application or infrastructure, use the exact same test scenarios.&lt;/p&gt;
&lt;p&gt;If not, either analyse the user flows of actual traffic for a non-typical event (for example previous year&#39;s Black Friday traffic for preparing for this year&#39;s Black Friday) or analyse user flows for the most crucial journeys through the application.&lt;/p&gt;
&lt;p&gt;In the test scenarios (for example defined in JS for k6) define page views and events for the user flows with realistic wait times in between. You could make these random within a range, to reflect the variance of real users.&lt;/p&gt;
&lt;p&gt;You&#39;ll have to make a judgment call for how closely you&#39;ll make the user flows and data reflect the variation and user quirks of your actual traffic, by considering how much that extra realism is worth in comparison to the time spent defining it and testing it does what it should.&lt;/p&gt;
&lt;h2&gt;Deciding how to test&lt;/h2&gt;
&lt;h3&gt;Load testing&lt;/h3&gt;
&lt;p&gt;In the tool you&#39;re using to drive traffic, add up and down ramping which the application can easily handle and spawn virtual users to the concurrent usage results you got in &amp;quot;Deciding concurrent usage&amp;quot; and have those virtual users execute the journeys you defined in &amp;quot;Deciding what to test&amp;quot;. Keep this running for an hour (or longer if you&#39;d like to make this a soak or endurance test).&lt;/p&gt;
&lt;p&gt;Review relevant metrics which were gathered during the test and look for bottlenecks. What resources were under most stress? Disk, CPU, RAM, the network?&lt;/p&gt;
&lt;h3&gt;Stress testing&lt;/h3&gt;
&lt;p&gt;In the tool you&#39;re using to drive traffic, configure a ramping up which will not end. Decide what you will consider the breaking point of your application. For example, 10% of HTTP requests fail with a 500 error. Then either automatically (if the tool allows it) or manually stop the stress test when you reach that point.&lt;/p&gt;
&lt;p&gt;Mark the number of concurrent virtual users at this point, and review the most burdened resources (for example, CPU usage) and significant metrics (for example, throughput).&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>When chasing efficiency is a false economy</title>
    <link href="https://wilcovanes.ch/articles/when-chasing-efficiency-is-a-false-economy/"/>
    <updated>2022-07-13T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/when-chasing-efficiency-is-a-false-economy/</id>
    <content type="html">&lt;p&gt;In software engineering we&#39;re often — and rightly so — looking for ways to be more efficient, to be faster than competitors, to reduce waste. Some of those ways work out really well and some turn out to be false economies where the time or other costs we initially save end up costing us more in the end.&lt;/p&gt;
&lt;p&gt;Here are seven examples, with charitable titles to show why they might seem like a good idea.&lt;/p&gt;
&lt;h2&gt;Anticipating needs&lt;/h2&gt;
&lt;p&gt;When you&#39;re working on a feature to integrate a payment provider, it&#39;s tempting to think &amp;quot;we should build this feature to accommodate any future payment providers we may add&amp;quot;.&lt;/p&gt;
&lt;p&gt;This &lt;strong&gt;could&lt;/strong&gt; be smart, if you suspect that it&#39;s going to take six weeks to develop and you know the business needs multiple other payment providers to be added six weeks from now.&lt;/p&gt;
&lt;p&gt;If the need is not immediate, though, resist the temptation. Keep it simple. That will get you feedback quicker and it will prevent changing circumstances from making all that work obsolete.&lt;/p&gt;
&lt;p&gt;See also the XP principles &lt;a href=&quot;https://ronjeffries.com/xprog/articles/practices/pracsimplest/#:~:text=The%20most%20important%20rule%20in,ability%20to%20make%20rapid%20progress.&quot;&gt;Do The Simplest Thing That Can Possibly Work&lt;/a&gt; and &lt;a href=&quot;https://martinfowler.com/bliki/Yagni.html&quot;&gt;You Aren&#39;t Going To Need It&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Parallel work streams&lt;/h2&gt;
&lt;p&gt;If you have a team of five developers, wouldn&#39;t you get the most utilisation out of them by having each developer plugging away at their own piece of work?&lt;/p&gt;
&lt;p&gt;Not necessarily. Pair programming, pair testing, ensemble programming, and ensemble testing have immense benefits for immediacy of feedback, the quality of delivered work, knowledge sharing, and team cohesion, against a cost of having multiple members of a development team working on the same thing.&lt;/p&gt;
&lt;p&gt;To the uninitiated that cost is all that&#39;s visible. &amp;quot;How is it more efficient to have two developers deliver one thing instead of delivering two things?&amp;quot; Firstly, the cycle time is faster when collaborating. Think one thing delivered instead of two almost delivered. Secondly, the efficiency of team members working in isolation is illusory. With isolated work you miss out on &lt;a href=&quot;https://wiki.c2.com/?PairProgrammingBenefits&quot;&gt;the benefits of pair programming&lt;/a&gt;, &lt;a href=&quot;https://smartbear.com/blog/pair-testing/&quot;&gt;pair testing&lt;/a&gt; or &lt;a href=&quot;https://ensembleprogramming.xyz/Download/EnsembleProgrammingGuidebook.pdf&quot;&gt;ensemble (or mob) programming or testing&lt;/a&gt; (PDF).&lt;/p&gt;
&lt;h2&gt;Deliver value now, refactor later&lt;/h2&gt;
&lt;p&gt;You really do want to deliver value quickly and unless you&#39;re in a safety-critical context (health, manufacturing) or compliance-driven context (government, finance) you can worry about making it better later.&lt;/p&gt;
&lt;p&gt;In practice, there are three complications which make this a false economy if they&#39;re not accounted for. One, teams don&#39;t do this for one development cycle and then do the actual refactoring and iterating later. &amp;quot;Later&amp;quot; never comes, or at best &lt;em&gt;some of it&lt;/em&gt; comes in ad hoc fashion over time. Second, hastily coded solutions tend to make a codebase less easy and safe to change. Third, when returning to the code later on you (or by this time, someone else) have to spend time getting fully reacquainted with the implementation and its context.&lt;/p&gt;
&lt;p&gt;Consider this and &lt;strong&gt;then&lt;/strong&gt; decide &amp;quot;yes, actually all of that cost is worth incurring for getting this code change out now&amp;quot; (could be a hotfix, could be preventing imminent churn of a major customer, could be a huge revenue builder) or &amp;quot;no, we can prevent all that cost by spending a bit more time now without losing anything&amp;quot;.&lt;/p&gt;
&lt;h2&gt;A fully loaded sprint&lt;/h2&gt;
&lt;p&gt;A team is heading into a new time-boxed development cycle and their main goal is to deliver the most requested feature by customers, one that&#39;s not very exciting but removes a ton of clicks and data entry. However, the team&#39;s projected velocity is double what they need to deliver it. They fill the rest of their capacity with various bits and bobs from their backlog. Research, bug fixes, library updates, small feature requests.&lt;/p&gt;
&lt;p&gt;A nice full sprint should lead to lots of good things delivered, but does it? What typically unfolds is that the primary goal doesn&#39;t feel primary even to the one or two persons doing work related to it. Questions, obstacles, and risks do not get identified or resolved as quickly as they could have.&lt;/p&gt;
&lt;p&gt;Contrast that to a sprint where everyone is working towards the primary goal before attending to new work. It feels like a top priority, all communication and agile ceremonies are geared towards completing it, anyone not directly writing code is getting stakeholder input or mitigating risks. Probably fewer tickets will get completed, but the primary goal is actually delivered and with more confidence that it will indeed be what all those customers had been waiting for.&lt;/p&gt;
&lt;p&gt;I&#39;m not suggesting a team should work on only one thing per sprint, or even necessarily that the whole team should be working together on one thing &lt;strong&gt;at a time&lt;/strong&gt;, but what I would suggest is that if a sprint has a genuine primary goal that it&#39;s treated like a primary goal instead of only being paid lip service to in the form of a &amp;quot;Sprint Goal&amp;quot; written down where no-one looks at it and a lot of the rest of the sprint being filled with low priority work.&lt;/p&gt;
&lt;h2&gt;Don&#39;t Repeat Yourself&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Don%27t_repeat_yourself&quot;&gt;Don&#39;t Repeat Yourself&lt;/a&gt; is probably one of the most well-known principles in software engineering. It makes intuitive sense. Don&#39;t code the same thing in multiple places when you could abstract it away into one place and call it from anywhere else. Then in the future you only have to make changes in that one place.&lt;/p&gt;
&lt;p&gt;Unfortunately what this can lead to is &lt;a href=&quot;https://kentcdodds.com/blog/aha-programming&quot;&gt;premature abstraction&lt;/a&gt; or &lt;a href=&quot;https://testing.googleblog.com/2019/12/testing-on-toilet-tests-too-dry-make.html&quot;&gt;indiscriminate abstraction&lt;/a&gt;. Coming from the world of software testing, I&#39;ve seen this a lot in UI test suites.&lt;/p&gt;
&lt;p&gt;In premature abstraction, a UI interaction might be abstracted away into a helper function despite it only being used once. That&#39;s too early. You&#39;re now incurring the cost of the test being harder to debug for a benefit that does not yet exist.&lt;/p&gt;
&lt;p&gt;In indiscriminate abstraction, everything is abstracted by default. Take for example the Page Object Model. It can result in beautifully formatted and easy to read specs, but when you&#39;re debugging someone else&#39;s tests it&#39;s not enough to read the specs, you&#39;ll now have to continually navigate under the hood between the specs and various supporting files to understand what&#39;s going on and whether the issue is in the specs, page selectors, global selectors, page helper methods, global helper methods, or the UI.&lt;/p&gt;
&lt;p&gt;This doesn&#39;t mean &amp;quot;never abstract&amp;quot; or &amp;quot;never use POM&amp;quot;. It means, consider the tradeoffs.&lt;/p&gt;
&lt;h2&gt;Repay technical debt when it hurts&lt;/h2&gt;
&lt;p&gt;Some companies reserve a &lt;a href=&quot;https://www.svpg.com/engineering-wants-to-rewrite/&quot;&gt;20% (or higher) &amp;quot;technical debt&amp;quot; margin&lt;/a&gt; for all types of technical tasks outside of feature delivery. The argument against this goes that people will then just look for things to do, things that aren&#39;t necessarily more valuable than backlog items delivering on the business vision. An efficient way of working would be to do only the most important thing right now.&lt;/p&gt;
&lt;p&gt;The practical outcome is leaving technical improvements to be undiscovered or languishing on a backlog. Individually it&#39;s hard for these improvements to compete with revenue-driving user-facing features, but over time the codebase, the test framework, the local development environment, and the CI/CD pipeline become increasingly harder to work with and the application slower and/or less secure for end users.&lt;/p&gt;
&lt;p&gt;You can be better off actually eating the cost of a guaranteed lower team capacity in order to gain the long term benefit of easy and rapid change and a reduction in cross-functional risks.&lt;/p&gt;
&lt;h2&gt;Hire more developers&lt;/h2&gt;
&lt;p&gt;You have three product development teams. You need double the work done. Being a leader who understands that growth requires investment, you greenlight a swift expansion of the teams to a total of six.&lt;/p&gt;
&lt;p&gt;This never seems to work. Similar to &lt;a href=&quot;https://en.wikipedia.org/wiki/Brooks%27s_law&quot;&gt;Brooke&#39;s Law&lt;/a&gt;, doubling the number of developers doesn&#39;t double the amount of work done (or even maintain the current pace). The number of dependencies increase, the number of communication lines increase, there is lots of onboarding to be done, and either you have teams of new joiners who will be slower than existing teams for some time or you mix all the teams and slow everyone down (by less) with severe disruption to the existing team dynamics.&lt;/p&gt;
&lt;p&gt;It may be counterintuitive when aiming for fast growth, but integration of new developers should be done slowly and deliberately in order to actually achieve the intended increase in pace (and have it be sustainable pace, too). The trick is to do it before you need them, even if that means lower utilisation until that need arises. If it&#39;s too late for that, the focus should be less on manpower and more on making tough decisions on priorities.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Unable to boot to desktop after upgrading Raspbian to Buster</title>
    <link href="https://wilcovanes.ch/articles/unable-to-boot-to-desktop-after-upgrading-raspbian-to-buster/"/>
    <updated>2022-03-22T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/unable-to-boot-to-desktop-after-upgrading-raspbian-to-buster/</id>
    <content type="html">&lt;p&gt;When upgrading Raspberry Pi 3&#39;s officially supported OS Raspbian from a version based on Debian 9 (Stretch) to a version based on Debian 10 (Buster), you could find yourself unable to boot to the desktop.&lt;/p&gt;
&lt;p&gt;Assuming the boot order is still set to boot to desktop (you can check this with &lt;code&gt;sudo raspi-config&lt;/code&gt;), a likely cause is not fully updating the sources lists after the upgrade to Buster.&lt;/p&gt;
&lt;p&gt;On the command line, try starting the X Window environment:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;startx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;At this point, you will likely get some error output containing the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Fatal server error:
(EE) no screens found(EE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&#39;s another thing you can check to determine that we&#39;re on our way to the right solution. Open the X Server log with &lt;code&gt;nano /var/log/Xorg.0.log&lt;/code&gt; and hit CTRL+W to search for &lt;em&gt;fbturbo&lt;/em&gt;. You may then find the message &lt;code&gt;Failed to load module &amp;quot;fbturbo&amp;quot; (module does not exist, 0)&lt;/code&gt;. Hit CTRL+X to close the file.&lt;/p&gt;
&lt;p&gt;To fix the issue, update your sources lists to find packages compatible with Buster, update your packages accordingly, and remove the &lt;code&gt;fbturbo&lt;/code&gt; driver (that last step was required for me, I have no explanation for why):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;sudo nano /etc/apt/sources.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Look for &lt;code&gt;stretch&lt;/code&gt; and if you find it, replace it with &lt;code&gt;buster&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Hit CTRL+X and save the change if you made one&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo nano /etc/apt/sources.list.d/raspi.list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Look for &lt;code&gt;stretch&lt;/code&gt; and if you find it, replace it with &lt;code&gt;buster&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Hit CTRL+X and save the change if you made one&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt full-upgrade&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo mv /usr/share/X11/xorg.conf.d/99-fbturbo.conf ~&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now you should be able to reboot and be presented with the desktop, or run &lt;code&gt;startx&lt;/code&gt; to get there.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How does a test specialist fit into a DevOps and Continuous Delivery team?</title>
    <link href="https://wilcovanes.ch/articles/how-does-a-test-specialist-fit-into-a-devops-and-continuous-delivery-team/"/>
    <updated>2022-01-21T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-does-a-test-specialist-fit-into-a-devops-and-continuous-delivery-team/</id>
    <content type="html">&lt;h2&gt;Context example&lt;/h2&gt;
&lt;p&gt;Let&#39;s assume a feature team (or &lt;a href=&quot;https://teamtopologies.com/key-concepts&quot;&gt;a stream-aligned team&lt;/a&gt;) which is independently able to deliver value in some form, such as feature updates in a section of an application. The team has strong expertise in back-end and front-end development, software architecture, product management, and UI design. The team automatically merges any changes which pass the pipeline stages directly into the main trunk. Work in progress is hidden from end users through feature toggles.&lt;/p&gt;
&lt;p&gt;How does a test specialist fit into this team?&lt;/p&gt;
&lt;h2&gt;Software quality in DevOps and CD&lt;/h2&gt;
&lt;p&gt;If the whole team is responsible for software quality, do you need a test specialist? Everyone should build the skill and claim the time to challenge explicit requirements, discover implicit requirements, cover cross-functional requirements, write test automation at the right level, perform skilled exploratory testing, and improve testability and observability. If a team has no trouble doing this and doing it well, they probably don&#39;t need a test specialist. In practice, teams without test specialists lack in all of the above. Teams with test specialists sometimes also lack in many of the above. If you are that test specialist you&#39;ll never lack for things to do and improve.&lt;/p&gt;
&lt;p&gt;So what might a test specialist practically do, on an average day?&lt;/p&gt;
&lt;h3&gt;Pairing &amp;amp; ensembling&lt;/h3&gt;
&lt;p&gt;Feedback should come as close to when the code is being written as possible, to reduce delays and to reduce context switching. There are a couple of ways of going about this.&lt;/p&gt;
&lt;p&gt;Ensemble programming (also known as mob programming) means completing the entire development cycle for a discrete piece of work as a team. Feedback can&#39;t get more immediate than this. Instead of thinking of a solution on your own and having it be examined much later, the whole team is there to discuss or whiteboard it together. Every line of code is written together, getting continuous collaborative code review. A test specialist should be there to challenge the solution, to generate test ideas, to give guidance on how to make the automated test scenarios meaningful.&lt;/p&gt;
&lt;p&gt;When the team does not work with ensembling, a test specialist can give the same input on a smaller scale through pair testing. This can happen when a merge request has passed code review — to make it so retesting is reduced — or as soon as a merge request is created — to keep feedback closer to when code is created — or before code has been written — to challenge or rubber duck the solution before it has been embarked upon. As with ensembling, pairing has additional benefits of accelerated knowledge sharing and resolving questions without delay.&lt;/p&gt;
&lt;h3&gt;Shifting left &amp;amp; shifting right&lt;/h3&gt;
&lt;p&gt;There are many more opportunities for improving software quality than a &amp;quot;Ready for Testing&amp;quot; column would suggest. Imagine a team is discussing a feature which would allow a warehouse&#39;s working hours to be set. A developer asks what the granularity should be for each working day&#39;s start and end times. A discussion follows, comparing the consequences of choosing 1 hour to 30 minutes to 15 minutes. An agreement is reached and the team moves on. A tester could have been there to ask a question like &amp;quot;what if the user wants to set lunch hours in addition to just start and end times?&amp;quot; One simple question could prevent a great deal of headache later on.&lt;/p&gt;
&lt;p&gt;It wouldn&#39;t have to be just one question, of course, nor would they have to be about completeness of requirements. It could also be asking why we think there will be return on investment from this feature. It could also be asking whether we can get more information on the user needs from the users directly. It could also be rewording the requirements to be more lucid, revealing new possible scenarios or edge cases as a side effect. It could also be asking if there&#39;s a better way to solve the underlying problem the users want solved. It could also be asking how we&#39;re going to prove we&#39;ve done a good job. It could also be asking about cross-functional risks, to help prevent performance issues or security vulnerabilities and so on.&lt;/p&gt;
&lt;h3&gt;Test automation &amp;amp; testability&lt;/h3&gt;
&lt;p&gt;A test specialist could inform and/or do the scripting of test automation. When a tester writes test automation it&#39;s typically in the form of UI-level tests which exercise the entire stack, as lower level tests live closer to the code and are more easily created by those also living close to the code. If UI tests are considered appropriate for the team, the test specialist can implement and maintain these tests and optimise them for running as swiftly and as reliably as they can and ensure the information they provide is of value. Aside from UI-level checks, they could also write custom test tooling wherever it would provide more benefit than it would cost to create and maintain.&lt;/p&gt;
&lt;p&gt;The tester could also inform the test automation by deciding with the team which happy and unhappy scenarios would make sense to cover at the unit versus integration versus UI level. If the test strategy is for UI-level tests to cover critical user journeys, then they could analyse these journeys. They could also help developers in determining what is a useful unit tests, what is a useful integration tests, are they testing what they are trying to test, what could they add, what could they throw away, what could be more efficient?&lt;/p&gt;
&lt;h3&gt;Observability &amp;amp; user advocacy&lt;/h3&gt;
&lt;p&gt;If you have a team with developers, a product owner, and a UX/UI designer, then you have a technical perspective, a business perspective, and a user perspective. With a test specialist you have someone who combines all three in one person. You might argue that everyone in a team should have all three perspectives, which I agree with, but in practice it&#39;s difficult to get out of the detailed technical perspective when you&#39;re knee deep (or neurons deep) in code unless you&#39;re participating in a pairing or ensembling session. A tester should think beyond correctness, about what else will matter to users and weigh those things against the cost of developing and testing against them. A tester should also challenge assumptions about who the user is and what the priorities of the business are.&lt;/p&gt;
&lt;p&gt;Then, when a code change is deployed, the team should not stop caring. A test specialist can help ensure that a change continues to be monitored — regardless of whether they ultimately are the one to do so or not — to see what the uptake is, to see if any unpredicted issues occur, to bring back customer feedback to the team, to set up dynamic threshold alerts to be automatically notified if key metrics become worrisome, to create an environment where the team gets the information it needs to make good follow-up decisions after a change.&lt;/p&gt;
&lt;h3&gt;Exploratory testing&lt;/h3&gt;
&lt;p&gt;When testing is imagined as verification of explicitly defined functional requirements, then there is neither much joy nor value in testing. Testing should go far beyond that and exploratory testing is one way how. This doesn&#39;t change in a DevOps or CD environment. Just because it works best to not have a gated testing phase doesn&#39;t mean an end to exploratory testing. As soon as there&#39;s a branch for new change you could serve the application locally to test the change in the actual user interface, or you may be able to trigger a test environment for the branch, or you may do it in a pairing session, or you may do it in production. Wherever you do it, there need to be opportunities to uncover unknown unknowns.&lt;/p&gt;
&lt;p&gt;This is where a test specialist could use their imagination, their metacognitive abilities, their experience of what they&#39;ve seen before, and the skill they have hopefully been able to hone for years to efficiently discover all kinds of unexpected functional and cross-functional issues and risks. Their mind should be buzzing with questions and they should be running mini-experiments to answer the ones that they think matter most in the moment, leading to more questions and more mini-experiments. This is skilled work only a human can do. It&#39;s also difficult to teach, but valuable for everyone in a team to gain some ability in.&lt;/p&gt;
&lt;h3&gt;Continual process improvement&lt;/h3&gt;
&lt;p&gt;Code is not created in a vacuum. The cadence of delivery and its results might be affected by many things. How are business opportunities and user needs communicated and refined? How do teams align with each other? How do teams align internally? How do teams manage and prevent technical debt? A test specialist is perfectly placed to guide such processes to be efficient yet effective. Their broad involvement across the entire software development cycle, their drive for improvement (or, less charitably, their fixation on imperfection), their knowledge of how to deliver software with high quality and all the processes and human dynamics that affect it, it all puts them into a good position to make positive changes.&lt;/p&gt;
&lt;p&gt;A lot of this might sound like something for an agile coach to do and/or for the whole team to do through retrospectives. It&#39;s certainly not necessary to have these activities done by a test specialist. It should also not be expected that one person has sole responsibility for improving ways of working, but if you have a team who aren&#39;t already making regular measurable improvements and no agile coach or a not especially skilled agile coach then the test specialist&#39;s role in this becomes crucial.&lt;/p&gt;
&lt;h3&gt;Quality coaching&lt;/h3&gt;
&lt;p&gt;A test specialist can help a team form a quality culture where the end goal is not just to deliver as much and as quickly as possible, but to do it in a way whereby you can demonstrate that something of value was done, to do it as a team in which everyone helped to make the end result the best it could be, and to do it with as little waste as possible.&lt;/p&gt;
&lt;p&gt;A great coach is someone who seeks to make themselves unnecessary, but is able to intervene at just the right moment with just a few words or a simple question to let a team discover a better way of doing things or bring out the best in an individual. A tester has likely already learned great diplomatic skill due to having to make unpleasant news less annoying many times over, has learned humility through being thought of by some as disposable or redundant, and could leverage their social skills to give a team motivation and even enthusiasm in being critical, writing good tests, clearly structuring code, collaborating well, and thinking of ways to grow justified confidence in their own work.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;These are some of the ways in which a test specialist can be of value in a &amp;quot;modern&amp;quot; software development team using DevOps and Continuous Delivery practices. Is it too much for one person? Yes. They will have to do more of some and less of others, prioritising whatever the team needs more of at the time. Or you might go as far as having multiple testing roles, such as one focused on test automation, test tooling, and optimising pipelines, and another focused on whole-team testing, shifting left &amp;amp; right, exploratory testing, and testability and observability. Either way, there&#39;s a still a lot more for test specialists to do than merely translating acceptance criteria into UI-level tests.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How to prevent a team from feeling overburdened</title>
    <link href="https://wilcovanes.ch/articles/how-to-prevent-a-team-from-feeling-overburdened/"/>
    <updated>2022-01-14T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-to-prevent-a-team-from-feeling-overburdened/</id>
    <content type="html">&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;: Software development teams with an Agile/Scrum-like way of working.&lt;/p&gt;
&lt;p&gt;Say you&#39;ve got a pretty good situation going. Executive-level managers tell the feature teams they have full autonomy and responsibility for their work. The teams are told the company goals, given some problems to solve that could help reach those goals, but the teams are free to come up with better solutions, prioritise their efforts, anything they think is best to reach the company goals. Within the teams, the product owners are fully on board with this as well, with team members having the autonomy to decide what goes into their sprints.&lt;/p&gt;
&lt;p&gt;Yet, the feature teams almost universally complain that the workload is excessive, that they feel like they&#39;re always just pumping out features, always rushing, that there&#39;s no time to do proper design thinking or refactoring, that there&#39;s no room to develop themselves at this company.&lt;/p&gt;
&lt;p&gt;How could this possibly be? The teams have been told they are autonomous, they&#39;ve been given the space to plan their own work, yet they feel overburdened.&lt;/p&gt;
&lt;p&gt;Imagine an airline which holds safety as its highest priority. It does all it can reasonably do in terms of safety procedures, audits, reports, training, corporate communications. Yet, lots of minor incidents are happening and even a near disaster. When digging deep, you find that the experience of personnel is not that safety is the highest priority. They feel that the informal communications and performance evaluations are geared towards the number of on-time departures and arrivals and the amount of time a plane is out of action. Sometimes when a situation feels trivially worrying, maintenance personnel and pilots alike decide to forge ahead. After all, it would be hard to explain to management why so much time was lost in extra checks for something that most of the time will turn out to be a non-issue.&lt;/p&gt;
&lt;p&gt;The point is you can invest lots of time and effort into communicating how you want people to behave, but unless your incentives and your casual interactions don&#39;t line up with it and do so consistently, it won&#39;t matter.&lt;/p&gt;
&lt;p&gt;How do you fix it?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Decide if you actually do trust the teams. If not, you have other problems to fix first.&lt;/li&gt;
&lt;li&gt;Be consistent in your communication. You could encourage teams in just the right way for a year, but one &amp;quot;it feels like there&#39;s very little output&amp;quot; or &amp;quot;is my favourite feature X done yet?&amp;quot; can undo &lt;em&gt;all&lt;/em&gt; of it.&lt;/li&gt;
&lt;li&gt;When anyone still complains the pace is unsustainable, ask them what&#39;s keeping them from changing that pace. At this point the solution should require only a conversation and the answer will make it clear who need to have that conversation.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Edit a file on startup in Windows</title>
    <link href="https://wilcovanes.ch/articles/edit-a-file-on-startup-in-windows/"/>
    <updated>2021-07-23T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/edit-a-file-on-startup-in-windows/</id>
    <content type="html">&lt;h2&gt;Example use case&lt;/h2&gt;
&lt;p&gt;Maybe you need a value in a Docker &lt;code&gt;config.json&lt;/code&gt; to be updated and persist, but the value resets whenever Docker is closed down.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;h3&gt;Create a startup script&lt;/h3&gt;
&lt;p&gt;In any text editor or IDE, create a &lt;code&gt;startup.ps1&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;In the file, add the following (while replacing with the values relevant to you):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;powershell -Command &amp;quot;(gc &#92;&#92;wsl$&#92;Ubuntu-20.04&#92;home&#92;wilco&#92;.docker&#92;config.json) --replace &#39;oldvalue&#39;, &#39;newvalue&#39; | sc &#92;&#92;wsl$&#92;Ubuntu-20.04&#92;home&#92;wilco&#92;.docker&#92;config.json&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gc&lt;/code&gt;: Get-Content (get the contents of a file)&lt;br&gt;
&lt;code&gt;sc&lt;/code&gt;: Set-Content (write to a file)&lt;/p&gt;
&lt;p&gt;Place this file in a convenient location, such as your user directory or &lt;code&gt;C:&#92;tools&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Schedule a startup task&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;Task Scheduler&lt;/strong&gt; (Windows key + start typing &lt;em&gt;task&lt;/em&gt;, then select it)&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Create Basic Task..&lt;/strong&gt; (under &lt;strong&gt;Task Scheduler Library&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Give the task a name you will recognise later&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Next&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;When I log on&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Next&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ensure &lt;strong&gt;Start a program&lt;/strong&gt; is selected&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Next&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Program/script&lt;/strong&gt;, enter &lt;em&gt;PowerShell.exe&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Add arguments&lt;/strong&gt;, add &lt;em&gt;-ExecutionPolicy Bypass C:&#92;YOURSCRIPTLOCATION&#92;startup.ps1&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Next&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Finish&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;-ExecutionPolicy Bypass&lt;/code&gt;: you&#39;re allowing PowerShell to do anything in this script, nothing is blocked or warned about.&lt;/p&gt;
&lt;p&gt;And there you go. Check the file has the old value, reboot, and you will find the file is now updated.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>It is the best of times and the worst of times for a test specialist</title>
    <link href="https://wilcovanes.ch/articles/it-is-the-best-of-times-and-the-worst-of-times-for-a-test-specialist/"/>
    <updated>2021-07-11T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/it-is-the-best-of-times-and-the-worst-of-times-for-a-test-specialist/</id>
    <content type="html">&lt;p&gt;Finally!&lt;/p&gt;
&lt;p&gt;You&#39;ve arrived in an organisation that embraces DevOps culture, Continuous Delivery in a microservices environment, agile in the truest sense, and is filled with clever and driven people. This is it. This is how you deliver software (in some contexts), this is what will make you proud of the work your team delivers and the role you as a test specialist have played in it.&lt;/p&gt;
&lt;p&gt;Right?&lt;/p&gt;
&lt;p&gt;Maybe not.&lt;/p&gt;
&lt;p&gt;Maybe your team has a test engineer building a large, slow, unreliable test suite of end-to-end UI-level tests. Developers are adding some unit and integration tests without collaboration with the test engineer and this is all that Shifting Left means to the team. Exploratory testing is something that is unheard of or is considered to mean manual ad hoc testing that still needs to be automated. Performance, security, resilience, and other quality concerns not explicitly covered in acceptance criteria are ignored or addressed only when an incident or enough customer complaints happen. The quality of the monitoring, alerting, and logging are afterthoughts. Standups are used to justify time spent instead of progressing work. Refinements lead to no changes to the acceptance criteria or priorities. Each team member works on their own ticket. There is no mob programming or pair programming or pair testing.&lt;/p&gt;
&lt;p&gt;It&#39;s the best of times for a test specialist, because lessons you&#39;ve learned over time are now being promoted on a large scale.
It&#39;s the worst of times for a test specialist, because they&#39;re being implemented in the worst way possible if at all.&lt;/p&gt;
&lt;p&gt;It&#39;s the worst of times for a test specialist, because great testers are becoming mediocre developers.
It&#39;s the best of times for a test specialist, because some developers are now very open to learning better testing.&lt;/p&gt;
&lt;p&gt;It&#39;s the worst of times for a test specialist, because your expertise is no longer considered essential.
It&#39;s the best of times for a test specialist, because you can turn all this around.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How to make standups more useful</title>
    <link href="https://wilcovanes.ch/articles/how-to-make-standups-more-useful/"/>
    <updated>2021-07-07T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-to-make-standups-more-useful/</id>
    <content type="html">&lt;p&gt;Practical examples of how to recognise a less effective standup and how to improve:&lt;/p&gt;
&lt;h2&gt;&amp;quot;Who wants to start?&amp;quot;&lt;/h2&gt;
&lt;p&gt;Crickets chirp, time drags into eternity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt; wait for a volunteer to be the first to speak&lt;br&gt;
&lt;strong&gt;Better:&lt;/strong&gt; call out a name, or pass around a microphone or other token&lt;br&gt;
&lt;strong&gt;Best:&lt;/strong&gt; go from right to left on the board, whoever&#39;s assigned to the ticket should speak&lt;/p&gt;
&lt;h2&gt;The three questions&lt;/h2&gt;
&lt;p&gt;Instead of having individual contributors justify their time, focus as a team on what needs to happen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;From person to person:&lt;/p&gt;
&lt;p&gt;&amp;quot;What did you do yesterday?&amp;quot;&lt;br&gt;
&amp;quot;What will you do today?&amp;quot;&lt;br&gt;
&amp;quot;Are there any impediments in your way?&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Better:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Go from ticket to ticket (from closest to done to less close to done):&lt;/p&gt;
&lt;p&gt;&amp;quot;This is our current progress.&amp;quot;&lt;br&gt;
&amp;quot;This is what we still need (to do).&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Go from ticket to ticket (from closest to done to less close to done):&lt;/p&gt;
&lt;p&gt;&amp;quot;This is what we still need (to do) to get this done.&lt;br&gt;
&amp;quot;Who will explicitly commit to (code reviewing, rubber ducking, pair testing)?&amp;quot;&lt;br&gt;
&amp;quot;I&#39;m ##% confident we&#39;ll get this done today.&amp;quot;&lt;/p&gt;
&lt;p&gt;And &amp;quot;are we still aligned with the priorities and does anything in To Do need unblocking?&amp;quot;&lt;/p&gt;
&lt;p&gt;All of this helps keep people focused throughout and to think like an actual team.&lt;/p&gt;
&lt;h2&gt;Waiting to discuss obstacles&lt;/h2&gt;
&lt;p&gt;The (correct) notion that obstacles can be discussed in Standups and Retros means that some people wait for those occasions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: Waiting for a ceremony to discuss being blocked or impeded by something.&lt;br&gt;
&lt;strong&gt;Better&lt;/strong&gt;: Raising urgent blockers or impediments to a Scrum Master immediately.&lt;br&gt;
&lt;strong&gt;Best&lt;/strong&gt;: Sorting out an urgent blocker or impediment with the relevant team members or stakeholders immediately.&lt;/p&gt;
&lt;p&gt;Note: there are times when raising an impediment with a Scrum Master is a better use of everyone&#39;s time than solving it yourself with the relevant people who can help. Use your best judgement.&lt;/p&gt;
&lt;h2&gt;Diversions&lt;/h2&gt;
&lt;p&gt;It&#39;s easy and natural for discussions to occur and for those discussions to take too long and not need everyone&#39;s attention.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: Using standups for freeform discussions on technical implementations, design walkthroughs, process improvements.&lt;br&gt;
&lt;strong&gt;Better&lt;/strong&gt;: Nipping in the bud any meandering discussions and encouraging them to be continued afterwards in a smaller group.&lt;br&gt;
&lt;strong&gt;Best&lt;/strong&gt;: Nipping discussions in the bud except where a problem could be most easily and quickly solved by the whole team right then.&lt;/p&gt;
&lt;h2&gt;Being dead serious&lt;/h2&gt;
&lt;p&gt;Every day&#39;s experience in agile software development teams affirms how much it matters for a team to be happy and communicate well with each other in order to do their best work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: Discourage the wasting of time with joking around and silly distractions.&lt;br&gt;
&lt;strong&gt;Better&lt;/strong&gt;: Encourage positive socialising among team members during standups.&lt;br&gt;
&lt;strong&gt;Best&lt;/strong&gt;: Start the standup with casual banter, during the standup encourage/praise/thank others, end on an optimistic note.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Get the number of items in a dropdown via DevTools</title>
    <link href="https://wilcovanes.ch/articles/get-the-number-of-items-in-a-dropdown-via-devtools/"/>
    <updated>2021-07-05T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/get-the-number-of-items-in-a-dropdown-via-devtools/</id>
    <content type="html">&lt;p&gt;Say you have a page containing one or more dropdowns with lots of options and you want to know exactly how many options are being displayed.&lt;/p&gt;
&lt;h2&gt;Chrome&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Visit the page&lt;/li&gt;
&lt;li&gt;Open DevTools (F12)&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Elements tab&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Right-click the dropdown -&amp;gt; &lt;strong&gt;Inspect&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Find the &lt;code&gt;select&lt;/code&gt; or &lt;code&gt;ul&lt;/code&gt; element which contains the &lt;code&gt;option&lt;/code&gt; or &lt;code&gt;li&lt;/code&gt; options&lt;/li&gt;
&lt;li&gt;Right-click that element -&amp;gt; &lt;strong&gt;Copy&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Copy selector&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Open search (CTRL + F) and paste the element&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;&amp;gt; option&lt;/code&gt; (if the element is a &lt;code&gt;select&lt;/code&gt;) or &lt;code&gt;&amp;gt; li&lt;/code&gt; (if the element is a &lt;code&gt;ul&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You will now see &lt;strong&gt;1 out of ##&lt;/strong&gt; where &lt;strong&gt;##&lt;/strong&gt; is the number of dropdown options.&lt;/p&gt;
&lt;h2&gt;Firefox&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Visit the page&lt;/li&gt;
&lt;li&gt;Open DevTools (F12)&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Inspector&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Right-click the dropdown -&amp;gt; &lt;strong&gt;Inspect&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Find the &lt;code&gt;select&lt;/code&gt; or &lt;code&gt;ul&lt;/code&gt; element which contains the &lt;code&gt;option&lt;/code&gt; or &lt;code&gt;li&lt;/code&gt; options&lt;/li&gt;
&lt;li&gt;Right-click that element -&amp;gt; &lt;strong&gt;Copy&lt;/strong&gt; -&amp;gt; &lt;strong&gt;CSS Selector&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Open search (CTRL + F) and paste the element&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;&amp;gt; option&lt;/code&gt; (if the element is a &lt;code&gt;select&lt;/code&gt;) or &lt;code&gt;&amp;gt; li&lt;/code&gt; (if the element is a &lt;code&gt;ul&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;&amp;gt; option&lt;/code&gt; (if the element is a &lt;code&gt;select&lt;/code&gt;) or &lt;code&gt;&amp;gt; li&lt;/code&gt; (if the element is a &lt;code&gt;ul&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You will now see &lt;strong&gt;1 out of ##&lt;/strong&gt; where &lt;strong&gt;##&lt;/strong&gt; is the number of dropdown options.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Fix incorrect time in WSL Ubuntu and Docker</title>
    <link href="https://wilcovanes.ch/articles/fix-incorrect-time-in-wsl-ubuntu-and-docker/"/>
    <updated>2021-07-04T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/fix-incorrect-time-in-wsl-ubuntu-and-docker/</id>
    <content type="html">&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;The system time in WSL is out of sync with your Windows system time. Docker containers launched from WSL suffer from the same incorrect time, which they inherit from WSL. I&#39;ve observed this in WSL2 Ubuntu in Windows 10.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We can use &lt;code&gt;ntpdate&lt;/code&gt; to update the date &amp;amp; time according to some source we trust. Generally running an &lt;code&gt;ntpd&lt;/code&gt; daemon is preferred over using &lt;code&gt;ntpdate&lt;/code&gt;, but it&#39;s quick and does the job and I&#39;m not worried about issues with running services getting confused about living in a different time all of a sudden.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ntpdate -v&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If this command gives &lt;code&gt;Command &#39;ntpdate&#39; not found&lt;/code&gt;, run &lt;code&gt;sudo apt install ntpdate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo ntpdate time.windows.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Other NTP servers can be found on &lt;a href=&quot;https://www.ntppool.org/en/&quot;&gt;the NTP Pool Project website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;date&lt;/code&gt; to verify the date &amp;amp; time are now correct.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How to use Chrome in Cypress on WSL</title>
    <link href="https://wilcovanes.ch/articles/how-to-use-chrome-in-cypress-on-wsl/"/>
    <updated>2021-07-01T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-to-use-chrome-in-cypress-on-wsl/</id>
    <content type="html">&lt;h2&gt;Problem 1&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Can&#39;t run because you&#39;ve entered an invalid browser name.

Browser: &#39;chrome&#39; was not found on your system or is not supported by Cypress.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Solution 1&lt;/h2&gt;
&lt;p&gt;I always like to have a command I can run before and after a change to verify a change had the desired effect:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;google-chrome --version&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This should fail. Now install the latest version of Chrome in WSL:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Followed by:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo apt -y install ./google-chrome-stable_current_amd64.deb&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can delete the &lt;code&gt;.deb&lt;/code&gt; archive afterwards.&lt;/p&gt;
&lt;p&gt;Now again try:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;google-chrome --version&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Problem 2&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Could not start Cypress headlessly. Your CI provider must support XVFB.&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Solution 2&lt;/h2&gt;
&lt;p&gt;Ensure you&#39;ve enabled &lt;a href=&quot;https://wilcovanes.ch/articles/setting-up-the-cypress-gui-in-wsl2-ubuntu-for-windows-10/&quot;&gt;running the Cypress GUI in WSL&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How to handle technical debt</title>
    <link href="https://wilcovanes.ch/articles/how-to-handle-technical-debt/"/>
    <updated>2021-06-30T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-to-handle-technical-debt/</id>
    <content type="html">&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;With the best intentions not to release anything that doesn&#39;t meet the highest standards of quality, the pressure of speedy delivery and the realities of imperfect humans and circumstances can mean we build up technical debt which over time causes the code to be more difficult to maintain, the application to seem slower to end users, the company to be vulnerable to security breaches, and so on.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;h3&gt;Expose it&lt;/h3&gt;
&lt;p&gt;Create tasks in your task &amp;amp; issue management system. Some teams wait to be given permission by management or product owners to handle technical debt and when they eventually get their way, they have no tasks ready to go. Create the tasks. If a team isn&#39;t coming up with enough initiatives, find out what&#39;s holding them back. Is it lack of time? Is it lack of expertise? Is it lack of analytical tools? Find out &amp;amp; address it.&lt;/p&gt;
&lt;h3&gt;Promote it&lt;/h3&gt;
&lt;p&gt;There is or should be a healthy push and pull in agile development teams between developers and the product owner when it comes to what goes into a sprint. The team picks the tickets, but the product owner should protest if what&#39;s being pulled in is out of touch with the product roadmap. The product owner can protest, but the team must stand up for quality. If either side is too shy, you&#39;ll end up delivering insufficiently valuable work or insufficient valuable work.&lt;/p&gt;
&lt;h3&gt;Protect it&lt;/h3&gt;
&lt;p&gt;Even during a sprint, external pressures can mean work on technical debt tasks is halted or delayed in favour of more urgent feature work. In theory, a team should be assertive enough to protect against the inevitable eroding of technical debt work. In practice it means persuasive &amp;quot;just this time&amp;quot; requests. To prevent this, get buy-in from the highest levels of the organisation, demonstrate to them the importance of working on technical debt in their language (ROI) and explicitly agree with them a % margin in each sprint that teams can dedicate to it, as otherwise the long term benefit of moving fast safely can&#39;t compete with the more immediate benefits of feature delivery until it&#39;s too late.&lt;/p&gt;
&lt;h3&gt;Spread it&lt;/h3&gt;
&lt;p&gt;Some organisations get so fed up with technical debt, they decide to dedicate 1 or 2 sprints solely to paying off technical debt. This doesn&#39;t work. First, you lose momentum on feature work and there will be pressure to make up for it. Second, the technical debt might be heavy on a particular specialty (such as backend). Third, you will just have to do it again after a few months. You&#39;ll want to keep momentum on both feature work &lt;strong&gt;and&lt;/strong&gt; technical debt. Spread it out and keep up on your repayments.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>The Good, the Bad, and the Pedantic</title>
    <link href="https://wilcovanes.ch/articles/the-good-the-bad-and-the-pedantic/"/>
    <updated>2021-06-23T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/the-good-the-bad-and-the-pedantic/</id>
    <content type="html">&lt;p&gt;If there&#39;s one thing the test community loves, it&#39;s to argue terminology. If you doubt this, throw the question &amp;quot;What&#39;s wrong with the term Quality Assurance?&amp;quot; in any public forum or social media channel with a software testing and quality focus.&lt;/p&gt;
&lt;p&gt;I&#39;m going to highlight three of these semantic arguments, find the good and bad in them, and with brazen hubris declare how they should be now and forever resolved.&lt;/p&gt;
&lt;h2&gt;QAs, do you mind QA&#39;ing this release?&lt;/h2&gt;
&lt;p&gt;Years ago I read an interview with a well-respected test consultant in which he was asked the one thing he wanted non-testers to know about testing and his answer was that they shouldn&#39;t use the term QA, that the term is looked down upon by the test community.&lt;/p&gt;
&lt;p&gt;QA stands for Quality Assurance and it&#39;s true that taken literally we cannot &lt;strong&gt;assure&lt;/strong&gt; quality, if we interpret this as test specialists guaranteeing that a product release has perfect quality. It&#39;s important to know that testing cannot guarantee the absence of bugs and that even the absence of bugs does not guarantee quality.&lt;/p&gt;
&lt;p&gt;However, that&#39;s not on any non-tester&#39;s mind. Their idea of what QA is did not come from reading the dictionary definitions of the individual words that make up Quality Assurance, it came from how the term was used in the companies where they first encountered it. QA, for non-testers, is almost synonymous with testing. Almost. They often come to understand there&#39;s an ineffable extra &#39;thing that QA does&#39; (asking tricky questions, critiquing things that weren&#39;t in the requirements) that doesn&#39;t fit their idea of testing (the latter of which would traditionally be called Quality Control), so they ask someone to QA instead of to test.&lt;/p&gt;
&lt;p&gt;I&#39;ve made the mistake myself of trying to move people from saying Quality Assurance to saying Quality Assistance (and there are many other alternative interpretations of the QA abbreviation, such as Quality Advocacy or Quality Acceleration). The battle to stop people from saying QA or Quality Assurance has raged for a long time (I first came across it in the late 2000s) and it&#39;s not worth the effort.&lt;/p&gt;
&lt;p&gt;Instead of writing blog articles (am I guilty of it myself now?) and social media posts complaining about the term, which doesn&#39;t give the greatest impression to newcomers or casual guests to the test community, explain at appropriate moments what testing and quality specialists can contribute and try not to cringe when someone says QA (which in the case of &amp;quot;QAs&amp;quot; or &amp;quot;QA&#39;ing&amp;quot; is &lt;strong&gt;really&lt;/strong&gt; hard to do!) We don&#39;t have to break up conversations about things that matter with complaints about things that matter less.&lt;/p&gt;
&lt;h2&gt;Manual testing&lt;/h2&gt;
&lt;p&gt;Manual testing is a terrible term. It sounds like grunt work, like a repetitive chore. Scripted testing and exploratory testing are both called manual, but are vastly different in how much they resemble grunt work or a repetitive chore. This is especially significant because manual testing is contrasted with test automation while scripted testing can indeed be automated and exploratory testing cannot (unless you want to be pedantic and say that once a test scenario has been uncovered through exploratory testing you may then be able to automate it).&lt;/p&gt;
&lt;p&gt;This term just has to go.&lt;/p&gt;
&lt;p&gt;I&#39;ve seen some traction within the test community for the idea of getting rid of the manual vs automated dichotomy altogether and instead talk about &amp;quot;attended&amp;quot; and &amp;quot;non-attended&amp;quot; testing. I think this is another battle that&#39;s doomed to be lost, especially considering how thought leaders in the test community have typically engaged with the wider development community (either very little or in an adversarial way). The persuasive power needed is entirely absent.&lt;/p&gt;
&lt;p&gt;A more realistic - and, I think, elegant - solution is to keep the manual vs automated dichotomy, but simply replace &amp;quot;manual&amp;quot; with &amp;quot;human&amp;quot;. Contrasting test automation with &amp;quot;human testing&amp;quot; feels apt, easy to understand, and replaces the association of grunt work with an association of conscious presence and higher reasoning. It&#39;s difficult to imagine someone getting confused because humans are involved in test automation as well, since no-one was confused about test automation involving manual work despite being called automated.&lt;/p&gt;
&lt;h2&gt;Non-functional requirements&lt;/h2&gt;
&lt;p&gt;Non-functional requirements (NFRs) have been an industry term for many years and used as meaning the additional requirements beyond requirements describing the functionality to be implemented. However, some have realised that with a bit of ill will it&#39;s possible to interpret the term as being requirements for an application not to function. And surely we want applications to function?&lt;/p&gt;
&lt;p&gt;I don&#39;t think anyone has ever been genuinely tripped up by the term and thought that it meant an application should be implemented in a way so that it doesn&#39;t function, but rather that this is used as a rhetorical device to advocate for more precise language, but either way it has led to alternatives like extra-functional, para-functional, or cross-functional requirements. To a person who doesn&#39;t know anything about the background, it&#39;s not very intuitive to hear someone talk about para-functional or especially about extra-functional requirements. Are they talking about extra functional requirements (note the absence of a hyphen)?&lt;/p&gt;
&lt;p&gt;Admittedly the term non-functional requirements &lt;strong&gt;is&lt;/strong&gt; a bit imprecise, but instead let&#39;s use cross-functional requirements. It&#39;s by far the most commonly used alternative already. Personally I also find it more intuitive, as it clarifies that they apply across all functionality - for example in the form of SLAs - instead of applying only to a specific feature - for example as defined in a Jira ticket&#39;s acceptance criteria.&lt;/p&gt;
&lt;h2&gt;In short&lt;/h2&gt;
&lt;p&gt;Don&#39;t get too excited about the imperfection of industry-standard terms. You want people to understand what you&#39;re talking about. If you get a chance to use better terms (such as human testing instead of manual testing), promote their use in a human-friendly way (such as adding &amp;quot;also called &lt;em&gt;traditional term&lt;/em&gt;&amp;quot; or a very succinct explanation).&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Handling incorrect iPad data in Google Analytics</title>
    <link href="https://wilcovanes.ch/articles/handling-incorrect-ipad-data-in-google-analytics/"/>
    <updated>2021-06-17T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/handling-incorrect-ipad-data-in-google-analytics/</id>
    <content type="html">&lt;p&gt;This is important to know about when you use mobile/tablet data to make decisions.&lt;/p&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;In Google Analytics, if you filter data by &lt;strong&gt;Device Category&lt;/strong&gt; regex &lt;code&gt;(mobile|tablet)&lt;/code&gt; or by &lt;strong&gt;Mobile Device Model&lt;/strong&gt; -&amp;gt; &lt;strong&gt;iPad&lt;/strong&gt;, it won&#39;t include data for iPads running iPadOS 13 (released September 2019) and above.&lt;/p&gt;
&lt;h2&gt;Cause&lt;/h2&gt;
&lt;p&gt;Apple decided to force desktop behaviour for iPads by changing the User Agent to resemble a desktop device.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;You have to identify the data from iPadOS 13 and above by yourself. You&#39;re looking for &lt;strong&gt;Operating System&lt;/strong&gt; &lt;em&gt;Macintosh&lt;/em&gt; instead of &lt;em&gt;iOS&lt;/em&gt;, with &lt;strong&gt;Screen Resolution&lt;/strong&gt; width (note, this is the logical resolution, so may be lower than you&#39;re expecting) of &lt;em&gt;1024&lt;/em&gt; or lower (which currently covers iPad models with iPadOS 13 and above and excludes macOS models).&lt;/p&gt;
&lt;p&gt;An example of when I needed the percentage of iPad users versus all users:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Note the count of all sessions for a specific date range (&lt;strong&gt;Audience&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Overview&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Find the sessions for macOS (&lt;strong&gt;Technology&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Browser &amp;amp; OS&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Dimension:&lt;/strong&gt; &lt;em&gt;Operating System&lt;/em&gt; -&amp;gt; Value: &lt;em&gt;Macintosh&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Dig down to screen resolution (&lt;strong&gt;Secondary Dimension&lt;/strong&gt;: &lt;em&gt;Screen Resolution&lt;/em&gt;, &lt;strong&gt;Show rows&lt;/strong&gt;: &lt;em&gt;5000&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Export the data (&lt;strong&gt;Export&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Google Sheets&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Agree to importing the data to your Google account&lt;/li&gt;
&lt;li&gt;In Google Sheets, select the &lt;strong&gt;Screen Resolution&lt;/strong&gt; column (in my case: B)&lt;/li&gt;
&lt;li&gt;From the Data menu, select &lt;strong&gt;Split text to columns&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;As separator, select &lt;strong&gt;Custom&lt;/strong&gt;, then enter &lt;em&gt;x&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Now, in any empty cell, enter formula &lt;code&gt;=SUMIF(B:B,&amp;quot;&amp;lt;=1024&amp;quot;,C:C)&lt;/code&gt; (replacing B with the column containing the screen resolution width - the leftmost value - and C with the column containing the sessions)&lt;/li&gt;
&lt;li&gt;Finally, divide this number by the number for all sessions and make it a percentage&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>What do bi-directional contract testing flows look like?</title>
    <link href="https://wilcovanes.ch/articles/what-do-bi-directional-contract-testing-flows-look-like/"/>
    <updated>2021-04-20T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/what-do-bi-directional-contract-testing-flows-look-like/</id>
    <content type="html">&lt;p&gt;It&#39;s pretty easy to find &lt;a href=&quot;https://docs.pactflow.io/docs/bi-directional-contract-testing/&quot;&gt;an explanation of bi-directional contract testing&lt;/a&gt;, but much harder if not impossible to find concrete explanations of how it impacts team workflows for realistic scenarios. Here&#39;s an attempt.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Team Contractors&#39;&lt;/strong&gt; API endpoint &lt;code&gt;/contractor&lt;/code&gt; contains parameters such as &lt;code&gt;available&lt;/code&gt; and &lt;code&gt;stock&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When assigning a contractor to a job, &lt;strong&gt;Team Odd Jobs&lt;/strong&gt; consumes the endpoint, using &lt;code&gt;available&lt;/code&gt; to retrieve available contractors.&lt;/p&gt;
&lt;p&gt;When calculating total stock, &lt;strong&gt;Team Materials&lt;/strong&gt; consumes the endpoint, using &lt;code&gt;stock&lt;/code&gt; to retrieve active stock.&lt;/p&gt;
&lt;p&gt;Now &lt;strong&gt;Team Contractors&lt;/strong&gt; would like to make a change to their &lt;code&gt;/contractor&lt;/code&gt; endpoint, changing the &lt;code&gt;stock&lt;/code&gt; data type from a &lt;code&gt;string&lt;/code&gt; to an &lt;code&gt;integer&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Without bi-directional contract testing&lt;/h2&gt;
&lt;p&gt;Let&#39;s say there is some testing in place.&lt;/p&gt;
&lt;p&gt;There is a test that checks the GET request on &lt;code&gt;/contractor&lt;/code&gt; returns a 200 response. This should PASS.&lt;/p&gt;
&lt;p&gt;There is a test that checks the method that works with &lt;code&gt;stock&lt;/code&gt; is supplied with a &lt;code&gt;string&lt;/code&gt;. This should FAIL.&lt;/p&gt;
&lt;p&gt;Or maybe there is a test that checks the full API response structure against a JSON schema. This should FAIL.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team Contractors&lt;/strong&gt; attempt to merge their change to production.&lt;/p&gt;
&lt;p&gt;The pipeline succeeds.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;Team Odd Jobs&lt;/strong&gt; this is fine, but places where &lt;strong&gt;Team Materials&lt;/strong&gt; retrieve the value for &lt;code&gt;stock&lt;/code&gt; from the &lt;code&gt;/contractor&lt;/code&gt; endpoint fail, not with a 400 Bad Request on the request itself (they are doing a simple GET on &lt;code&gt;/contractor&lt;/code&gt; and taking the value from something like &lt;code&gt;response.data.stock&lt;/code&gt;), but at the point the value is used in the code with some method expecting to work with a string.&lt;/p&gt;
&lt;p&gt;Why did the tests not stop the breaking change?&lt;/p&gt;
&lt;p&gt;Because those tests run in &lt;strong&gt;Team Materials&lt;/strong&gt;&#39; pipeline, not in that of &lt;strong&gt;Team Contractors&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Each team is able to independently deploy their service, with build and test stages covering &lt;strong&gt;only their own service&lt;/strong&gt; and mocking or virtualising any interaction with other services. There are companies who, despite having a microservices architecture in every other respect, solve this by having a stage in the pipeline of each service that builds all services and runs integration and end-to-end tests in order to safeguard interactions between services. This is antithetical to the idea of independently deploying. There is an alternative that&#39;s (almost) as safe and allows us to move a lot faster.&lt;/p&gt;
&lt;h2&gt;With bi-directional contract testing&lt;/h2&gt;
&lt;p&gt;Now let&#39;s say we have bi-directional contract testing (BDCT) in place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team Contractors&lt;/strong&gt; attempt to merge their change to production.&lt;/p&gt;
&lt;p&gt;Their pipeline fails.
It fails at the point where the provider-side contract containing the changed data type is statically compared (by a contract broker) against the latest consumer-side contracts, which in the case of &lt;strong&gt;Team Materials&lt;/strong&gt; still defines an expectation for the old data type.
&lt;strong&gt;Team Odd Jobs&lt;/strong&gt; uses the same endpoint, but doesn&#39;t do anything with the &lt;code&gt;stock&lt;/code&gt; parameter, so their contract doesn&#39;t even include &lt;code&gt;stock&lt;/code&gt; and the contract comparison passes. This consumer is not affected.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team Contractors&lt;/strong&gt; have discovered their breaking change and can instead handle the change properly, such as by applying the &lt;a href=&quot;https://martinfowler.com/bliki/ParallelChange.html&quot;&gt;expand and contract pattern&lt;/a&gt; or by communicating with &lt;strong&gt;Team Materials&lt;/strong&gt; about being more &lt;a href=&quot;https://en.wikipedia.org/wiki/Robustness_principle&quot;&gt;liberal in what they accept&lt;/a&gt; or by some other method the team finds appropriate.&lt;/p&gt;
&lt;h2&gt;Flow details&lt;/h2&gt;
&lt;p&gt;This is what happens in BDCT when a provider (in this case &lt;strong&gt;Team Contractors&lt;/strong&gt;) makes a change:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Team Contractors&lt;/strong&gt; makes a change to their API specification.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team Contractors&lt;/strong&gt; makes the same change to their API endpoint.&lt;/li&gt;
&lt;li&gt;The pipeline runs automated schema validation checks comparing the API specification against the API endpoint.&lt;/li&gt;
&lt;li&gt;The pipeline runs any additional human-coded functional API checks against the API endpoint.&lt;/li&gt;
&lt;li&gt;The pipeline generates a provider-side contract from the API specification, now that we&#39;ve proven it matches the actual endpoint.&lt;/li&gt;
&lt;li&gt;The pipeline returns a response from the contract broker, which statically compares the provider-side contract against the latest consumer-side contracts that use the endpoint.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is what happens in BDCT when a consumer (in this case &lt;strong&gt;Team Materials&lt;/strong&gt;) makes a change:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Team Materials&lt;/strong&gt; changes their mocked test which defines what they need from the provider endpoint.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team Materials&lt;/strong&gt; makes the same change to the actual request.&lt;/li&gt;
&lt;li&gt;The pipeline generates a consumer-side contract from the request-response pair the team&#39;s mocked test defines.&lt;/li&gt;
&lt;li&gt;The pipeline returns a response from the contract broker, which statically compares the consumer-side contract against the latest provider-side contract for the endpoint.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this case of a deliberate change on the consumer side, a failure would mean they should communicate with the provider about the new needs they have, so the provider can update their capabilities. Of course they should be doing this before actually making the change and seeing the pipeline fail, BDCT is just there to say &amp;quot;what do you think you&#39;re doing?&amp;quot; in case this does not happen or when a valid expectation is inadvertently changed to an invalid one.&lt;/p&gt;
&lt;h2&gt;Who are the providers and consumers?&lt;/h2&gt;
&lt;p&gt;The flows apply regardless of whether the providers and consumers are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microservices interacting with each other (and each could be a provider, a consumer, or both)&lt;/li&gt;
&lt;li&gt;The backend (provider) and frontend (consumer) within a service&lt;/li&gt;
&lt;li&gt;External services, if we can agree to upload contracts to a broker one of us owns&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Error while loading shared library libnss3.so running Cypress in Ubuntu</title>
    <link href="https://wilcovanes.ch/articles/error-while-loading-shared-library-libnss3so-running-cypress-in-ubuntu/"/>
    <updated>2021-03-15T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/error-while-loading-shared-library-libnss3so-running-cypress-in-ubuntu/</id>
    <content type="html">&lt;p&gt;When attempting to run Cypress in Ubuntu:&lt;/p&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/home/wilco/.cache/Cypress/4.12.1/Cypress/Cypress: error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sudo apt install libgconf-2-4 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm-dev libnss3-dev libxss-dev&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;What you&#39;re installing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;libgconf-2-4&lt;/code&gt;: GNOME configuration database system (shared libraries).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libatk1.0-0&lt;/code&gt;: ATK accessibility toolkit.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libatk-bridge2.0-0&lt;/code&gt;: AT-SPI 2 toolkit bridge (shared library).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libgdk-pixbuf2.0-0&lt;/code&gt;: GDK Pixbuf library.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libgtk-3-0&lt;/code&gt;: GTK+ graphical user interface library.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libgbm-dev&lt;/code&gt;: Generic buffer management API (development files).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libnss3-dev&lt;/code&gt;: Development files for the Network Security Service libraries.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libxss-dev&lt;/code&gt;: X11 Screen Saver extension library (development headers)&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Setting up the Cypress GUI in WSL2 Ubuntu for Windows 10</title>
    <link href="https://wilcovanes.ch/articles/setting-up-the-cypress-gui-in-wsl2-ubuntu-for-windows-10/"/>
    <updated>2021-03-12T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/setting-up-the-cypress-gui-in-wsl2-ubuntu-for-windows-10/</id>
    <content type="html">&lt;p&gt;ℹ️ &lt;strong&gt;Update April 2025&lt;/strong&gt;: With the inclusion of &lt;a href=&quot;https://github.com/microsoft/wslg&quot;&gt;WSLg&lt;/a&gt; in WSL2, you should no longer need an X server. To test this, run &lt;code&gt;xeyes&lt;/code&gt; in a WSL2 terminal and check if the simple eyes UI appears.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;First, check that you&#39;re actually running WSL2. In a Windows 10 command prompt (not in WSL bash):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wsl -l -v&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;For this guide, you should be seeing NAME: Ubuntu, STATE: Running, VERSION: 2.&lt;/p&gt;
&lt;p&gt;You&#39;ll also need Node. To check if you&#39;ve already got it, again in a command prompt:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node --version&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If you don&#39;t have a project yet with Cypress in it, let&#39;s go into WSL bash and create one. In a command prompt:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;bash&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; ~
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; some-project
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; some-project
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; i cypress --save-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then (or if you already have a project with Cypress), let&#39;s add a convenience command to &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;  &lt;span class=&quot;token string-property property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./node_modules/.bin/cypress&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows us to run Cypress tests by simply using &lt;code&gt;npm t&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you run it right now, it won&#39;t bring up the Cypress GUI. WSL2 doesn&#39;t have the ability to display graphic interfaces, but we can ask Windows 10 to display them for us. We do that using an &lt;a href=&quot;https://wiki.ubuntu.com/X/Architecture&quot;&gt;X Server&lt;/a&gt;. There are a number of them, but I&#39;ll use VcXsrv here. Go ahead and install &lt;a href=&quot;https://sourceforge.net/projects/vcxsrv/&quot;&gt;VcXsrv&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now we have to make sure graphical interfaces launched from WSL2 know what local ip WSL2 is at:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cd ~/home/YOURUSERNAME/.bashrc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add the following to the top(!) of the file:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# set DISPLAY variable to the IP automatically assigned to WSL2&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;&lt;span class=&quot;token environment constant&quot;&gt;DISPLAY&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; /etc/resolv.conf &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; nameserver &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;{print $2; exit;}&#39;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;:0.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we&#39;ll have to enable &lt;a href=&quot;https://en.wikipedia.org/wiki/D-Bus&quot;&gt;D-Bus&lt;/a&gt; so these applications can exchange messages.&lt;/p&gt;
&lt;p&gt;Add the following underneath the place where we set the DISPLAY environment variable:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Automatically start D-Bus to allow communication with Cypress GUI app&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; /etc/init.d/dbus start &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&gt;&lt;/span&gt; /dev/null&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now launch the X Server (if you&#39;re using VcXsrv, look for &lt;strong&gt;XLaunch&lt;/strong&gt;.)&lt;/p&gt;
&lt;p&gt;You can accept the defaults for &lt;strong&gt;Display settings&lt;/strong&gt; and &lt;strong&gt;Client startup&lt;/strong&gt;, but under &lt;strong&gt;Extra settings&lt;/strong&gt; make sure to check the &lt;strong&gt;Disable access control&lt;/strong&gt; checkbox.&lt;/p&gt;
&lt;p&gt;In the Windows security alert that comes up first time XLaunch is launched, allow public &amp;amp; private networks.&lt;/p&gt;
&lt;p&gt;Now launch your project&#39;s web server.&lt;/p&gt;
&lt;p&gt;Finally, launch Cypress with &lt;code&gt;npm t&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>What is the value of UI-level end-to-end tests?</title>
    <link href="https://wilcovanes.ch/articles/what-is-the-value-of-ui-level-end-to-end-tests/"/>
    <updated>2021-03-11T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/what-is-the-value-of-ui-level-end-to-end-tests/</id>
    <content type="html">&lt;p&gt;By UI-level end-to-end tests I mean &lt;em&gt;tests which cover a user flow (the path to complete a task) both horizontally (from start to end of the flow) and vertically (the test involves the entire stack).&lt;/em&gt; Specifically I&#39;m going to talk about automated UI-level end-to-end tests, which drive a browser to run the tests.&lt;/p&gt;
&lt;h1&gt;Levels&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Speed to Run&lt;/th&gt;
&lt;th&gt;Confidence Gained&lt;/th&gt;
&lt;th&gt;Reliability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit (solitary, i.e. classes or functions)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit (sociable, i.e. includes some dependencies)&lt;/td&gt;
&lt;td&gt;Low to Medium&lt;/td&gt;
&lt;td&gt;High to Medium&lt;/td&gt;
&lt;td&gt;Low to Medium&lt;/td&gt;
&lt;td&gt;High to Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration (component, service, or contract)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End to end (UI, acceptance)&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;The test pyramid&lt;/h1&gt;
&lt;p&gt;There you see the rough reason why these levels of testing are traditionally represented as a pyramid. At the lowest level — of a unit test against a single class or function — the test takes a trivial amount of time to run, the test is so close to the code it&#39;s relatively quick to debug, the test is relatively quick to write, and the test is unlikely to pass or fail inconsistently. The downside is that a solitary unit test like this doesn&#39;t tell you very much.&lt;/p&gt;
&lt;p&gt;On the other extreme you have a UI-level end-to-end test scenario. If even a single part of the flow succeeds (for instance, after submitting a valid username and password a My Account link appears) you know a number of things are working together correctly. The downsides are that the test is relatively slow to run, the test could pass or fail inconsistently for various reasons such as fluctuating page load speed, the test is costly to maintain as it depends on a relatively large part of the codebase, and a failing test is more difficult to debug as it lives several layers of abstraction away from the code.&lt;/p&gt;
&lt;h1&gt;Justification&lt;/h1&gt;
&lt;p&gt;With all those downsides, is it even justified to have automated UI-level end-to-end tests? I would say it is if you limit the scenarios to the highest priority paths through the application. For the low cost of just a few scenarios, you can increase a team&#39;s confidence in their output by showing that the most important flows in the application can be completed successfully in a realistic simulation. See it as automated acceptance tests on top of all your other tests.&lt;/p&gt;
&lt;h1&gt;Example&lt;/h1&gt;
&lt;p&gt;For an eCommerce company I covered priority flows which involved the searching for a product, navigating to a product page, adding the product to the cart, going to checkout, and filling the checkout information. To identify these priority flows, you (or a Data Analytics or User Experience specialist near you) can analyse the user behaviour in your analytics suite and come to an agreement with your stakeholders or product owner. Be careful not to overreach. For example, logging in (maybe even by API) to be able to start a flow is fine, but testing various happy and unhappy paths for logging in, account creation, forgotten password, etc. might not be best placed in your end-to-end tests. These might be better done at lower levels.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How much testing is enough in software testing?</title>
    <link href="https://wilcovanes.ch/articles/how-much-testing-is-enough-in-software-testing/"/>
    <updated>2021-03-10T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-much-testing-is-enough-in-software-testing/</id>
    <content type="html">&lt;p&gt;Answers to this question typically fall into two categories: narrative and numerical.&lt;/p&gt;
&lt;h2&gt;Narrative&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Testing is never done, but you should stop when all relevant risks have been covered to the point you can afford (budget) and accept (agreed with stakeholders)&lt;/em&gt; / &lt;em&gt;Testing is done when you&#39;ve comprehensively explored all high and medium priority functionality&lt;/em&gt; / &lt;em&gt;Testing is done when a skilled test specialist judges they have gathered enough information about the product for informed decisions to be made about its delivery&lt;/em&gt; / &lt;em&gt;Testing is done when you&#39;ve exhausted all the test ideas you can come up with&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Numerical&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Testing is done when 100% of planned test cases have been executed&lt;/em&gt; / &lt;em&gt;Testing is done when at least 80% of the test scenarios are passing&lt;/em&gt; / &lt;em&gt;Testing is done when 0 issues are reported from automated or manual test suites&lt;/em&gt; / &lt;em&gt;Testing is done when test coverage is above an agreed percentage, e.g. 95%, and all those tests have been executed with 0 fails&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;My perspective&lt;/h1&gt;
&lt;p&gt;The exit criteria in the numerical category upset me and I will explain, with a number, why that is. The chance that your documented tests accurately represent all the tests that matter is precisely 0 (zero). There simply are too many ways in which software can disappoint.&lt;/p&gt;
&lt;p&gt;That being said, feeling very strongly about an opinion can be a sign of missing important nuance. I will seek that nuance. If you&#39;re working in a highly regulated environment, I can imagine you have to demonstrate your formal exit criteria and how you measure them in concrete KPIs such as executed test cases and passed test cases. I would still hope your testing wouldn&#39;t stop at executing already documented tests, but perhaps prescribe a certain coverage for session-based exploratory testing, and include a formal signoff – if you really work in that type of environment – based on the informed advice of the test specialist(s).&lt;/p&gt;
&lt;p&gt;Another nuance I can think of is marketing agencies producing high-turnover near-identical email, banner, or microsite campaigns. You could choose to have standardised test suites of which you can say that if 100% pass, it&#39;s automatically a go for that campaign. Those test suites could also include less prescriptive high level test scenarios for exploring functional and non-functional requirements, thus covering off more risks whilst still figuring into a number.&lt;/p&gt;
&lt;p&gt;I lean more towards the narrative category. &lt;em&gt;Testing is done when a skilled test specialist judges that - within a particular context of risk tolerance and regulatory requirements - additional time spent on testing is not worth the additional information that would be revealed.&lt;/em&gt; Narratives, however, are not the most practical thing. If I would have to give practical exit criteria for testing in the environment of Agile web development (in which I have done most of my work), I would say:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Requirements have been interrogated and testing needs anticipated (both can be done in 3 Amigos and Refinement sessions)&lt;/li&gt;
&lt;li&gt;Local testing and meaningful code reviews have been completed&lt;/li&gt;
&lt;li&gt;Static, unit, integration, and UI-level test scenarios are passing&lt;/li&gt;
&lt;li&gt;Acceptance criteria in the User Story have been verified&lt;/li&gt;
&lt;li&gt;Other relevant functional and non-functional requirements have been verified (exploratory as well as standards from your test strategy)&lt;/li&gt;
&lt;li&gt;The impact of code changes (performance, live issues, etc.) has been monitored after deployment to production&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When all these have been done, you can stop testing that piece of work. Or can you? You could argue that once a code change is in production, it&#39;s in your care forever, but anything resulting from that care would be a new piece of work.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Common mistakes when handling outages or data breaches</title>
    <link href="https://wilcovanes.ch/articles/common-mistakes-when-handling-outages-or-data-breaches/"/>
    <updated>2021-02-06T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/common-mistakes-when-handling-outages-or-data-breaches/</id>
    <content type="html">&lt;p&gt;Your company&#39;s site is down or you&#39;ve become aware of an active data breach or some other incident occurs and:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it needs to be fixed straight away&lt;/li&gt;
&lt;li&gt;it can&#39;t ever happen again&lt;/li&gt;
&lt;li&gt;you need to deal with the fallout&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some pitfalls to avoid:&lt;/p&gt;
&lt;h1&gt;Wasteful lines of communication&lt;/h1&gt;
&lt;p&gt;You receive a notification from a monitoring tool about slower response times on a website you control.&lt;/p&gt;
&lt;p&gt;You investigate.&lt;/p&gt;
&lt;p&gt;Five minutes later, assessing the cause and scope of the slowdown, the site goes down.&lt;/p&gt;
&lt;p&gt;Two minutes after that, your manager wants to know what&#39;s happening, how it&#39;s possible this is happening to us, and whose fault it is.&lt;/p&gt;
&lt;p&gt;Whilst talking to your manager, the CEO joins in to explain how bad it would be if the site were to stay down.&lt;/p&gt;
&lt;p&gt;During an active incident, people will want to know what went wrong, whose fault it is, and what processes need to change to make sure it doesn&#39;t happen again. In the meantime, the incident will not be investigated or resolved.&lt;/p&gt;
&lt;p&gt;The obvious solution would be to politely ask those people to wait until you have better answers and to reassure them that they will receive updates every X minutes. This doesn&#39;t work very well in real life. It wastes time and not everyone can resist management pressure even when they know it should be done. What you need is a lightning rod. Designating someone as Incident Manager can free up the right people to focus on what needs to be done whilst the rest of the business know who to chase for information.&lt;/p&gt;
&lt;p&gt;Another example of wasteful lines of communication is when multiple people and teams have identified something&#39;s happening and are independently investigating and discussing it and coming to conclusions without being aware of each other&#39;s efforts. This is another reason why it&#39;s so useful for people to know about the lightning rod that is the Incident Manager. They contact the Incident Manager, the Incident Manager decides who is best placed to take action, and they will communicate with that person and the rest of the business.&lt;/p&gt;
&lt;p&gt;What if your business can&#39;t afford to designate someone as Incident Manager? In that case it could be agreed that there will be a messaging group in which any active threats to the business are raised and actions delegated by the group among themselves, with business stakeholders added to this group so they can view progress. However, it may be more expensive to distract all the group members in this way than to designate one person as managing the response to incidents.&lt;/p&gt;
&lt;h1&gt;Inappropriate follow-through&lt;/h1&gt;
&lt;p&gt;Say a data breach has occurred and was handled, you understand exactly what happened, and recommendations have been put together for concrete improvements of which everyone agrees that any one of them would have prevented the incident from occurring if they had been in place.&lt;/p&gt;
&lt;p&gt;A year later the same incident reoccurs, because the improvements have not been done. How do you explain that?&lt;/p&gt;
&lt;p&gt;To avoid having to explain it, prevent similar incidents from occurring again. Now, it&#39;s easy enough to say that you should chase recommendations for improvements until they&#39;ve been completed, but there&#39;s a reason this is such a common omission. There may be strong pressures to return to business as usual, as the incident already caused business disruption and the incident is over. This pressure may be added onto by senior management, the very people with the most to lose if a similar incident did reoccur. You can use that fact - that similar incidents occurring again would damage the business more than the time and budget spent on preventive activities - to gain support.&lt;/p&gt;
&lt;p&gt;So, use the leverage of a harmful event to make real changes. Then, once improvements have been implemented, consider how you might simulate the incident that has occurred and similar realistic versions of it, and test whether your preventive measures are adequate. Perhaps someone&#39;s login details were compromised via a spearphishing attack and used to log in to a CMS through which malware was added to a website. If you&#39;ve implemented defense-in-depth protections, you should then test whether users recognise phishing attempts, whether users have permissions limited to only what they actually need, the ability to add custom HTML/CSS/JS to the CMS disabled or reduced to only what is genuinely needed, and any additional relevant protections such as ip whitelisting.&lt;/p&gt;
&lt;h1&gt;Telling people to do their job better&lt;/h1&gt;
&lt;p&gt;Another pitfall in the recommendations for improvement is to conclude that procedures were not followed and if people would just follow the procedures the incident would not have happened. Whilst that conclusion might be entirely true, telling people to just follow procedures or be less negligent is a fool&#39;s errand. There&#39;s a reason (or there might be a complex web of reasons) why they didn&#39;t following procedures and telling them to do better doesn&#39;t remove those reasons.&lt;/p&gt;
&lt;p&gt;Is their workload so high they simply can&#39;t take the time to go by the book if they want to deliver results? Do they face a great deal of context-switching and distractions? Are the procedures viewed as out of date and out of touch? Are the procedures difficult to find or make sense of? Do the required tools suffer from issues in functionality or usability which invite skipping steps or creative workarounds? There could be any number of reasons.&lt;/p&gt;
&lt;p&gt;You can find out by interviewing the relevant person. They can explain all the reasons something seemed like a good idea at the time. In hindsight it will likely be just as clear to them as to you that something could have been done better, but that doesn&#39;t change that everything they knew and experienced at the time made them do something different. If you don&#39;t change what they or someone else in their position knows and experiences the next time something similar happens, there&#39;s no reason they won&#39;t act the same way next time.&lt;/p&gt;
&lt;h1&gt;Implicit expectations about out-of-hours support&lt;/h1&gt;
&lt;p&gt;When immediate action is required outside of regular business hours, you don&#39;t want to have to call around and find that the needed people are not available or don&#39;t have access to the tools they need. You also don&#39;t want to get into disagreements with vendors about their conditions of out-of-hours support.&lt;/p&gt;
&lt;p&gt;Say you work with an offshore development team and you ask if they provide 24/7 on-call support in case of incidents. Perhaps they respond you can email the project manager at any time and the project manager will then call someone from the team to assist. Don&#39;t stop there. Should we understand the project manager has sound on for their email notifications at all times? Does the rest of their team know (and are prepared for) that any of them can be called at any time of the day or night to respond to an incident? Are we agreed on what counts as an incident? How do the lines of communication proceed once everyone who&#39;s needed is on the case?&lt;/p&gt;
&lt;p&gt;Or say you have internal development teams that have agreed on-call schedules. Have you also agreed whether their time will be compensated? Have you agreed a method of communication, such as a messaging group for which notifications can be set individually, that each team will use? Have you ensured there are fallbacks for people who turn out to be unreachable? Have you ensured incidents that have been known to occur in the past and which can be easily resolved (such as through a service restart) have been documented, so you can prevent waking up people for such issues?&lt;/p&gt;
&lt;h1&gt;Incorrect conclusions due to cognitive biases&lt;/h1&gt;
&lt;p&gt;The direction of investigation into an incident can be powerfully derailed by cognitive biases, as can the root cause analysis and forming effective recommendations for improvement. It would be tiresome to list all the possible cognitive biases, but let&#39;s view a few examples which will alert you to the danger and then let&#39;s see how we can avoid this pitfall.&lt;/p&gt;
&lt;p&gt;Perhaps a manager says a website you&#39;re working on has become dramatically slower without there having been a deployment of any changes. The manager asks a developer about this and the developer loads the website&#39;s homepage with the browser console open and notices a JavaScript error. The developer states &amp;quot;there you go, there&#39;s your problem&amp;quot; and the manager gets another developer to go fix the JavaScript error. The website is no longer slow, then the fix is done, and everyone is happy.&lt;/p&gt;
&lt;p&gt;As you can tell from the order of events, the JS error wasn&#39;t the cause of the reduced performance of the site, plus the performance of the site did not actually change at any time (it was the office&#39;s WiFi connection). How then did the developer and manager get it so wrong? The developer didn&#39;t typically look at the browser console, so they assumed that since there was a problem and since the console showed an error, they must be related. The developer could have seen from the error message that it had no relation to performance, they could also have looked further to see if there could be any other explanation. The developer could have also challenged the premise of slow performance by viewing the monitoring tools to see whether users of the site are experiencing any slowdown, the manager could also have asked whether the site is any slower instead of stating that it is.&lt;/p&gt;
&lt;p&gt;In another example, perhaps a website goes down and someone discovers that there were hundreds of logins during the downtime. When the site returns after a server reboot, an additional basic authentication dialog is added to the login page to prevent what is seen as a brute force hacking attempt. Leaving aside whether this would be the best way to protect on-page and API logins from brute force attack, there was no relation between the logins and the downtime. Cursory investigation would have shown that the hundreds of logins during the downtime came down to five logins per minute, which did not noticeably affect the server or application performance, but it was the same average logins per minute as in the days and weeks before the downtime.&lt;/p&gt;
&lt;p&gt;How to avoid such mistakes? Again there is an obvious answer that doesn&#39;t work in real life. The obvious answer is that people should be aware of cognitive biases in order to avoid them. This is much easier said than done, plus training in recognising cognitive biases does not appear to transfer between different contexts even when closely related. It&#39;s much easier, however, for someone trained in cognitive biases to recognise &lt;em&gt;other people&lt;/em&gt;&#39;s cognitive biases. As a result, a testing or quality specialist is well suited to being involved in incident investigation and remediation.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How to write effective Gherkin scenarios</title>
    <link href="https://wilcovanes.ch/articles/how-to-write-effective-gherkin-scenarios/"/>
    <updated>2020-04-30T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-to-write-effective-gherkin-scenarios/</id>
    <content type="html">&lt;h1&gt;Why do we use Gherkin?&lt;/h1&gt;
&lt;p&gt;Easier and less error-prone &lt;strong&gt;translation of business needs into functional behaviour&lt;/strong&gt;, easier and less error-prone &lt;strong&gt;understanding of functional behaviour&lt;/strong&gt; by the development team, and easier and quicker conversion of &lt;strong&gt;functional behaviour into (manual and automated) acceptance tests&lt;/strong&gt;.&lt;/p&gt;
&lt;h1&gt;When is a scenario good enough?&lt;/h1&gt;
&lt;p&gt;If you&#39;re worried you&#39;re spending too much time crafting the perfect user story, stop when each scenario in your user story:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Covers a single behaviour&lt;/li&gt;
&lt;li&gt;Contains Precondition -&amp;gt; Action -&amp;gt; Result&lt;/li&gt;
&lt;li&gt;Avoids implementation details&lt;/li&gt;
&lt;li&gt;Uses Specification By Example in case of many permutations&lt;/li&gt;
&lt;li&gt;Contains succinct and accurate language&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Quick help&lt;/h1&gt;
&lt;p&gt;Here&#39;s one of the most common pitfalls for teams who just started using Gherkin:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mistake&lt;/strong&gt;: merely reformatting plain text acceptance criteria into Given/When/Then format.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: writing acceptance criteria from scratch as things a user would see and do.&lt;/p&gt;
&lt;p&gt;Merely rewriting acceptance criteria in Gherkin format leads to scenarios which are ambiguous and hide requirements.&lt;/p&gt;
&lt;h2&gt;Example 1&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Scenario 1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIVEN&lt;/strong&gt; the item list&lt;br&gt;
&lt;strong&gt;WHEN&lt;/strong&gt; wanting to add a new item&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; I find a button below the item list to add new items&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What works less well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This scenario describes the presence of an element, not behaviour.&lt;/li&gt;
&lt;li&gt;The scenario implies that if I do not want to add a item then the button should not be there.&lt;/li&gt;
&lt;li&gt;We want to allow adding of items, let the team decide if it should be a button below the list.&lt;/li&gt;
&lt;li&gt;What kind of items are these? Would every stakeholder interpret correctly what items these are?&lt;/li&gt;
&lt;li&gt;Is the precondition correct? Do I just need to view a list or do I need to be logged in as a certain user?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How could it be improved?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the scenario really is about validating the presence of elements, it could be a design document instead&lt;/li&gt;
&lt;li&gt;The scenario could be rewritten to cover the intended behaviour:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Scenario 1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIVEN&lt;/strong&gt; I am logged in as a delivery driver&lt;br&gt;
&lt;strong&gt;WHEN&lt;/strong&gt; I view the list of deliveries&lt;br&gt;
&lt;strong&gt;AND&lt;/strong&gt; I choose to add a delivery&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; I see a list of available parcels&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is no definite correct scenario. Multiple people could come up with different scenarios that are useful.&lt;/li&gt;
&lt;li&gt;This would be one of several scenarios defining a flow for the imaginary process of adding a delivery.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Example 2&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Scenario 1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIVEN&lt;/strong&gt; our application&lt;br&gt;
&lt;strong&gt;WHEN&lt;/strong&gt; attempting to click on the In Progress button after the previous screen&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; it is enabled only when delivery is planned for today&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What works less well&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The supposed precondition tells us nothing.&lt;/li&gt;
&lt;li&gt;The actual precondition is at the end. Once I get to the end, I have to go back to re-interpret the scenario.&lt;/li&gt;
&lt;li&gt;Don&#39;t refer to other scenarios (&amp;quot;previous screen&amp;quot;) within the scenario, set up a precondition if needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example 3&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIVEN&lt;/strong&gt; the Open button on the delivery details&lt;br&gt;
&lt;strong&gt;WHEN&lt;/strong&gt; I change the dropdown to In Progress&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; I get a pop-up window with 3 options (Skip, Cancel, OK)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIVEN&lt;/strong&gt; the displayed pop-up after I have clicked In Progress&lt;br&gt;
&lt;strong&gt;WHEN&lt;/strong&gt; selecting Skip&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; the pop-up is dismissed&lt;br&gt;
&lt;strong&gt;AND&lt;/strong&gt; I get navigated to a screen where the first button is Arrived&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 3&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIVEN&lt;/strong&gt; the displayed pop-up after I have clicked In Progress&lt;br&gt;
&lt;strong&gt;WHEN&lt;/strong&gt; selecting Cancel&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; the pop-up is dismissed&lt;br&gt;
&lt;strong&gt;AND&lt;/strong&gt; I go back to the original screen&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 4&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIVEN&lt;/strong&gt; the displayed pop-up after I have clicked In Progress&lt;br&gt;
&lt;strong&gt;WHEN&lt;/strong&gt; selecting OK&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; the pop-up is dismissed&lt;br&gt;
&lt;strong&gt;AND&lt;/strong&gt; the delivery is marked as In Progress&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What works less well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Difficult to identify gaps or issues in the user flow without a full view of interaction options.&lt;/li&gt;
&lt;li&gt;Inconsistent wording for the same result (&amp;quot;delivery is marked as&amp;quot; and &amp;quot;screen where the first button is&amp;quot;).&lt;/li&gt;
&lt;li&gt;In Scenario 2, &amp;quot;the first button&amp;quot; is ambiguous, as the previous one (not the first in the UI) is meant.&lt;/li&gt;
&lt;li&gt;In Scenario 3, &amp;quot;I go back to the original screen&amp;quot; is extraneous and may be misinterpreted as a navigation.&lt;/li&gt;
&lt;li&gt;Similar scenarios with a few variables are more clearly expressed using Scenario Outlines.&lt;/li&gt;
&lt;li&gt;Specific text labels such as &amp;quot;In Progress&amp;quot; are clearer when in quotation marks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How could it be improved?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Background:&lt;/strong&gt; I am logged in as a delivery driver&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario Outline 1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIVEN&lt;/strong&gt; I view the details of a delivery&lt;br&gt;
&lt;strong&gt;AND&lt;/strong&gt; the delivery status is &amp;lt;current status&amp;gt;&lt;br&gt;
&lt;strong&gt;WHEN&lt;/strong&gt; I change the status to &amp;lt;desired status&amp;gt;&lt;br&gt;
&lt;strong&gt;AND&lt;/strong&gt; I select &amp;lt;dialog option&amp;gt;&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; the new status is &amp;lt;new status&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;current status&lt;/th&gt;
&lt;th&gt;desired status&lt;/th&gt;
&lt;th&gt;dialog option&lt;/th&gt;
&lt;th&gt;new status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;in progress&lt;/td&gt;
&lt;td&gt;Skip&lt;/td&gt;
&lt;td&gt;arrived&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;in progress&lt;/td&gt;
&lt;td&gt;Cancel&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;in progress&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;in progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;in progress&lt;/td&gt;
&lt;td&gt;arrived&lt;/td&gt;
&lt;td&gt;Cancel&lt;/td&gt;
&lt;td&gt;in progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;in progress&lt;/td&gt;
&lt;td&gt;arrived&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;arrived&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Example 3&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GIVEN&lt;/strong&gt; an additional notes section&lt;br&gt;
&lt;strong&gt;WHEN&lt;/strong&gt; I want to edit an existing note&lt;br&gt;
&lt;strong&gt;OR&lt;/strong&gt; add new information&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; I can remove or add new text of up to 250 characters or photos (no other types of attachment)&lt;br&gt;
&lt;strong&gt;AND&lt;/strong&gt; save my changes&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What works less well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The use of an OR (or of multiple ANDs) is a sign a scenario may be overloaded.&lt;/li&gt;
&lt;li&gt;There are a number of implicit behaviours which could use their own scenario(s).&lt;/li&gt;
&lt;li&gt;There are a number of implicit constraints which should be made explicit.&lt;/li&gt;
&lt;li&gt;There are missing preconditions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How could it be improved?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Background:&lt;/strong&gt; I view a work log as a logged in administrator&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WHEN&lt;/strong&gt; I edit text in the &amp;quot;Additional Notes&amp;quot; section&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; I can use a rich text WYSIWYG editor&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; It&#39;s best not to force a Given when you don&#39;t need more preconditions than the Background provides.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WHEN&lt;/strong&gt; I add text to the &amp;quot;Additional Notes&amp;quot; section&lt;br&gt;
&lt;strong&gt;AND&lt;/strong&gt; I save the section&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; I am given feedback that the changes were saved&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We should abstract away the actual text we add. We could also use property-based testing behind the scenes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 3&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WHEN&lt;/strong&gt; I type more text than 250 characters&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; I cannot add any more characters&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 4&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WHEN&lt;/strong&gt; I paste text of more than 250 characters&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; only the first 250 characters are pasted&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 5 Outline&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WHEN&lt;/strong&gt; I upload &amp;lt;photo or video&amp;gt; to the &amp;quot;Additional Notes&amp;quot; section&lt;br&gt;
&lt;strong&gt;THEN&lt;/strong&gt; I am given feedback that &amp;lt;something happened&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;photo or video&lt;/th&gt;
&lt;th&gt;something happened&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;a PNG with dimensions 1920x1080 and size 1MB&lt;/td&gt;
&lt;td&gt;Photo was saved. Preview is shown.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a PNG with dimensions 10000x10000 and size 10MB&lt;/td&gt;
&lt;td&gt;Message “Unable to upload file. You can upload valid photo, video, and PDF/XLSX files below 25MB.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a PDF&lt;/td&gt;
&lt;td&gt;PDF was saved. Preview is shown.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;an XLS(X)&lt;/td&gt;
&lt;td&gt;XLS(X) was saved. Preview is shown.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a DOC(X)&lt;/td&gt;
&lt;td&gt;Message “Unable to upload file. You can upload valid photo, video, and PDF/XLSX files below 25MB.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;an MP4 with size 10MB&lt;/td&gt;
&lt;td&gt;Video was saved. Preview is shown.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;an MP4 with size 50MB&lt;/td&gt;
&lt;td&gt;Message “Unable to upload file. You can upload valid photo, video, and PDF/XLSX files below 25MB.”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; actual permitted extensions, dimensions, and sizes may be different than this example.&lt;br&gt;
&lt;strong&gt;Note:&lt;/strong&gt; in reality you&#39;d likely want to use examples that account for boundary values and permutations.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Working with WebdriverIO | Issues you might encounter</title>
    <link href="https://wilcovanes.ch/articles/working-with-webdriverio-or-issues-you-might-encounter/"/>
    <updated>2020-04-28T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/working-with-webdriverio-or-issues-you-might-encounter/</id>
    <content type="html">&lt;p&gt;Examples of issues you might encounter:&lt;/p&gt;
&lt;h1&gt;This version of ChromeDriver only supports Chrome version X&lt;/h1&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/this-version-of-chromedriver-only-supports-chrome-version-x.png&quot; alt=&quot;This version of ChromeDriver only supports Chrome version X&quot;&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; the version of the &lt;code&gt;chromedriver&lt;/code&gt; Node package has to match the Chrome version on your machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check the Chrome version on your machine&lt;/li&gt;
&lt;li&gt;Open your &lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Under &lt;code&gt;devDependencies&lt;/code&gt;, change the &lt;code&gt;chromedriver&lt;/code&gt; version specified to your Chrome version (Example: if my PC has Chrome 83.0.4103.97, my &lt;code&gt;package.json&lt;/code&gt; needs &lt;code&gt;“chromedriver”: “^83.0.0”&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Execute &lt;code&gt;npm i chromedriver&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Now run your tests again&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Cannot use import statement outside a module&lt;/h1&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/cannot-use-import-statement-outside-a-module.png&quot; alt=&quot;Cannot use import statement outside a module&quot;&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; With ES6 you can create modules and import them from elsewhere in your application, but you do have to configure Babel correctly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure you have the Babel Node packages installed (&lt;code&gt;@babel/cli&lt;/code&gt;, &lt;code&gt;@babel/core&lt;/code&gt;, &lt;code&gt;@babel/preset-env&lt;/code&gt;, &lt;code&gt;@babel/register&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Ensure you’re requiring Babel in your &lt;code&gt;wdio.conf.js&lt;/code&gt;. In your &lt;code&gt;cucumberOpts&lt;/code&gt; (or the options for whatever framework you’re using): &lt;code&gt;requireModule: [ ‘@babel/register’ ],&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ensure you have a Babel config: &lt;code&gt;babel.config.js&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example contents for Node 12:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  presets: [
    [&#39;@babel/preset-env&#39;, {
      targets: {
        node: 12
      }
    }]
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Missing ) after argument list&lt;/h1&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/missing-parenthesis-after-argument-list.png&quot; alt=&quot;Missing parenthesis after argument list&quot;&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; not every test has been given a closing parenthesis and closing bracket.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; go through the test script and ensure every opening parenthesis and bracket has a partner.&lt;/p&gt;
&lt;h1&gt;Text not found despite being present in the DOM&lt;/h1&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/page-title-empty-though-page-has-title.png&quot; alt=&quot;Page title is empty though the page has a title&quot;&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; you’re checking the page title without visiting the page first.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; even if you add a &lt;code&gt;baseUrl&lt;/code&gt;, it won’t go there unless you do &lt;code&gt;browser.url(&#39;/&#39;)&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Working with WebdriverIO | Switching to Cucumber</title>
    <link href="https://wilcovanes.ch/articles/working-with-webdriverio-or-switching-to-cucumber/"/>
    <updated>2020-04-27T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/working-with-webdriverio-or-switching-to-cucumber/</id>
    <content type="html">&lt;p&gt;Cucumber is a BDD framework which adds another layer of abstraction (bad), but makes it easier to translate tickets to tests (good) and makes
acceptance tests readable by non-technical people before the functionality is even written (good if used that way).&lt;/p&gt;
&lt;p&gt;To switch to Cucumber, first make sure your tests run successfully in your current setup, so that if something goes wrong during or after the steps
below, you know the blame is likely with the steps taken rather than with issues in your pre-existing setup.&lt;/p&gt;
&lt;p&gt;If the tests still run successfully, let’s go ahead:&lt;/p&gt;
&lt;h1&gt;Install the Cucumber Node package&lt;/h1&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm i @wdio/cucumber-framework --save-dev&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Afterwards, if you look at your &lt;code&gt;package.json&lt;/code&gt;, you will see it lists &lt;code&gt;cucumber-framework&lt;/code&gt; alongside the other wdio packages, matching their
version.&lt;/p&gt;
&lt;h1&gt;Switch the framework configuration&lt;/h1&gt;
&lt;p&gt;To switch from the Mocha framework (which we had set up in &lt;a href=&quot;https://deploy-preview-39--xenodochial-wilson-8a829e.netlify.app/articles/working-with-webdriverio-or-writing-the-first-test/&quot;&gt;Working with WebdriverIO | Writing the first test&lt;/a&gt;) to the Cucumber framework:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open up your &lt;code&gt;wdio.conf.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Search for text fragment &lt;em&gt;framework:&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Change &lt;code&gt;mocha&lt;/code&gt; to &lt;code&gt;cucumber&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Set the Cucumber options&lt;/h1&gt;
&lt;p&gt;Add a new section to your &lt;code&gt;wdio.conf.js&lt;/code&gt; as follows.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//
// Options to be passed to Cucumber.
cucumberOpts: {
  require: [&#39;./test/steps/*.js&#39;],
  backtrace: false,
  dryRun: false,
  failFast: false, // Fail on first step, useful for debugging
  format: [&#39;pretty&#39;],
  snippets: true, // Show pending step suggestions
  ignoreUndefinedDefinitions: true // Treat undefined definitions as warnings
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It doesn’t really matter where you put it, but to be organised put it after &lt;code&gt;mochaOpts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Many of these options are, indeed, optional. I recommend adding them so they are made explicit. That way, when you ask yourself why something is happening the way it is, you don’t have to remember what Cucumber’s default options are, you can see them right in &lt;code&gt;wdio.conf.js&lt;/code&gt;. I’ve not added all of them in this example, only the few I feel could be useful to know about.&lt;/p&gt;
&lt;p&gt;Explanations and the fill list of options can be found at &lt;a href=&quot;https://webdriver.io/docs/configurationfile.html&quot;&gt;WebdriverIO’s Testrunner Configuration page&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Change the specs directory&lt;/h1&gt;
&lt;p&gt;You now have the step location in two places in your &lt;code&gt;wdio.conf.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;specs: [
  &#39;./test/specs/**/*.js&#39;
],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cucumberOpts: {
  require: [&#39;./test/steps/*.js&#39;],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can choose whatever location you like and match the folder and file structure to it.&lt;/p&gt;
&lt;p&gt;We will add features to a new features folder, so let’s change the specs setting to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;specs: [
&#39;./test/features/*.feature&#39;
],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when you use the test runner it would look for features. We don’t have any yet though.&lt;/p&gt;
&lt;h1&gt;Change the folder structure&lt;/h1&gt;
&lt;p&gt;You can use Cucumber with or without using the page object model (POM). Using page objects has the advantage of being able to abstract away
a page’s element selectors and custom functions into an intuitive structure, making it easier to stay up to date with a changing application. The
downsides are that it’s yet another layer of abstraction, making debugging slower, and that dividing an application into pages is increasingly
unrepresentative of modern applications.&lt;/p&gt;
&lt;p&gt;We’ll include POM. Let’s address our folder structure:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go into the &lt;code&gt;test&lt;/code&gt; folder. If you’ve followed along with the &lt;a href=&quot;https://deploy-preview-42--xenodochial-wilson-8a829e.netlify.app/articles/working-with-webdriverio-or-basic-setup/&quot;&gt;Basic setup&lt;/a&gt; article, it would only contain folder &lt;code&gt;specs&lt;/code&gt; at this point.&lt;/li&gt;
&lt;li&gt;Rename &lt;code&gt;specs&lt;/code&gt; to &lt;code&gt;steps&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create new empty folders and name them:
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;features&lt;/code&gt; (this is where we will define our scenarios)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pages&lt;/code&gt; (this is where we will define the page objects)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;support&lt;/code&gt; (this is where we could add helper functions generic to all pages)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Add a feature&lt;/h1&gt;
&lt;p&gt;Let’s add a scenario to drive the tests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In your IDE, navigate to the &lt;code&gt;features&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;Create a new feature file. For this guide, we can just call it &lt;code&gt;tutorial.feature&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add the basic keywords and spacing of a feature file:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;Feature:
  As
  I want to
  so

Background:
  Given

Scenario:
  When
  Then
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Explanation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Feature: what area of functionality are we covering and why do we need this functionality?&lt;/li&gt;
&lt;li&gt;Background: what conditions need to be in place before we can run our test?&lt;/li&gt;
&lt;li&gt;Scenario: what steps do we need to follow to verify the functional behaviour?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Writing the feature&lt;/h2&gt;
&lt;p&gt;There isn’t one right way, but here’s a suggestion. I will use a home automation web shop as example.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Feature:
  As a new user
  I want to find smart products that fit my living situation and needs
  so I can start the journey of making my home smarter

Background:
  Given I visit the 50five homepage

Scenario:
  When I search for the &amp;quot;Nest protect&amp;quot;
  Then I see search results
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see I’ve put “Nest protect” in quotation marks. This way we can feed that string as a variable to our tests. If you then change the string in
the feature file at a later time, you don’t have to update the test.&lt;/p&gt;
&lt;p&gt;Note also that we don’t use a generic “When I search for a product” and define a batch of products in a hidden helper function. Cucumber and
WebdriverIO are not meant to exhaustively test functionality, but rather to verify very specific example flows through the application as a layer of
acceptance tests on top of our cheaper more exhaustive unit and integration tests, as well as to serve as a tool for communicating and documenting clear requirements.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In your IDE, you can install extensions for Cucumber and JavaScript to get nice syntax highlighting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Rewrite the tests&lt;/h1&gt;
&lt;p&gt;If you have existing tests using the describe/it DSL, that needs to change now.&lt;/p&gt;
&lt;p&gt;We’ll first do it without POM. Then we’ll create a page object and refactor.&lt;/p&gt;
&lt;p&gt;Use or create &lt;code&gt;test/steps/tutorial.js&lt;/code&gt; and add this to the top of the file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Given, When, Then } from &#39;cucumber&#39;;

Given(/^I visit the 50five homepage$/, () =&amp;gt; {

})

When(/^I search for the (.*)$/, (keyword) =&amp;gt; {

})

Then(/^I see search results$/, () =&amp;gt; {

})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have existing code, comment it all out for now so it won’t be run but you can use it to put inside the Given/When/Then statements later. In
JavaScript, multiline commenting uses:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*
The thing you want to comment out.
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Given/When/Then syntax&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/^&lt;/code&gt; and &lt;code&gt;$/&lt;/code&gt;: These indicate the start and end of a regular expression (regex). The contents need to match the feature file exactly, except you can
use capturing groups to indicate variables.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(.*)&lt;/code&gt;: This is a capturing group. In this case, I’m saying &amp;quot;capture anything that’s in this position of the sentence”.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;() =&amp;gt; {}&lt;/code&gt;: This is an arrow function. It just means &amp;quot;go and do the stuff in brackets&amp;quot;. When I put &lt;code&gt;keyword&lt;/code&gt; in the parentheses, that just means that
the capturing group I used in the regex will be available as a variable.&lt;/p&gt;
&lt;h2&gt;Babel&lt;/h2&gt;
&lt;p&gt;At this point, the suite still won’t run properly. This is because I’m using JavaScript ES6 syntax. This includes a bunch of cool new features which
we’ll want to take advantage of. However, the browser can’t interpret it, so we’ll need to use a compiler. That’s where babel comes in.&lt;/p&gt;
&lt;p&gt;To start using babel:&lt;/p&gt;
&lt;p&gt;In your &lt;code&gt;package.json&lt;/code&gt;, add the following to the &lt;code&gt;devDependencies&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;@babel/cli&amp;quot;: &amp;quot;^7.6.4&amp;quot;,
&amp;quot;@babel/core&amp;quot;: &amp;quot;^7.6.4&amp;quot;,
&amp;quot;@babel/preset-env&amp;quot;: &amp;quot;^7.6.3&amp;quot;,
&amp;quot;@babel/register&amp;quot;: &amp;quot;^7.6.2&amp;quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now run &lt;code&gt;npm i&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once the packages are installed, add this to your &lt;code&gt;wdio.conf.js&lt;/code&gt; in the &lt;code&gt;cucumberOpts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;requireModule: [
  &#39;@babel/register&#39;
],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;cucumberOpts&lt;/code&gt; should now read:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//
// Options to be passed to Cucumber.
cucumberOpts: {
  require: [&#39;./test/steps/*.js&#39;],
  requireModule: [
    &#39;@babel/register&#39;
  ],
  backtrace: false,
  dryRun: false,
  failFast: false, // Fail on first step, useful for debugging
  format: [&#39;pretty&#39;],
  snippets: true, // Show pending step suggestions
  ignoreUndefinedDefinitions: true // Treat undefined definitions as warnings
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we just need a babel configuration file and we can get trucking. In the root of your test suite, create a new file &lt;code&gt;babel.config.js&lt;/code&gt;. Check
your node version via &lt;code&gt;node -v&lt;/code&gt; and add this to &lt;code&gt;babel.config.js&lt;/code&gt; (where 12 is replaced by whichever major Node version you’re using):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  presets: [
    [&#39;@babel/preset-env&#39;, {
      targets: {
        node: 12
      }
    }]
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, you can run the suite again!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm t&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This should return:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/wdio_cucumber_undefined.png&quot; alt=&quot;Output for Cucumber with undefined steps&quot;&gt;&lt;/figure&gt;
&lt;p&gt;With the right formatter, this will have pretty colours.&lt;/p&gt;
&lt;p&gt;Now let’s move our previous scenario into this new format. Example:&lt;/p&gt;
&lt;h2&gt;From&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import { Given, When, Then } from &#39;cucumber&#39;;

Given(/^I visit the 50five homepage$/, () =&amp;gt; {

})

When(/^I search for the (.*)$/, (keyword) =&amp;gt; {

})

Then(/^I see search results$/, () =&amp;gt; {

})

/*
describe(&#39;the 50five homepage&#39;, () =&amp;gt; {
  it(&#39;should show search results when using site search&#39;, () =&amp;gt; {
    browser.url(&#39;/&#39;)
    searchBar = $(&#39;input#search&#39;)
    searchBar.waitForDisplayed()
    searchBar.setValue(&#39;Nest protect&#39;)
    searchBar.keys(&amp;quot;&#92;uE007&amp;quot;)
    expect($(&#39;.search.results&#39;)).toBeDisplayed()
  })
})
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Given, When, Then } from &#39;cucumber&#39;;

Given(/^I visit the 50five homepage$/, () =&amp;gt; {
  browser.url(&#39;/&#39;)
})

When(/^I search for the (.*)$/, (keyword) =&amp;gt; {
  const searchBar = $(&#39;input#search&#39;)
  searchBar.waitForDisplayed()
  searchBar.setValue(keyword)
  searchBar.keys(&amp;quot;&#92;uE007&amp;quot;)
})

Then(/^I see search results$/, () =&amp;gt; {
  expect($(&#39;.search.results&#39;)).toBeDisplayed()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s run &lt;code&gt;npm t&lt;/code&gt; again!&lt;/p&gt;
&lt;p&gt;You should see:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/wdio_cucumber_steps.png&quot; alt=&quot;Output for Cucumber with defined steps&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Presto!&lt;/p&gt;
&lt;h1&gt;The page object model&lt;/h1&gt;
&lt;p&gt;Now a little bit of refactoring to start using the page object model.&lt;/p&gt;
&lt;p&gt;In your &lt;code&gt;test/pages&lt;/code&gt; folder, create a new file called &lt;code&gt;home.js&lt;/code&gt;. This page object can be used to define a generic page with selectors you want to
be able to use anywhere. You could do the same for functions, but we’ll leave that for the &lt;code&gt;support&lt;/code&gt; folder helper functions and just have a
generic page visiting method here.&lt;/p&gt;
&lt;p&gt;Start with this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&#39;use strict&#39;

class HomePage {

/**
* Selectors
*/

/**
* Methods
*/
}

export default new HomePage()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we define a page object this way, it means we can call it from other page objects to use anything defined in it (in this case we’d do so using
&lt;code&gt;import HomePage from ‘../pages/home’)&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you expand the test suite and add specific page objects for discrete sections or pages (for example: &lt;code&gt;pages&#92;search.js&lt;/code&gt;, you’ll need to refactor this a tiny bit to where you instantiate a class from those specific page objects and let those page objects import from the generic page object, instead of what we do now which is instantiate a class from the generic page object directly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, let’s think about our sample scenario. It has 2 selectors, one for the search bar and one for the search results. These are not specific to a
page, so we can define them in the main page object. Let’s do that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&#39;use strict&#39;

class HomePage {

  /**
  * Selectors
  */

  get searchBar() { return $(&#39;input#search&#39;) }
  get searchResults() { return $(&#39;.search.results&#39;) }

  /**
  * Methods
  */

  open(path) {
    browser.url(path)
  }
}

export default new HomePage()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can use those selectors anywhere we import this page object. So let’s import the page object.&lt;/p&gt;
&lt;p&gt;Go into &lt;code&gt;tutorial.js&lt;/code&gt; and underneath the Given/When/Then import add:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import HomePage from &#39;../pages/home&#39;;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We can now use the search selectors we defined:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Given, When, Then } from &#39;cucumber&#39;
import HomePage from &#39;../pages/home&#39;

Given(/^I visit the 50five homepage$/, () =&amp;gt; {
  HomePage.open(&#39;/&#39;)
})

When(/^I search for the (.*)$/, (keyword) =&amp;gt; {
  HomePage.searchBar.waitForDisplayed()
  HomePage.searchBar.setValue(keyword)
  HomePage.searchBar.keys(&amp;quot;&#92;uE007&amp;quot;)
})

Then(/^I see search results$/, () =&amp;gt; {
  expect(HomePage.searchResults).toBeDisplayed()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run &lt;code&gt;npm t&lt;/code&gt; and you should end up with the same test output as before.&lt;/p&gt;
&lt;p&gt;Now we are in a land of beauty and sunshine.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Working with WebdriverIO | Find the right selector to use</title>
    <link href="https://wilcovanes.ch/articles/working-with-webdriverio-or-find-the-right-selector-to-use/"/>
    <updated>2020-04-24T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/working-with-webdriverio-or-find-the-right-selector-to-use/</id>
    <content type="html">&lt;p&gt;You could of course do the following (not recommended):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the page you’re going to test in a browser&lt;/li&gt;
&lt;li&gt;Right-click the element on the page -&amp;gt; &lt;strong&gt;Inspect&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Right-click the element in the HTML -&amp;gt; &lt;strong&gt;Copy&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Copy selector&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you paste it, you get something like:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#maincontent &amp;gt; div.columns &amp;gt; div &amp;gt; div:nth-child(15) &amp;gt; div &amp;gt; div &amp;gt; div:nth-child(6) &amp;gt; figure &amp;gt; a &amp;gt; img. pagebuilder-mobile-hidden&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Cool. Theoretically you could use this. It’s a unique reference to the element.&lt;/p&gt;
&lt;p&gt;It’s also horribly long and unwieldy.&lt;/p&gt;
&lt;p&gt;What you want is the shortest possible unique reference. The easiest way to get it is through using the element’s &lt;code&gt;id&lt;/code&gt; tag. An id should always be
unique. Example: &lt;code&gt;$(#login)&lt;/code&gt;. The &lt;code&gt;#&lt;/code&gt; symbol is how you select by id. However, many elements will not have an id tag defined. In those cases
you can:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a custom data attribute defined by the developers. Example: &lt;code&gt;$(&#39;[data-test=&amp;quot;search&amp;quot;]&#39;)&lt;/code&gt;. This would require it to be added to the source code first. You can ask a developer or submit an MR yourself.&lt;/li&gt;
&lt;li&gt;Use the smallest selector chain that will give you the unique element.
&lt;ol&gt;
&lt;li&gt;Maybe your element is the first element on the page with the class product-item. You could use &lt;code&gt;$(&#39;.product-item&#39;)&lt;/code&gt;. This would select the first element in the DOM with the class &lt;code&gt;product-item&lt;/code&gt;. Classes are not unique, but if we know where it sits in the DOM we can still get it this way.&lt;/li&gt;
&lt;li&gt;Maybe your element is the second element on the page with the class &lt;code&gt;product-item&lt;/code&gt;. You could use &lt;code&gt;$$(&#39;.product-item&#39;)[1]&lt;/code&gt;. Note the &lt;code&gt;$$&lt;/code&gt;. We are selecting all elements with class &lt;code&gt;product-item&lt;/code&gt; instead of the first one in the DOM. Then of those elements we’re selecting the second one. It’s a zero-indexed array, which means that 0 would select the first item and 1 would select the second.&lt;/li&gt;
&lt;li&gt;Maybe this is all Chinese to you. In that case here’s a simple trick for you. You could take the absurdly long selector you got from the DOM (&lt;code&gt;#maincontent &amp;gt; div.columns &amp;gt; div &amp;gt; div:nth-child(15) &amp;gt; div &amp;gt; div &amp;gt; div:nth-child(6) &amp;gt; figure &amp;gt; a &amp;gt; img.pagebuilder-mobile-hidden&lt;/code&gt;) and simply remove all elements except the last one and put them back in one by one until they lead to a unique element.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Working with WebdriverIO | Debugging tests</title>
    <link href="https://wilcovanes.ch/articles/working-with-webdriverio-or-debugging-tests/"/>
    <updated>2020-04-23T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/working-with-webdriverio-or-debugging-tests/</id>
    <content type="html">&lt;p&gt;You’ve written a test, you run it, you think it should work, but it doesn’t. What can you do?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pause browser execution&lt;/li&gt;
&lt;li&gt;Inspect variables and elements&lt;/li&gt;
&lt;li&gt;Manually run the steps&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/debugging_tactics.jpg&quot; alt=&quot;Debugging tactics: run the same code again, hope it magically works now&quot;&gt;&lt;/figure&gt;
&lt;h1&gt;Pause browser execution&lt;/h1&gt;
&lt;p&gt;An easy way to debug is to run the test with a pause in it, so you can view whether an element is really visible, you can inspect the element, you
can see if an element is actually overlapped, etc.&lt;/p&gt;
&lt;p&gt;You simply add &lt;code&gt;browser.pause(30000)&lt;/code&gt; on a line above the command that’s not doing what you want it to do, and run the test. You can replace &lt;code&gt;30000&lt;/code&gt; with however many milliseconds you think you’ll need.&lt;/p&gt;
&lt;h1&gt;Inspect variables and elements&lt;/h1&gt;
&lt;p&gt;You can use &lt;code&gt;console.log()&lt;/code&gt; to show the contents of variables and other useful things whilst it’s running through the test steps. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;console.log(searchBar)&lt;/code&gt; shows what’s in variable &lt;code&gt;searchBar&lt;/code&gt; if it has been defined. If the variable represents a selector, it will also output
what the selector is defined as, and all the properties of the browser object including session id and capabilities.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;console.log(typeof searchBar)&lt;/code&gt; shows what the datatype is of variable &lt;code&gt;searchBar&lt;/code&gt;. If the variable represents a selector, it should output
&lt;code&gt;object&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This output will get lost in all the other output if you use the default log level (which is very verbose). To change that, go into your &lt;code&gt;wdio.conf.js&lt;/code&gt;
and change the &lt;code&gt;logLevel&lt;/code&gt; setting from &lt;code&gt;info&lt;/code&gt; (or whatever you have it set to) to &lt;code&gt;error&lt;/code&gt;. Now on running tests you’ll see only the test output, the
most important potential problems, and your console.log output.&lt;/p&gt;
&lt;h1&gt;Manually run the steps&lt;/h1&gt;
&lt;p&gt;You could open your browser to simulate the steps you’ve written, but it wouldn’t be the same as running it within a chromedriver instance via
WebDriver and with the &lt;code&gt;wdio.conf.js&lt;/code&gt; capabilities. Good news! We can do the latter manually as well.&lt;/p&gt;
&lt;p&gt;Open your &lt;code&gt;package.json&lt;/code&gt;. The &lt;code&gt;scripts&lt;/code&gt; section probably looks something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;test&amp;quot;: &amp;quot;wdio wdio.conf.js&amp;quot;,
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change it to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;test&amp;quot;: &amp;quot;wdio wdio.conf.js&amp;quot;,
  &amp;quot;debug&amp;quot;: &amp;quot;wdio repl chrome&amp;quot;
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means that in the console we can run &lt;code&gt;npm run debug&lt;/code&gt; and it will open a chromedriver instance which is configured and run exactly as it
would run during our tests. The beautiful thing is you can now run wdio commands in your console shell. For example, try &lt;code&gt;browser.url (&#39;https://www.google.co.uk&#39;)&lt;/code&gt;. You can run all your test steps against the browser instance that opens.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Working with WebdriverIO | How to recognise a good test</title>
    <link href="https://wilcovanes.ch/articles/working-with-webdriverio-or-how-to-recognise-a-good-test/"/>
    <updated>2020-04-22T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/working-with-webdriverio-or-how-to-recognise-a-good-test/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;For now, this guide will be for Windows specifically. Later on it will list alternative Unix-style commands (e.g. for WSL, Ubuntu, and
macOS).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let’s take this (overly) simple example:&lt;/p&gt;
&lt;h1&gt;WebdriverIO 6 with expect-webdriver&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;it(&#39;should include 50five in the page title&#39;, () =&amp;gt; {
  browser.url(&#39;/&#39;)
  expect(browser.getTitle()).to.include(&#39;50five&#39;)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;WebdriverIO 5 with Chai&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;it(&#39;should include 50five in the page title&#39;, () =&amp;gt; {
  browser.url(&#39;/&#39;)
  expect(browser).toHaveTitle(&#39;50five&#39;, { containing: true })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I run it, the test passes.&lt;/p&gt;
&lt;p&gt;Does that mean we’re done?&lt;/p&gt;
&lt;p&gt;I consider the shortest possible criteria for a good test to be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Is it &lt;strong&gt;meaningful&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Does it pass &lt;strong&gt;when it should pass&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Does it fail &lt;strong&gt;when it should fail&lt;/strong&gt;?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The example test passes. Should it pass? When I manually check the page, the page title indeed contains ‘50five’, so it should pass.&lt;/p&gt;
&lt;p&gt;Does it fail when it should fail? To verify this, I can edit the HTML in my local browser instance. I would have to stop test execution, edit the HTML in the browser, and then let the test run on the now incorrect page title.&lt;/p&gt;
&lt;p&gt;First add a pause to the test, to be placed after the page to be tested is loaded:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;it(&#39;should include 50five in the page title&#39;, () =&amp;gt; {
  browser.url(&#39;/&#39;)
  browser.pause(30000)
  expect(browser.getTitle()).to.include(&#39;50five&#39;)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;I’ve assumed here that 30 seconds will be enough to make the change, but you can give yourself more time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once the page is loaded, we should:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the browser console (&lt;strong&gt;F12&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Ensure we’re on the &lt;strong&gt;Elements&lt;/strong&gt; (Chrome, Edge) or &lt;strong&gt;Inspector&lt;/strong&gt; (Firefox) tab&lt;/li&gt;
&lt;li&gt;Find the title tag (&lt;strong&gt;CTRL + F&lt;/strong&gt; &lt;em&gt;title&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Remove &lt;em&gt;| 50five.nl&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Select a different element, which will save the current one&lt;/li&gt;
&lt;li&gt;Wait for the test to run&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Expected: the test fails, and it gives useful feedback (e.g. “found X instead of Y”).
Observed:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/wdio_failed_test.png&quot; alt=&quot;Output for a failed test&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Sweet. This is what we want to see. You can now remove the pause from the test.&lt;/p&gt;
&lt;p&gt;So, this means the criterion of failing when it should fail has passed.&lt;/p&gt;
&lt;p&gt;The final criterion is: is it a meaningful test? How much confidence does it give us when we see it passes? When it fails, does that tell us about a
real issue? Does the error message allow us to easily find the root cause? Taken one by one:&lt;/p&gt;
&lt;p&gt;Confidence in the code: well, when the test passes it only really tells us that the homepage is still accessible. This is good to know, &lt;em&gt;but&lt;/em&gt; this would be more effectively monitored through uptime monitoring tools.&lt;/p&gt;
&lt;p&gt;A real issue is identified: not really. This is because the most likely cause of the page title not matching (aside from the site being down) is that a
colleague deliberately improved it. At most it is possible that when trying to improve part of the page title, they accidentally removed the “50five”
part, but in modern browsers the latter part of the page title is likely obscured in the browser tab anyway.&lt;/p&gt;
&lt;p&gt;Makes debugging easy: it tells us &lt;em&gt;what&lt;/em&gt; is wrong and &lt;em&gt;where&lt;/em&gt;. The test is so simple and specific that it does not hide a different issue. This is good.
Overall, the only thing this test has got going for it is that it’s easy to debug. We wouldn’t miss anything of value if we &lt;em&gt;didn’t&lt;/em&gt; have this test. Since
every test takes &lt;strong&gt;time to execute&lt;/strong&gt; and every non-meaningful passing test could create &lt;strong&gt;false confidence&lt;/strong&gt; in the application as a whole, this test
should be removed.&lt;/p&gt;
&lt;p&gt;What would be a more meaningful test?&lt;/p&gt;
&lt;p&gt;Let’s check whether you can do a search using the header site search. This is commonly used by visitors of the site &lt;em&gt;and&lt;/em&gt; takes them towards
products and therefore hopefully towards adding them to their cart.&lt;/p&gt;
&lt;p&gt;This is how we could do it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;describe(&#39;the 50five homepage&#39;, () =&amp;gt; {
  it(&#39;should show search results when using site search&#39;, () =&amp;gt; {
    browser.url(&#39;/&#39;)
    searchBar = $(&#39;input#search&#39;)
    searchBar.waitForDisplayed()
    searchBar.setValue(&#39;Nest protect&#39;)
    searchBar.keys(&amp;quot;&#92;uE007&amp;quot;)
    expect($(&#39;.search.results&#39;)).toBeDisplayed
  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Translation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Browser object, go to the &lt;code&gt;baseUrl&lt;/code&gt; at its root.&lt;/li&gt;
&lt;li&gt;Find the input field for the site search, and add it to a variable &lt;code&gt;searchBar&lt;/code&gt; so we can use it later&lt;/li&gt;
&lt;li&gt;Make sure the input field is visible, so we can interact with it&lt;/li&gt;
&lt;li&gt;Fill the input field with the search query &lt;em&gt;Nest protect&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Send the Enter key to the input field, which submits it&lt;/li&gt;
&lt;li&gt;Check whether the search results grid is displayed, which should be the case on the search results page&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;With input fields you can also find the submit button and click that. In this example I chose to submit the Enter key, making the
assumption that this is the more common way for a user to submit their search query and because we’re not worried about mobile here.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;npm t&lt;/code&gt;. See it pass when it should pass.&lt;/p&gt;
&lt;p&gt;Comment out &lt;code&gt;searchBar.keys(&amp;quot;&#92;uE007&amp;quot;)&lt;/code&gt; (by adding &lt;code&gt;//&lt;/code&gt; in front of that line). See it fail when it should fail.&lt;/p&gt;
&lt;p&gt;Is it meaningful? Well, if it passes it tells us that critical functionality is working. Great. If it fails it tells us that either the site search is not present, or it cannot be filled, or it cannot be submitted, or it does not lead to search results. This is great to know about! However, it’s not perfect, since we’ll have to find out which one it is, but on the other hand it does tell us this in the results. For example:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/wdio_failed_test_better.png&quot; alt=&quot;Output for failing to find search results&quot;&gt;&lt;/figure&gt;
&lt;p&gt;This shows you can submit a search, but the results are not displayed (or of course it could always be the case that someone or something
renamed the element selector on the page).&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/wdio_failed_test_alternative.png&quot; alt=&quot;Output for failing to find search bar&quot;&gt;&lt;/figure&gt;
&lt;p&gt;This shows there’s no site search on the page. (To make it easier to simulate this, I just looked for the wrong selector. I could have also just
renamed the element in the HTML whilst the test was running.)&lt;/p&gt;
&lt;p&gt;In conclusion, we now have a better test, using the criteria provided and by testing our tests!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Working with WebdriverIO | Writing the first test</title>
    <link href="https://wilcovanes.ch/articles/working-with-webdriverio-or-writing-the-first-test/"/>
    <updated>2020-04-21T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/working-with-webdriverio-or-writing-the-first-test/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;For now, this guide will be for Windows specifically. Later on it will list alternative Unix-style commands (e.g. for WSL, Ubuntu, and
macOS).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If within your Node project directory you do &lt;code&gt;npm list @wdio/cli&lt;/code&gt;, what’s the first number after &lt;code&gt;-- @wdio/cli@&lt;/code&gt; in the output? If it’s 6, see
the instructions for WebdriverIO 6 below. If it’s 5, see the instructions for WebdriverIO 5 below.&lt;/p&gt;
&lt;h1&gt;WebdriverIO 6&lt;/h1&gt;
&lt;p&gt;WebdriverIO 6 bundles its own native assertion library, &lt;code&gt;expect-webdriverio&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Configure expect-webdriverio&lt;/h2&gt;
&lt;p&gt;The only thing you have to do to activate it is the following.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open up your &lt;code&gt;wdio.conf.js&lt;/code&gt; in your IDE&lt;/li&gt;
&lt;li&gt;Uncomment the &lt;code&gt;beforeSession&lt;/code&gt; section&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;require(&#39;expect-webdriverio&#39;)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So it becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;beforeSession: function (config, capabilities, specs) {
  require(&#39;expect-webdriverio&#39;)
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. You don’t need to call it within test files, it will get called at the start of each session.&lt;/p&gt;
&lt;h2&gt;Write a test&lt;/h2&gt;
&lt;p&gt;Let’s start as simply as we possibly can. For my examples I will use https://www.50five.nl as my test page.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;describe(&#39;the 50five homepage&#39;, () =&amp;gt; {
  it(&#39;should have a pending test&#39;, () =&amp;gt; {

  })

  it(&#39;should include 50five in the page title&#39;, () =&amp;gt; {
    browser.url(&#39;/&#39;)
    expect(browser).toHaveTitle(&#39;50five&#39;, { containing: true })
  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Translation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Browser object, go to the &lt;code&gt;baseUrl&lt;/code&gt; at its root.&lt;/li&gt;
&lt;li&gt;Tell me whether the value in the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag of the current browser tab contains the substring &lt;code&gt;50five&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Run &lt;code&gt;npm t&lt;/code&gt;. The result should be something like:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/wdio_first_test_expect.png&quot; alt=&quot;Output for a simple wdio test using expect-webdriver&quot;&gt;&lt;/figure&gt;
&lt;h1&gt;WebdriverIO 5&lt;/h1&gt;
&lt;p&gt;By default, WebdriverIO 5 uses &lt;strong&gt;Mocha&lt;/strong&gt;&#39;s assertion library. Mocha&#39;s default assertion module only supports the functions listed on the &lt;a href=&quot;https://nodejs.org/api/assert.html&quot;&gt;Node.js
Assertion Testing page&lt;/a&gt;, which is very limited. We will definitely want to change this, so we can assert in more ways and more elegantly.&lt;/p&gt;
&lt;p&gt;A nice way to do this is with &lt;strong&gt;Chai&lt;/strong&gt;’s ‘expect’ assertions.&lt;/p&gt;
&lt;h2&gt;Install Chai&lt;/h2&gt;
&lt;p&gt;We’ll need to install the Mocha adapter for WebdriverIO, and then the Chai package. Make sure you’re in your project folder, then run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm i --save-dev @wdio/mocha-framework&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm i --save-dev chai&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now let’s get it configured.&lt;/p&gt;
&lt;h2&gt;Configure Chai&lt;/h2&gt;
&lt;p&gt;In your IDE, open up &lt;code&gt;wdio.conf.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Find this commented out section:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// before: function (capabilities, specs) {
// },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uncomment it, so it becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;before: function (capabilities, specs) {
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As it explains right above that section, this gets executed before test execution begins. There are also other commented out sections where you could
define what should run before each test, after each test, or what should run before a session is initialised, etc. We won’t bother with those now.&lt;/p&gt;
&lt;p&gt;Now, let’s call Chai in this &lt;code&gt;before&lt;/code&gt; section:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;before: function (capabilities, specs) {
  var chai = require(&#39;chai&#39;);
  global.expect = chai.expect;
  chai.Should();
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save the file.&lt;/p&gt;
&lt;p&gt;Now open up your test file. If you’re following on from &lt;a href=&quot;https://deploy-preview-35--xenodochial-wilson-8a829e.netlify.app/articles/working-with-webdriverio-or-basic-setup/&quot;&gt;Working with WebdriverIO | Basic setup&lt;/a&gt;, this will be &lt;code&gt;tutorial.js&lt;/code&gt; and it will contain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const assert = require(&#39;assert&#39;);

describe(&#39;the 50five homepage&#39;, () =&amp;gt; {
  it(&#39;should have a pending test&#39;, () =&amp;gt; {

  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change this to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var expect = require(&#39;chai&#39;).expect

describe(&#39;the 50five homepage&#39;, () =&amp;gt; {
  it(&#39;should have a pending test&#39;, () =&amp;gt; {

  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is still an empty test, but we want to know Chai is working right before trying anything else:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm t&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The output should still be 1 passing test.&lt;/p&gt;
&lt;h2&gt;Write a test&lt;/h2&gt;
&lt;p&gt;Okay, now we can get to business.&lt;/p&gt;
&lt;p&gt;Let’s start as simply as we possibly can:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var expect = require(&#39;chai&#39;).expect

describe(&#39;the 50five homepage&#39;, () =&amp;gt; {
  it(&#39;should have a pending test&#39;, () =&amp;gt; {
})

it(&#39;should include 50five in the page title&#39;, () =&amp;gt; {
  browser.url(&#39;/&#39;)
  expect(browser.getTitle()).to.include(&#39;50five&#39;)
  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Translation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Browser object, go to the &lt;code&gt;baseUrl&lt;/code&gt; at its root.&lt;/li&gt;
&lt;li&gt;Browser object, get the value in the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag&lt;/li&gt;
&lt;li&gt;Tell me whether the value in the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag contains the substring &lt;code&gt;50five&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Run &lt;code&gt;npm t&lt;/code&gt;. The result should be something like:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/wdio_first_test_chai.png&quot; alt=&quot;Output for a simple test using Chai&quot;&gt;&lt;/figure&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;That’s the first real test done!&lt;/p&gt;
&lt;p&gt;Is it a useful test? Not really. We’ll work on that.&lt;/p&gt;
&lt;p&gt;In other articles we’ll look at:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How do you test interesting things like login, search, interacting with overlays?&lt;/li&gt;
&lt;li&gt;How do you switch from Chai to using Cucumber, if so desired?&lt;/li&gt;
&lt;li&gt;How do you debug problematic tests?&lt;/li&gt;
&lt;li&gt;How do you determine what to test and how to test, what are common pitfalls and solutions?&lt;/li&gt;
&lt;li&gt;How do you tell a bad test from a good test?&lt;/li&gt;
&lt;li&gt;How do you create a repo for the tests and work with that repo?&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Working with WebdriverIO | Basic setup</title>
    <link href="https://wilcovanes.ch/articles/working-with-webdriverio-or-basic-setup/"/>
    <updated>2020-04-20T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/working-with-webdriverio-or-basic-setup/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;For now, this guide will be for Windows specifically. Later on it will list alternative Unix-style commands (e.g. for WSL, Ubuntu, and
macOS).&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Prerequisites&lt;/h1&gt;
&lt;p&gt;To get started with WebdriverIO, we first need an IDE (development environment), Node, and Java.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Choose and install an IDE (recommended: &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;vscode / Visual Studio Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Install Node.js (check if you have it via &lt;code&gt;node -v&lt;/code&gt; in a command prompt)&lt;/li&gt;
&lt;li&gt;Install the Java Development Kit (JDK) (check if you have it via &lt;code&gt;java -version&lt;/code&gt; in a command prompt)&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Create a test project&lt;/h1&gt;
&lt;p&gt;Let’s create a folder to work in and initialise a new Node project:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Decide where to place your project (example: &lt;code&gt;C:&#92;Node&lt;/code&gt;) and go there in a command prompt: &lt;code&gt;cd&#92;node&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Decide a project name (example: &lt;code&gt;wdio-sandbox&lt;/code&gt;) and create it: &lt;code&gt;mkdir wdio-sandbox &amp;amp;&amp;amp; cd wdio-sandbox&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Initialise the project: &lt;code&gt;npm init&lt;/code&gt;
&lt;ol&gt;
&lt;li&gt;Package name: just press Enter&lt;/li&gt;
&lt;li&gt;Version: just press Enter&lt;/li&gt;
&lt;li&gt;Description: &lt;code&gt;Sandbox for playing around with WebdriverIO&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enter point: just press Enter&lt;/li&gt;
&lt;li&gt;Test command: &lt;code&gt;wdio wdio.conf.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Git repository: just press Enter&lt;/li&gt;
&lt;li&gt;Keywords: just press Enter&lt;/li&gt;
&lt;li&gt;Author: enter your first and last name&lt;/li&gt;
&lt;li&gt;License: just press Enter&lt;/li&gt;
&lt;li&gt;“Is this OK?”: just press Enter&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;If you make a mistake, you can start over entirely by using &lt;code&gt;rm -r wdio-sandbox&lt;/code&gt; to delete the directory and all its contents. This is a
command to be careful with.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now open the folder you created in your IDE.&lt;/p&gt;
&lt;p&gt;You should see it only includes a &lt;code&gt;package.json&lt;/code&gt; file at this point.&lt;/p&gt;
&lt;h1&gt;Install WebdriverIO&lt;/h1&gt;
&lt;p&gt;Now we install WebdriverIO itself, specifically the command line tools (cli) including the test runner.&lt;/p&gt;
&lt;p&gt;Switch back to your command prompt and execute:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm i --save-dev @wdio/cli&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Translation: &lt;em&gt;Node package manager, install for me the @wdio/cli package and add it to the devDependencies section of package.json&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let&#39;s check if it was successful:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.&#92;node_modules&#92;.bin&#92;wdio --version&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If you get &lt;code&gt;&#39;.&#39; is not recognized as an internal or external command, operable program or batch file.&lt;/code&gt;, ensure you’re using forward slashes.&lt;/p&gt;
&lt;p&gt;It should now return the version number of the wdio cli tools. This should also be shown in the package.json under devDependencies.&lt;/p&gt;
&lt;h1&gt;Configure WebdriverIO&lt;/h1&gt;
&lt;p&gt;Now we generate the wdio configuration file which we can then manually change.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.&#92;node_modules.bin&#92;wdio config -y&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Open it up. Most interesting at this point are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;specs&lt;/code&gt; - Defines the location of your tests. You can put tests in this path once we start creating them, or change the path if you&#39;d like to structure things differently.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;capabilities&lt;/code&gt; - Here we define the browser(s) we&#39;ll use to run the tests (Chrome, Firefox, and/or Internet Explorer).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;baseUrl&lt;/code&gt; - The domain to run the tests on, so you don&#39;t have to include it for every url you visit.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;framework / mochaOpts&lt;/code&gt; - Here we could set the assertion framework and BDD framework options, which will become relevant very quickly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For now, just change the baseUrl value from &lt;code&gt;http://localhost&lt;/code&gt; to any public url of your choosing. You can change this later when you&#39;re writing tests for a
different domain.&lt;/p&gt;
&lt;h1&gt;Create a test file&lt;/h1&gt;
&lt;p&gt;In your IDE, create a folder structure matching what you have in &lt;code&gt;wdio.conf.js&lt;/code&gt; under &lt;code&gt;specs&lt;/code&gt;. So if you kept the default, create a folder &lt;code&gt;test&lt;/code&gt;
and within it a folder &lt;code&gt;specs&lt;/code&gt;. Within that, we can add test files. Create a file called &lt;code&gt;tutorial.js&lt;/code&gt; within the &lt;code&gt;specs&lt;/code&gt; folder.&lt;/p&gt;
&lt;h1&gt;Run a test file&lt;/h1&gt;
&lt;p&gt;We can already try running the test suite now. What would we expect? We&#39;d expect the test suite to run successfully, with 1 passed test (because it will count our empty test file without any tests as not having any failed assertions and therefore passing).&lt;/p&gt;
&lt;p&gt;In a command prompt, run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm t&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This is short for &lt;code&gt;npm test&lt;/code&gt;, which will run whatever you have defined in &lt;code&gt;package.json&lt;/code&gt; under &lt;code&gt;scripts&lt;/code&gt; -&amp;gt; &lt;code&gt;test&lt;/code&gt;. Earlier we defined this as part of the &lt;code&gt;npm init&lt;/code&gt; process. If we hadn&#39;t done this, we would have to run tests by typing &lt;code&gt;.&#92;node_modules.bin&#92;wdio wdio.conf.js&lt;/code&gt; every time. Later on we&#39;ll add another useful command to scripts to help solidify this notion of custom npm commands.
As output you see something like the following.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/wdio_no_tests.png&quot; alt=&quot;Output when there is no test yet&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Now, let’s add an empty test to the test file. Open &lt;code&gt;tutorial.js&lt;/code&gt; in your IDE and add:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const assert = require(&#39;assert&#39;);

describe(&#39;our test page&#39;, () =&amp;gt; {
  it(&#39;should have a pending test&#39;, () =&amp;gt; {

  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save it.&lt;/p&gt;
&lt;p&gt;Switch back to a command prompt and run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm t&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The output should now result in something like:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/wdio_empty_test.png&quot; alt=&quot;Output when there is one empty test&quot;&gt;&lt;/figure&gt;
&lt;p&gt;You can see the test runner still counts the test file as not having any failed assertions and therefore passing. That&#39;s one to look out for once you&#39;ve got a test suite going, as you might think a test is passing whereas in reality it doesn&#39;t have any assertions.&lt;/p&gt;
&lt;p&gt;We now have a development environment in place and an empty test.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Finding a Cloudflare event by Ray ID</title>
    <link href="https://wilcovanes.ch/articles/finding-a-cloudflare-event-by-ray-id/"/>
    <updated>2020-03-11T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/finding-a-cloudflare-event-by-ray-id/</id>
    <content type="html">&lt;p&gt;When one of your legitimate users gets blocked by Cloudflare, they will be quoted a Ray ID which you can use to look up the exact rule that blocked them. You can then choose to change the Block to a Challenge (CAPTCHA) or to Disable it altogether.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/cloudflare-block.png&quot; alt=&quot;Blocked by Cloudflare firewall page&quot;&gt;&lt;/figure&gt;
&lt;h3&gt;Search a Cloudflare event by Ray ID&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Log in to &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Navigate to the website you&#39;d like to manage access for&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Firewall&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Firewall Events&lt;/strong&gt;, hit &lt;strong&gt;Add filter&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;From the &lt;strong&gt;New filter&lt;/strong&gt; dropdowns, select &lt;strong&gt;Ray ID&lt;/strong&gt;, &lt;strong&gt;equals&lt;/strong&gt;, and the user&#39;s Ray ID&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Apply&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should now be seeing the &lt;strong&gt;Activity log&lt;/strong&gt; filtered down to 1 event.&lt;/p&gt;
&lt;h3&gt;Investigate the Cloudflare event&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Select the event&lt;/li&gt;
&lt;li&gt;Copy the value for &lt;strong&gt;Rule ID&lt;/strong&gt;, e.g. &lt;em&gt;100173&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Managed Rules&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Cloudflare Managed Ruleset&lt;/strong&gt;, hit &lt;strong&gt;Advanced&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;In the search bar, submit a search query for the Rule ID you copied&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should now be seeing the rules filtered down to 1 rule. You can view its description — to help you understand why the block occurred — and change the &lt;strong&gt;Mode&lt;/strong&gt; to decide what should happen when a user (whether they be well-meaning or hostile) encounters it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; if you can&#39;t find the rule in the ruleset &lt;strong&gt;Cloudflare Managed Ruleset&lt;/strong&gt;, try the other rulesets in the &lt;strong&gt;Managed Rules&lt;/strong&gt; section.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Manage merge rights in GitLab</title>
    <link href="https://wilcovanes.ch/articles/manage-merge-rights-in-gitlab/"/>
    <updated>2020-01-21T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/manage-merge-rights-in-gitlab/</id>
    <content type="html">&lt;p&gt;To prevent all users from being able to push a deployment to a production or acceptance environment, GitLab uses the concept of protected branches.&lt;/p&gt;
&lt;h1&gt;Protected branches&lt;/h1&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/gitlab-protected-branches.png&quot; alt=&quot;Example of protected branches&quot;&gt;&lt;/figure&gt;
&lt;p&gt;This example means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Everyone with at least &lt;strong&gt;Developer&lt;/strong&gt; permissions can create and/or approve a merge request to the &lt;strong&gt;release&lt;/strong&gt; branch.&lt;/li&gt;
&lt;li&gt;Only those with at least &lt;strong&gt;Maintainer&lt;/strong&gt; permissions can create and/or approve a merge request to the &lt;strong&gt;master&lt;/strong&gt; branch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more information on roles, see &lt;a href=&quot;https://wilcovanes.ch/articles/manage-access-to-gitlab-projects/&quot;&gt;Manage access to GitLab projects&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Set permissions for an existing protected branch&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;In GitLab, go to the repo you want to set branch permissions for&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Repository&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Expand the &lt;strong&gt;Protected Branches&lt;/strong&gt; section&lt;/li&gt;
&lt;li&gt;Now you can:
&lt;ol&gt;
&lt;li&gt;Select a different role tier for who is allowed to merge to a certain branch&lt;/li&gt;
&lt;li&gt;Select a different role tier for who is allowed to push to a certain branch&lt;/li&gt;
&lt;li&gt;Select whether the branch should be protected at all&lt;/li&gt;
&lt;li&gt;Select whether a code owner should approve any MR (note that if you were to use this, the code owners have to be configured separately)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Changing an existing branch to a protected branch&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;In GitLab, go to the repo you want to create a protected branch for&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Repository&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Expand the &lt;strong&gt;Protected Branches&lt;/strong&gt; section&lt;/li&gt;
&lt;li&gt;From the &lt;strong&gt;Branch&lt;/strong&gt; field, select a branch&lt;/li&gt;
&lt;li&gt;From the &lt;strong&gt;Allowed to merge&lt;/strong&gt; field, select a Role or Group or User (note: a Group or User could have lower permissions than you’d like) who should be able to merge a merge request into a branch. You’d want this to be as restrictive or more restrictive compared to the &lt;strong&gt;Allowed to push&lt;/strong&gt; field&lt;/li&gt;
&lt;li&gt;From the &lt;strong&gt;Allowed to push&lt;/strong&gt; field, select a Role or Group or User (note: a Group or User could have lower permissions than you’d like) who should be able to try to push a merge request to a branch&lt;/li&gt;
&lt;li&gt;Disable the &lt;strong&gt;Require approval from code owners&lt;/strong&gt; checkbox (unless you know what you’re doing)&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Protect&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Manage access to GitLab projects</title>
    <link href="https://wilcovanes.ch/articles/manage-access-to-gitlab-projects/"/>
    <updated>2020-01-20T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/manage-access-to-gitlab-projects/</id>
    <content type="html">&lt;p&gt;In GitLab, the following is how you set permissions for a repo.&lt;/p&gt;
&lt;h1&gt;Groups&lt;/h1&gt;
&lt;p&gt;You can divide your team or organisation into groups and subgroups. For example, you could have a group called weyland-yutani, with subgroups weyland-yutani/marketing and weyland-yutani/external.&lt;/p&gt;
&lt;p&gt;This works as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you invite a subgroup to a project, access to that project is extended to members in the subgroup &lt;em&gt;and&lt;/em&gt; the parent group.&lt;/li&gt;
&lt;li&gt;If you add a member to a subgroup, they gain access to projects assigned to the subgroup, but &lt;em&gt;not&lt;/em&gt; to projects assigned only the parent group.&lt;/li&gt;
&lt;li&gt;Why not create more groups instead of subgroups? Firstly because groups are GitLab-wide. You cannot use group names which another organisation uses. Secondly, you wouldn’t be able to inherit members from the parent group, you’d have to manage those members separately.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to give access to one or more people:&lt;/p&gt;
&lt;h1&gt;Giving access to a repo&lt;/h1&gt;
&lt;p&gt;For each individual:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Groups&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select the group or subgroup for editing (cog icon)&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Members&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ensure you’re on the &lt;strong&gt;Invite member&lt;/strong&gt; tab and:
&lt;ol&gt;
&lt;li&gt;Enter their GitLab username, GitHub username, or email address&lt;/li&gt;
&lt;li&gt;Choose the appropriate permission role (roles explained below)&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Invite&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you only added people to the main group, they’ve already been added to each project in the space, in which case the following steps are not needed.&lt;/p&gt;
&lt;p&gt;For each project:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Projects&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Your projects&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to the project (hit the linked project name)&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Members&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Invite group&lt;/strong&gt; tab and:
&lt;ol&gt;
&lt;li&gt;Select the group or subgroup&lt;/li&gt;
&lt;li&gt;Select the maximum permission role they should have (roles explained below)&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Invite&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Roles&lt;/h1&gt;
&lt;p&gt;Summary:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Can&lt;/th&gt;
&lt;th&gt;Cannot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Guest&lt;/td&gt;
&lt;td&gt;View basic project details&lt;/td&gt;
&lt;td&gt;Leave comments or create issues on private projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reporter&lt;/td&gt;
&lt;td&gt;Create issues, manage labels, see merge requests&lt;/td&gt;
&lt;td&gt;Create branches or push to branches.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer&lt;/td&gt;
&lt;td&gt;Create branches, create merge requests, cancel and retry jobs&lt;/td&gt;
&lt;td&gt;Push to a protected branch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintainer&lt;/td&gt;
&lt;td&gt;Can push to a protected branch, can add members, can manage CI/CD&lt;/td&gt;
&lt;td&gt;Transfer/remove projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner&lt;/td&gt;
&lt;td&gt;Transfer/remove projects&lt;/td&gt;
&lt;td&gt;Fly, become invisible, stop time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;More detail can be found in &lt;a href=&quot;https://docs.gitlab.com/ee/user/permissions.html&quot;&gt;the Permissions section of the GitLab Docs&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Commonly used Magento 2 commands</title>
    <link href="https://wilcovanes.ch/articles/commonly-used-magento-2-commands/"/>
    <updated>2019-11-01T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/commonly-used-magento-2-commands/</id>
    <content type="html">&lt;h1&gt;Display the cron jobs&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;crontab -l&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Install the cron jobs&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento cron:install&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Reindex&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento indexer:reindex&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Disable the cache&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento cache:disable&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Enable a module&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento module:enable Vendor_ModuleName&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;php bin/magento setup:upgrade&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Check status of modules&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento module:status&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Flush the cache&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento cache:flush&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Clean the cache&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento cache:clean&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Compile classes&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento setup:di:compile&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Set Magento to developer mode&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento deploy:mode:set developer&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Check the Magento mode&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento deploy:mode:show&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Publish assets for an area&lt;/h1&gt;
&lt;p&gt;Example for Back-end (adminhtml):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;php bin/magento setup:static-content:deploy en_US -a adminhtml -f&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Example for Front-end (frontend):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;php bin/magento setup:static-content:deploy en_GB -a frontend -t YourTheme/YourTheme -f&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Example for both areas (adminhtml &amp;amp; frontend):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;php bin/magento setup:static-content:deploy -f en_GB en_US&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Generate factory classes&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;sudo chmod -R 777 generated/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;php bin/magento setup:di:compile&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Unlock an admin user&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;php bin/magento admin:user:unlock AdminUsernamE&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Show query from collection&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;echo $collection-&amp;gt;getSelect()-&amp;gt;__toString();&lt;/code&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>SQLSTATE[40001]: Serialization failure: 1213 Deadlock found</title>
    <link href="https://wilcovanes.ch/articles/sqlstate[40001]-serialization-failure-1213-deadlock-found/"/>
    <updated>2019-08-06T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/sqlstate[40001]-serialization-failure-1213-deadlock-found/</id>
    <content type="html">&lt;p&gt;&lt;code&gt;&amp;quot;SQLSTATE[40001]: Serialization failure: 1213 Deadlock found&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This error can occur during reindexing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Category Products index is locked by another reindex process. 
Skipping.
Product Categories indexer process unknown error:
SQLSTATE&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;40001&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;: Serialization failure: &lt;span class=&quot;token number&quot;&gt;1213&lt;/span&gt; Deadlock found
when trying to get lock&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; try restarting transaction, 
query was: DELETE FROM catalog_category_product
WHERE &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;category_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;1050&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Either another reindex is running, or someone cancelled a previous reindex (in which case indexing for that table will remain locked).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;php bin/magento indexer:reset&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;php bin/magento indexer:reindex&lt;/code&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How Multi Source Inventory (MSI) works in Magento 2.3+</title>
    <link href="https://wilcovanes.ch/articles/how-multi-source-inventory-msi-works-in-magento-23/"/>
    <updated>2019-03-01T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-multi-source-inventory-msi-works-in-magento-23/</id>
    <content type="html">&lt;p&gt;Example view of Quantity per Source and Salable Quantity for a product:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Quantity Per Source&lt;/th&gt;
&lt;th&gt;Salable Quantity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product X&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Warehouse X&lt;/strong&gt;: 100&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;UK&lt;/strong&gt;: 185&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship A&lt;/strong&gt;: 50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;DE&lt;/strong&gt;: 180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship B&lt;/strong&gt;: 20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AT&lt;/strong&gt;: 180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship C&lt;/strong&gt;: 10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CH&lt;/strong&gt;: 180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship D&lt;/strong&gt;: 0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Brick store&lt;/strong&gt;: 5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Explanation (Quantity per Source)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;This particular store setup uses separate &lt;strong&gt;Websites&lt;/strong&gt; and &lt;strong&gt;Store Views&lt;/strong&gt; per locale&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Salable Quantity&lt;/strong&gt; for each locale combines stock from &lt;strong&gt;Sources&lt;/strong&gt; which have been set for that locale&lt;/li&gt;
&lt;li&gt;In this example, &lt;strong&gt;UK&lt;/strong&gt; ships from every &lt;strong&gt;Source&lt;/strong&gt;, so 100 + 50 + 20 + 10 + 5 = 185&lt;/li&gt;
&lt;li&gt;The other stores ship from everything except the physical store, so 100 + 50 + 20 + 10 = 180&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After a customer orders 1 &lt;em&gt;Product X&lt;/em&gt; from the &lt;strong&gt;AT&lt;/strong&gt; store, but &lt;em&gt;before&lt;/em&gt; it has been shipped:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Quantity Per Source&lt;/th&gt;
&lt;th&gt;Salable Quantity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product X&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Warehouse X&lt;/strong&gt;: 100&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;UK&lt;/strong&gt;: 184&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship A&lt;/strong&gt;: 50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;DE&lt;/strong&gt;: 179&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship B&lt;/strong&gt;: 20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AT&lt;/strong&gt;: 179&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship C&lt;/strong&gt;: 10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CH&lt;/strong&gt;: 179&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship D&lt;/strong&gt;: 0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Brick store&lt;/strong&gt;: 5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;This reservation reduces &lt;strong&gt;Salable Quantity&lt;/strong&gt; by 1 for all stores connected to the same &lt;strong&gt;Source&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;If &lt;strong&gt;Out of Stock Threshold&lt;/strong&gt; has a positive value, it is subtracted from &lt;strong&gt;Salable Quantity&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After the order has been shipped:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Quantity Per Source&lt;/th&gt;
&lt;th&gt;Salable Quantity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product X&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Warehouse X&lt;/strong&gt;: 99&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;UK&lt;/strong&gt;: 184&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship A&lt;/strong&gt;: 50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;DE&lt;/strong&gt;: 179&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship B&lt;/strong&gt;: 20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AT&lt;/strong&gt;: 179&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship C&lt;/strong&gt;: 10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CH&lt;/strong&gt;: 179&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dropship D&lt;/strong&gt;: 0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Brick store&lt;/strong&gt;: 5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;Now that the order has been shipped, the reservation is removed and the stock is reduced&lt;/li&gt;
&lt;li&gt;The stock is reduced at the &lt;strong&gt;Source&lt;/strong&gt; which is the highest priority in the configuration and has stock&lt;/li&gt;
&lt;li&gt;Reservations which do not lead to shipments (order cancelled, for example) are removed once daily&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Out of Stock Threshold setting&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Products&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Catalog&lt;/strong&gt; (under &lt;strong&gt;Inventory&lt;/strong&gt;) -&amp;gt; &lt;em&gt;Product X&lt;/em&gt; -&amp;gt; &lt;strong&gt;Advanced Inventory&lt;/strong&gt; (under &lt;strong&gt;Quantity&lt;/strong&gt;):&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/out-of-stock-threshold-setting.png&quot; alt=&quot;Out of Stock Threshold setting&quot;&gt;&lt;/figure&gt;
&lt;h1&gt;Formula&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Salable Quantity&lt;/strong&gt; = &lt;strong&gt;Source A stock&lt;/strong&gt; - &lt;strong&gt;Reservations&lt;/strong&gt; + &lt;strong&gt;Source B stock&lt;/strong&gt; - &lt;strong&gt;Reservations&lt;/strong&gt; (and so on in case of more &lt;strong&gt;Sources&lt;/strong&gt;) - &lt;strong&gt;Out of Stock Threshold&lt;/strong&gt; setting.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>eCommerce test prompts</title>
    <link href="https://wilcovanes.ch/articles/ecommerce-test-prompts/"/>
    <updated>2018-08-23T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/ecommerce-test-prompts/</id>
    <content type="html">&lt;p&gt;Testing a web shop and think you&#39;re done? Here are some test ideas you may or may not have covered yet (that will still not be exhaustive):&lt;/p&gt;
&lt;h1&gt;Regions&lt;/h1&gt;
&lt;p&gt;Tested across supported countries? What differences &lt;em&gt;should&lt;/em&gt; there be? Currency? Date &amp;amp; time formats? Payment methods? Shipping methods? Regulations?&lt;/p&gt;
&lt;h1&gt;Languages&lt;/h1&gt;
&lt;p&gt;Tested across supported languages? Special character rendering, capitalisation &amp;amp; punctuation, impact on cross-browser font rendering.&lt;/p&gt;
&lt;h1&gt;Form factors&lt;/h1&gt;
&lt;p&gt;Tested with touch navigation? Tested across supported resolutions?&lt;/p&gt;
&lt;h1&gt;Payment types&lt;/h1&gt;
&lt;p&gt;Pay now versus pay later. Credit card versus bank transfer.&lt;/p&gt;
&lt;h1&gt;User states&lt;/h1&gt;
&lt;p&gt;Check out as logged in user versus guest user. With and without address set on account. With old and new account (keep old accounts around).&lt;/p&gt;
&lt;h1&gt;Product types&lt;/h1&gt;
&lt;p&gt;Simple products. Configurable products. Bundles.&lt;/p&gt;
&lt;h1&gt;User actions&lt;/h1&gt;
&lt;p&gt;Printing a page. Letting a page time out. Navigating in landscape mode.&lt;/p&gt;
&lt;h1&gt;Product states&lt;/h1&gt;
&lt;p&gt;Out of stock product. Product with limited stock. Reserved product.&lt;/p&gt;
&lt;h1&gt;Cookie acceptance&lt;/h1&gt;
&lt;p&gt;Test impact of rejected or accepted state on cookies loaded. Choice remembered when it should be?&lt;/p&gt;
&lt;h1&gt;Cart&lt;/h1&gt;
&lt;p&gt;Other products in cart from old session. Other products in cart without stock. Other products in cart from different warehouse(s).&lt;/p&gt;
&lt;h1&gt;Microsites&lt;/h1&gt;
&lt;p&gt;Changes on main site also made on other sites when consistency is expected?&lt;/p&gt;
&lt;h1&gt;Tracking&lt;/h1&gt;
&lt;p&gt;Impact of changes on whether user data is still tracked as it should be.&lt;/p&gt;
&lt;h1&gt;Compliance&lt;/h1&gt;
&lt;p&gt;What safe shopping or other regulations need to still be safeguarded?&lt;/p&gt;
&lt;h1&gt;Performance&lt;/h1&gt;
&lt;p&gt;Impact of changes on page load time, Lighthouse score, transaction times of affected functionality.&lt;/p&gt;
&lt;h1&gt;Security&lt;/h1&gt;
&lt;p&gt;Do changes have implications for data privacy or create security vulnerabilities?&lt;/p&gt;
&lt;h1&gt;Accessibility&lt;/h1&gt;
&lt;p&gt;Do changes violate your supported level of the Web Content Accessibility Guidelines?&lt;/p&gt;
&lt;h1&gt;SEO&lt;/h1&gt;
&lt;p&gt;Verify that the structured data has the expected properties and values.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Mocking API requests and responses through Charles Proxy</title>
    <link href="https://wilcovanes.ch/articles/mocking-api-requests-and-responses-through-charles-proxy/"/>
    <updated>2018-06-13T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/mocking-api-requests-and-responses-through-charles-proxy/</id>
    <content type="html">&lt;p&gt;As a prerequisite to the below, ensure the SSL proxying settings and certificates are set and Charles is logging requests from the browser you&#39;re targeting.&lt;/p&gt;
&lt;h3&gt;Map Local&lt;/h3&gt;
&lt;p&gt;You can define modified response bodies to be loaded instead of the actual responses.&lt;/p&gt;
&lt;p&gt;First, ensure the following rewrite rule is in place for CORS:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Under &lt;strong&gt;Tools&lt;/strong&gt;, select &lt;strong&gt;Rewrite...&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ensure the &lt;strong&gt;Enable Rewrite&lt;/strong&gt; checkbox is checked&lt;/li&gt;
&lt;li&gt;Add a new rule&lt;/li&gt;
&lt;li&gt;Add the relevant URIs (you can use an asterisk as a wildcard)&lt;/li&gt;
&lt;li&gt;Add the actual rule&lt;br&gt;
Type: &lt;em&gt;Modify Header&lt;/em&gt;&lt;br&gt;
Where: &lt;em&gt;Response&lt;/em&gt;&lt;br&gt;
Name: &lt;em&gt;X-Charles-Map-Local&lt;/em&gt;&lt;br&gt;
Replace:&lt;br&gt;
Name: &lt;em&gt;Access-Control-Allow-Origin&lt;/em&gt;&lt;br&gt;
Value: *&lt;/li&gt;
&lt;li&gt;Ensure the rule is checked&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apply&lt;/strong&gt; it&lt;/li&gt;
&lt;li&gt;Reload the page with the request you&#39;re targeting&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then create the modified response to map to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In a browser, open up the developer console&#39;s &lt;strong&gt;Network&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;In the browser, trigger the response you want to modify&lt;/li&gt;
&lt;li&gt;Copy the response&lt;/li&gt;
&lt;li&gt;Paste the response into an editor which parses it nicely&lt;/li&gt;
&lt;li&gt;Modify the response to whatever you would like to see (whilst remaining valid)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then map to it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Under &lt;strong&gt;Tools&lt;/strong&gt;, select &lt;strong&gt;Map Local...&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ensure the &lt;strong&gt;Enable Map Local&lt;/strong&gt; checkbox is checked&lt;/li&gt;
&lt;li&gt;Add a new mapping&lt;/li&gt;
&lt;li&gt;Add the relevant URIs (you can copy the URI from the browser request and paste it in the Host field, Charles will break the URI into the appropriate fields once you move focus away from the Host field)&lt;/li&gt;
&lt;li&gt;Choose the local response file to map to&lt;/li&gt;
&lt;li&gt;Save the mapping&lt;/li&gt;
&lt;li&gt;Ensure the mapping is checked (and that potentially conflicting mappings are not checked)&lt;/li&gt;
&lt;li&gt;Reload the page with the request you&#39;re targeting&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Rewrite&lt;/h3&gt;
&lt;p&gt;You can enable or disable rewrites of request/response bodies, parameters, or status codes to be in place whenever you use the targeted URI(s).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Under &lt;strong&gt;Tools&lt;/strong&gt;, select &lt;strong&gt;Rewrite...&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ensure the &lt;strong&gt;Enable Rewrite&lt;/strong&gt; checkbox is checked&lt;/li&gt;
&lt;li&gt;Add a new rule&lt;/li&gt;
&lt;li&gt;Add the relevant URIs (you can copy the URI from the browser request and paste it in the Host field, Charles will break the URI into the appropriate fields once you move focus away from the Host field)&lt;/li&gt;
&lt;li&gt;Add the actual rule&lt;/li&gt;
&lt;li&gt;Ensure the rule is checked (and that potentially conflicting rules are not checked)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apply&lt;/strong&gt; it&lt;/li&gt;
&lt;li&gt;Reload the page with the request you&#39;re targeting&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example rules:&lt;/p&gt;
&lt;p&gt;Type: &lt;em&gt;Response Status&lt;/em&gt;&lt;br&gt;
Where: &lt;em&gt;Response&lt;/em&gt; (automatic)&lt;br&gt;
Match - Value: &lt;em&gt;200&lt;/em&gt;&lt;br&gt;
Replace - Value: &lt;em&gt;500&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Type: &lt;em&gt;Body&lt;/em&gt;&lt;br&gt;
Where: &lt;em&gt;Response&lt;/em&gt;&lt;br&gt;
Match - Value: &lt;em&gt;&amp;quot;Expires&amp;quot;:&amp;quot;2018-07-31T14:44:44Z&amp;quot;&lt;/em&gt;&lt;br&gt;
Replace - Value: &lt;em&gt;&amp;quot;Expires&amp;quot;:&amp;quot;2018-07-31T14:49:44Z&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Type: &lt;em&gt;Body&lt;/em&gt;&lt;br&gt;
Where: &lt;em&gt;Request &amp;amp; Response&lt;/em&gt;&lt;br&gt;
Match - Value: &lt;em&gt;Details&lt;/em&gt;&lt;br&gt;
Replace - Value:&lt;/p&gt;
&lt;p&gt;Type: &lt;em&gt;URL&lt;/em&gt;&lt;br&gt;
Where: &lt;em&gt;Request&lt;/em&gt; (automatic)&lt;br&gt;
Match - Value: &lt;em&gt;register&lt;/em&gt;&lt;br&gt;
Replace - Value: &lt;em&gt;asdf&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Type: &lt;em&gt;Modify Query Param&lt;/em&gt;&lt;br&gt;
Where: &lt;em&gt;Request&lt;/em&gt; (automatic)&lt;br&gt;
Match - Name: &lt;em&gt;LanguageCode&lt;/em&gt;&lt;br&gt;
Match - Value: &lt;em&gt;en&lt;/em&gt;&lt;br&gt;
Replace - Name: &lt;em&gt;LanguageCode&lt;/em&gt;&lt;br&gt;
Match - Value: &lt;em&gt;jp&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Breakpoints&lt;/h3&gt;
&lt;p&gt;You can target requests &#39;live&#39; where Charles will interrupt further requests and allow you to edit its parameters and continue with the changed parameters.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Under &lt;strong&gt;Proxy&lt;/strong&gt; (or from the toolbar), select &lt;strong&gt;Enable Breakpoints&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Proxy&lt;/strong&gt;, select &lt;strong&gt;Breakpoint Settings...&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Add a new breakpoint&lt;/li&gt;
&lt;li&gt;Select whether you want to target a GET or POST request in the Scheme field&lt;/li&gt;
&lt;li&gt;Add the relevant URIs (you can copy the URI from the browser request and paste it in the Host field, Charles will break the URI into the appropriate fields once you move focus away from the Host field)&lt;/li&gt;
&lt;li&gt;Select whether you want to target the request and/or the response&lt;/li&gt;
&lt;li&gt;Ensure the breakpoint is checked (and that potentially conflicting breakpoints are not checked)&lt;/li&gt;
&lt;li&gt;Trigger the request in the browser&lt;/li&gt;
&lt;li&gt;Edit the parameters as desired&lt;/li&gt;
&lt;li&gt;Execute the amended request/response&lt;/li&gt;
&lt;li&gt;Return to the browser&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>The over- and underestimation of test automation</title>
    <link href="https://wilcovanes.ch/articles/the-over-and-underestimation-of-test-automation/"/>
    <updated>2018-06-10T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/the-over-and-underestimation-of-test-automation/</id>
    <content type="html">&lt;p&gt;Let&#39;s be clever about test automation in web development at scale. If you think manual testing no longer has a place, this post is for you. Conversely if you think programmed checks have low value, this post is also for you.&lt;/p&gt;
&lt;h3&gt;Underestimation&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;Test automation is just a series of dumb checks telling us whether what was true is still true&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Whilst test automation is not limited to automated regression checks, those are already hugely valuable. Imagine putting changes into production every day and having to wait for a suite of manual regression checks to be completed each time to learn whether the changes break anything.&lt;/p&gt;
&lt;p&gt;There&#39;s an understandable predicament for testers who have no interest in an SDET (Software Development Engineer in Test) role and find the job market increasingly demands a tester to be just that. However, the response should not be to attempt to deny the value of test automation. The response should be to celebrate that test automation (whether it be done by you or by developers) will provide a test specialist with more time for the interesting aspects of testing, due to being able to outsource the repetitive yet necessary regression checks to one&#39;s friendly neighbourhood robot.&lt;/p&gt;
&lt;h3&gt;Overestimation&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;When can we expect to reach 0% manual testing?&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Looking on the bright side, it&#39;s encouraging that test automation has become so valuable in web development that some people think it can deliver us from all our woes. Less encouraging is that after at least two decades of teaching and improving the effectiveness and efficiency of exploratory test techniques and teaching that quality is more than verifying a few known functional claims are true, the global test community has failed to reach outside of their own silo and influence a new generation of software engineers and engineering managers.&lt;/p&gt;
&lt;p&gt;Fewer and fewer view testing as more than mere verification of acceptance criteria and/or scripted regression checks and/or arbitrary bug hunting. By those standards it&#39;s true — we could handle testing through test automation and, for the rudimentary bug hunting aspect, through developers clicking around their own work and reviewing screenshots from cross-browser previewing tools. Yet this leaves a number of risks unexamined and a number of other risks inadequately covered.&lt;/p&gt;
&lt;h3&gt;Where we are now&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;For testing we always add 1 story point.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Testing has evolved. It has become increasingly fit to handle the typical modern day web development project with its continuous delivery and swiftly changing requirements, thanks in large part to the increased emphasis on test automation at all levels (unit, integration, end-to-end) and all stages (TDD/BDD early on, regression tests in CI, logging and monitoring in production) and the application of Agile (or agile) and DevOps practices of shifting left and using a whole team approach to testing.&lt;/p&gt;
&lt;p&gt;However, with developers taking a more active and integrated part in testing — which is very welcome — and testers changing focus from exploratory test techniques, custom test tooling, and non-functional testing to a primary focus on developing browser-driven end-to-end regression suites, and with no test manager or test coach taking responsibility for tirelessly promoting quality and appropriate ways of achieving it, a number of gaps have opened up and are widening.&lt;/p&gt;
&lt;p&gt;There are now development teams which haven&#39;t had experience with professional testers (aside from SDETs) or test managers and think there are three types of testing: 1. unit tests written by a developer, 2. UI tests written by an SDET, and 3. verification where a developer or tester manually checks whether a user story&#39;s acceptance criteria are met. Some teams are also aware of integration testing.&lt;/p&gt;
&lt;p&gt;The gaps are filled only when someone happens to have that capability, depending on them having interest in a particular quality aspect and the time to look into it. Validation is done to some extent by product owners, with a business view rather than an integrated user/business/technical view — and this is not usually done until the acceptance stage rather than when requirements are worked out. Negative testing is done to some extent by ad hoc checks on one&#39;s own work and dogfooding, and canary releases are sometimes used to compensate for the lack of depth. Performance, security, accessibility, l10n/i18n, and other types of cross-functional testing are done by individual developers (depending on familiarity, motivation, and personality), or are outsourced, in both cases without anyone being able to explain why the chosen test approach is adequate to the needs of the project. Exploratory testing is not done at all. Test tools, harnesses, and frameworks are chosen and built or not built without considering cost/benefit, applicability, or limitations.&lt;/p&gt;
&lt;p&gt;The theme in all this is that we&#39;re so preoccupied with delivering a feature and making sure it works for a minimum set of use cases, that important risks are not addressed and are waved away with the idea that addressing risks is not providing value to customers (seeing as it does not provide a new feature to the user) or that these risks do not matter because you can respond quickly when things do fail in production. This is absurd when you could mitigate those risks early on at a much lower cost, preventing or identifying issues which are much harder to resolve at later stages, and when you&#39;d have to have monitoring in place that&#39;s more sophisticated and broad than quality controls you use before release.&lt;/p&gt;
&lt;h3&gt;Where we can go next&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;So then we just need to have developers think about quality more, right?&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You do, if you leave out &amp;quot;just&amp;quot;. Developers should consider risks, quality, and testing, and testers should promote that and assist with it and train developers in it, but don&#39;t expect every skilled developer to also become so skilled in testing as to equal the years of deliberate practice and experience of a dedicated test specialist. What&#39;s more, it would require switching mindsets effectively from &amp;quot;how do I make this work?&amp;quot; to &amp;quot;what are all the circumstances under which this wouldn&#39;t work or wouldn&#39;t meet the needs of our target audiences or the business?&amp;quot; throughout development and in my experience this is an unrealistic imposition on developers in terms of human cognition and available time and personal interest.&lt;/p&gt;
&lt;p&gt;So you can have a team where everyone is a software engineer (aside from the PO/BA, UX, and the Scrum Master), but you may still need at least one person among them who is a highly skilled test specialist. You also need at least one person - ideally the same person(s) - with both the will and the mandate to stand up for quality. You can pontificate about quality being everyone&#39;s job, and it certainly is, but in practice it still takes a torchbearer to light the way.&lt;/p&gt;
&lt;p&gt;You could take it a step further and have one person on the team focused on test automation, test tooling, test environments, and supporting developers in their testing, and another person focused on domain knowledge, big picture thinking, exploratory testing, cross-functional testing, and still a degree of technical testing. I&#39;ve heard it said that software engineers on a Scrum team should be interchangeable. I think that&#39;s a harmful idea. Great teams are not homogeneous, they are a group of specialists working together. One of those specialists should know all about the return on investment of every level, phase, and type of testing.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;We should exhaust the possibilities of test automation. We should apply it wherever it can save us time. We should apply it wherever it can provide swift, reliable, and meaningful feedback. We should apply it to functional testing as well as cross-functional testing. We should explore new avenues like &#39;AI-driven&#39; test generation. We should also use exploratory testing wherever it can save us time. We should apply exploratory testing wherever it can provide swift, reliable, and meaningful feedback. We should use exploratory testing to uncover scenarios no-one had thought of. We should learn and ask questions and challenge assumptions at all times.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>EPERM: operation not permitted, unlink chromedriver</title>
    <link href="https://wilcovanes.ch/articles/eperm-operation-not-permitted-unlink-chromedriver/"/>
    <updated>2018-05-12T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/eperm-operation-not-permitted-unlink-chromedriver/</id>
    <content type="html">&lt;p&gt;On running &lt;code&gt;yarn&lt;/code&gt; with a &lt;code&gt;selenium-standalone&lt;/code&gt; dependency, you may encounter this error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn install v1.3.2
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
error An unexpected error occurred:
&amp;quot;EPERM: operation not permitted, unlink
&#39;/node_modules/selenium-standalone/.selenium/chromedriver/2.37-x64-chromedriver&#39;&amp;quot;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If this occurs after you&#39;ve run WebDriver tests, there may be a very simple solution. If the tests used &lt;code&gt;chromedriver&lt;/code&gt; and didn&#39;t properly close the instances, then there may still be an active &lt;code&gt;chromedriver&lt;/code&gt; process prohibiting it from being unlinked. Check if this is the case and close the process(es).&lt;/p&gt;
&lt;p&gt;If there are no open &lt;code&gt;chromedriver&lt;/code&gt; processes and the error still occurs, check &lt;code&gt;npm -v&lt;/code&gt;. If the version is 5.4 or 5.5, either downgrade to 5.3 (&lt;code&gt;npm i -g npm@5.3&lt;/code&gt;) or upgrade to 5.6+ (&lt;code&gt;npm i -g npm&lt;/code&gt;).&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>ERROR: Cannot define class using reflection</title>
    <link href="https://wilcovanes.ch/articles/error-cannot-define-class-using-reflection/"/>
    <updated>2018-05-11T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/error-cannot-define-class-using-reflection/</id>
    <content type="html">&lt;p&gt;Running tests driven by WebDriver, I experienced the following issue.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ERROR: Cannot define class using reflection
 at new RuntimeError
 (.&#92;node_modules&#92;webdriverio&#92;build&#92;lib&#92;utils&#92;ErrorHandler.js:144:12)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This occurred after, and proved to be a direct result of, a Java update.&lt;/p&gt;
&lt;p&gt;To solve this, sadly you&#39;ll have to downgrade from JDK 10 to JDK 9 on your local machine (until Selenium WebDriver is updated to be compatible with Java 10 and until you&#39;re ready to update WebDriver on your project).&lt;/p&gt;
&lt;p&gt;You can find legacy JDK versions in the &lt;a href=&quot;http://www.oracle.com/technetwork/java/archive-139210.html&quot;&gt;Oracle Java Archive&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>ESLint gives &quot;Uncaught SyntaxError: Unexpected token&quot;</title>
    <link href="https://wilcovanes.ch/articles/eslint-gives-uncaught-syntaxerror-unexpected-token/"/>
    <updated>2018-03-31T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/eslint-gives-uncaught-syntaxerror-unexpected-token/</id>
    <content type="html">&lt;p&gt;If ESLint reports &lt;code&gt;Uncaught SyntaxError: Unexpected token&lt;/code&gt; for an empty line or for a character which does not violate any linting rule, there may be a conflict between multiple ESLint instances.&lt;/p&gt;
&lt;p&gt;For example, I received the &lt;code&gt;Unexpected token&lt;/code&gt; error for an RxJS Observable which had nothing wrong with it. The reason turned out to be that I had the &lt;code&gt;eslint&lt;/code&gt; and &lt;code&gt;babel-eslint&lt;/code&gt; libraries running on it, whereas my IDE (VSCode) already used the &lt;code&gt;eslint&lt;/code&gt; extension as well.&lt;/p&gt;
&lt;p&gt;I opted to disable the native extension in VSCode. The error vanished and linting ran as expected.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Switching between GitHub accounts on the same machine</title>
    <link href="https://wilcovanes.ch/articles/switching-between-github-accounts-on-the-same-machine/"/>
    <updated>2018-02-01T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/switching-between-github-accounts-on-the-same-machine/</id>
    <content type="html">&lt;p&gt;&lt;strong&gt;Update 18 June 2021: Replaced RSA with ED25519&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want to be able to work on different repositories with different GitHub accounts, for example using a work account for one repository and a home account for another repository, then this is what you need to do. In summary:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate an SSH key on the local machine for each account you want to use&lt;/li&gt;
&lt;li&gt;Edit the &lt;em&gt;/.ssh config&lt;/em&gt; file to include Host information for each account&lt;/li&gt;
&lt;li&gt;For individual repos, change the remote url to specify the Host to use&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Generate an SSH key on the local machine for each account you want to use&lt;/h3&gt;
&lt;p&gt;When you do the following, fight the tendency to accept default options. We&#39;ll need to make sure not to accept the default name for a new SSH key and instead save it under a custom name.&lt;/p&gt;
&lt;p&gt;For any GitHub account you don&#39;t have a local SSH key for yet, run &lt;code&gt;ssh-keygen -t ed25519 -C &amp;quot;your.email@address.here&amp;quot;&lt;/code&gt; for the email address associated with that GitHub account.&lt;/p&gt;
&lt;p&gt;When it generates the key, save it under a new name, so instead of the default &lt;strong&gt;id_ed25519&lt;/strong&gt; make it, for example, &lt;strong&gt;id_ed25519_HOME&lt;/strong&gt; or &lt;strong&gt;id_ed25519_WORK&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Your current keys are located in the &lt;em&gt;.ssh&lt;/em&gt; folder. You can view existing pub keys by running &lt;code&gt;ls ~/.ssh/*.pub&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Edit the /.ssh config file to include Host information for each account&lt;/h3&gt;
&lt;p&gt;Edit the SSH config in &lt;code&gt;~/.ssh/config&lt;/code&gt; and add any additional GitHub accounts on top of the default account. You need a unique Host for each, for example &lt;em&gt;github.com-username&lt;/em&gt;, and add the SSH key you created for it.&lt;/p&gt;
&lt;p&gt;Example config:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#HOME
Host github.com-homeuser
HostName github.com
User git
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_ed25519
AddKeysToAgent yes

#WORK
Host github.com-workuser
HostName github.com
User git
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_ed25519_WORK
AddKeysToAgent yes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;For individual repos, change the remote url to specify the Host to use&lt;/h3&gt;
&lt;p&gt;If you have a default GitHub account you use, but have a specific repository you want to use a different GitHub account for, check the current Host by running &lt;em&gt;git remote -v&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The remote will be something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;origin git@github.com:username/repo.git (fetch)
origin git@github.com:username/repo.git (push)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All you need to do is change the host, so you want to end up with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;origin git@github.com-workuser:username/repo.git (fetch)
origin git@github.com-workuser:username/repo.git (push)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To do so, simply run (replacing the values with those from your git config) &lt;code&gt;git remote set-url origin git@github.com-workuser:username/repo.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now this repository will use your work user account and its SSH key.&lt;/p&gt;
&lt;p&gt;Since we updated the &lt;strong&gt;Host&lt;/strong&gt; value for both the existing and the new key, we&#39;ll need to run the &lt;code&gt;set-url&lt;/code&gt; command for every local repo on the same machine, adjusting the &lt;strong&gt;Host&lt;/strong&gt; value depending on whether the repo is used professionally or personally.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Fixing the &quot;invalid signing key or OpenId metadata document&quot; error&quot; in Bot Builder</title>
    <link href="https://wilcovanes.ch/articles/fixing-the-invalid-signing-key-or-openid-metadata-document-error-in-bot-builder/"/>
    <updated>2017-08-21T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/fixing-the-invalid-signing-key-or-openid-metadata-document-error-in-bot-builder/</id>
    <content type="html">&lt;p&gt;When running a local emulation of a Microsoft Bot Builder chatbot, you might find that without making any changes to your code or dependencies you receive a new error.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; app.js
restify listening to http://&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;::&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;:3978
ERROR: ChatConnector: receive - invalid signing key
or OpenId metadata document.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the version of the botbuilder framework you&#39;re using, and the version of the emulator you&#39;re using. In my case, the Bot Framework Channel Emulator seemed to have updated itself to v3.5.31 (latest version at the time), with the botbuilder library at v3.5.4 (latest version being 3.9.1 at the time).&lt;/p&gt;
&lt;p&gt;As of v3.5.31, the emulator uses a new authorisation endpoint. &lt;a href=&quot;https://blog.botframework.com/2017/05/07/Bot-Framework-Auth-Change/&quot;&gt;Microsoft&#39;s Bot Framework blog has more information on this change&lt;/a&gt;. To solve the problem, simply update the botbuilder library (Node.js: &lt;code&gt;npm i botbuilder --save&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;After the update, we&#39;re back in business:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; app.js
restify listening to http://&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;::&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;:3978
ChatConnector: message received.&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Configuring ConEmu as a multi-terminal workspace</title>
    <link href="https://wilcovanes.ch/articles/configuring-conemu-as-a-multi-terminal-workspace/"/>
    <updated>2017-07-01T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/configuring-conemu-as-a-multi-terminal-workspace/</id>
    <content type="html">&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;You can download &lt;strong&gt;ConEmu&lt;/strong&gt; from &lt;a href=&quot;https://www.fosshub.com/ConEmu.html&quot;&gt;https://www.fosshub.com/ConEmu.html&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You&#39;ll likely want the &lt;em&gt;Installer&lt;/em&gt; download for the latest &lt;em&gt;Stable&lt;/em&gt; release.&lt;/p&gt;
&lt;p&gt;On running the installation, select either the x64 or x86 version, of course depending on whether you&#39;re running a 64-bit or 32-bit version of Windows respectively.&lt;/p&gt;
&lt;h3&gt;Splitting panes on launch&lt;/h3&gt;
&lt;p&gt;You can create a shortcut for ConEmu with the target path edited so it launches with multiple terminals.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;C:&#92;Program Files&#92;ConEmu&#92;ConEmu64.exe&amp;quot; -cmdlist
cmd -cur_console |||
cmd -cur_console:s1TVn |||
cmd -cur_console:s1THn |||
cmd -cur_console:s2THn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a grid of 4 terminals with equal width and height.&lt;/p&gt;
&lt;p&gt;Alternatively, you can edit the predefined task for the default shell. In ConEmu, go to &lt;strong&gt;Settings&lt;/strong&gt; and under &lt;strong&gt;Startup&lt;/strong&gt; select &lt;strong&gt;Tasks&lt;/strong&gt;. Edit the &lt;strong&gt;{Shells::cmd}&lt;/strong&gt; task.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmd.exe /k &amp;quot;%ConEmuBaseDir%&#92;CmdInit.cmd&amp;quot;
cmd -new_console:s66H
cmd -new_console:s50H
cmd -new_console:s2T50V
cmd -new_console:s3T50V
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the &lt;strong&gt;Default shell&lt;/strong&gt; checkbox and save the changes.&lt;/p&gt;
&lt;p&gt;This creates a 2x2 grid on launch, but with an extra full height terminal on the left hand side.&lt;/p&gt;
&lt;h3&gt;Setting default folders&lt;/h3&gt;
&lt;p&gt;To launch the terminals with specific work folders, add a &lt;strong&gt;:d&lt;/strong&gt; flag.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;C:&#92;Program Files&#92;ConEmu&#92;ConEmu64.exe&amp;quot; -cmdlist
cmd -cur_console:d:C:&#92;Ruby23-x64&#92; |||
cmd -cur_console:s1TVn:d:C:&#92;Ruby23-x64&#92;project&#92;app |||
cmd -cur_console:s1THn |||
cmd -cur_console:s2THn
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Setting default task executions&lt;/h3&gt;
&lt;p&gt;As with setting default folders, you can edit the predefined task for the default shell. In ConEmu, go to &lt;strong&gt;Settings&lt;/strong&gt; and under &lt;strong&gt;Startup&lt;/strong&gt; select &lt;strong&gt;Tasks&lt;/strong&gt;. Edit the &lt;strong&gt;{Shells::cmd}&lt;/strong&gt; task.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;cmd -cur_console:d:C:&#92;Python33&#92;project
/k &amp;quot;Scripts&#92;activate&amp;quot; &amp;amp; cd project

cmd -cur_console:s1TVn

cmd -cur_console:s1THn:d:C:&#92;elasticsearch-1.4.4&#92;bin &amp;amp;
/k &amp;quot;service start&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the &lt;strong&gt;Default shell&lt;/strong&gt; checkbox and save the changes.&lt;/p&gt;
&lt;p&gt;In the example, launching ConEmu would activate the virtual environment in a Python project, change the working directory to this project, and in a separate terminal it launches the ElasticSearch service.&lt;/p&gt;
&lt;h3&gt;Set background transparency&lt;/h3&gt;
&lt;p&gt;Open &lt;strong&gt;Settings&lt;/strong&gt; and under &lt;strong&gt;Features&lt;/strong&gt; select &lt;strong&gt;Transparency&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Now, ensure the &lt;strong&gt;Active window transparency&lt;/strong&gt; checkbox is selected and drag the slider to change the transparency. Something like 75% opaque should be sufficient to comfortably read console text whilst being able to, for example, read a webpage behind the console.&lt;/p&gt;
&lt;h3&gt;Disable focus on hover&lt;/h3&gt;
&lt;p&gt;If you keep losing focus on your selected console, this is likely due to the setting which changes the active console when your mouse hovers over a different one from the one you have selected. This has become a default setting.&lt;/p&gt;
&lt;p&gt;To disable this, in &lt;strong&gt;Settings&lt;/strong&gt; under &lt;strong&gt;Keys &amp;amp; Macro&lt;/strong&gt; select &lt;strong&gt;Mouse&lt;/strong&gt; and uncheck the checkbox for &lt;strong&gt;Activate split/pane on mouse hover&lt;/strong&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>uninitialized constant Middleman::Sitemap::Store::Pathname</title>
    <link href="https://wilcovanes.ch/articles/uninitialized-constant-middlemansitemapstorepathname/"/>
    <updated>2017-04-24T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/uninitialized-constant-middlemansitemapstorepathname/</id>
    <content type="html">&lt;p&gt;Let&#39;s hope I&#39;ll never have to refer back to this page.&lt;/p&gt;
&lt;h3&gt;What I did&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;irb&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;001&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;google-drive&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;What happened&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;NameError&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; uninitialized constant
Middleman&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Sitemap&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Store&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Pathname&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;What &#39;fixed&#39; it&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem uninstall middleman&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;core&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which was at version 4.2.1, followed by:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem install middleman&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;core &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;v &lt;span class=&quot;token number&quot;&gt;3.4&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;.1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This fixed the immediate issue, but was followed by new problems.&lt;/p&gt;
&lt;h3&gt;What I should have done instead&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;irb&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;001&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;google_drive&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&#39;s a big difference between the gems &lt;code&gt;google-drive&lt;/code&gt; and &lt;code&gt;google_drive&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Enabling a Let&#39;s Encrypt SSL certificate on a Rails app hosted on Heroku with a Namecheap DNS</title>
    <link href="https://wilcovanes.ch/articles/enabling-a-lets-encrypt-ssl-certificate-on-a-rails-app-hosted-on-heroku-with-a-namecheap-dns/"/>
    <updated>2017-04-21T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/enabling-a-lets-encrypt-ssl-certificate-on-a-rails-app-hosted-on-heroku-with-a-namecheap-dns/</id>
    <content type="html">&lt;p&gt;This guide applies if you want to secure the Namecheap domain for your Rails app with a free Let&#39;s Encrypt SSL certificate and force HTTP access to HTTPS.&lt;/p&gt;
&lt;p&gt;The following steps were last verified as valid on 21 April 2017.&lt;/p&gt;
&lt;h3&gt;Heroku&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Log in to your Heroku account&lt;/li&gt;
&lt;li&gt;Select the app you want to set an SSL certificate for&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Scroll to &lt;strong&gt;Domains and certificates&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Configure SSL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Automatically configure using Automated Certificate Management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Continue&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Namecheap&lt;/h3&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;Log in to your Namecheap account&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Dashboard&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For your app&#39;s domain, select &lt;strong&gt;Manage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Advanced DNS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Change the &lt;strong&gt;CNAME Record&lt;/strong&gt; for Host &lt;em&gt;www&lt;/em&gt; to &lt;em&gt;www.yourdomain.com.herokudns.com&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Save your change&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Heroku again&lt;/h3&gt;
&lt;ol start=&quot;14&quot;&gt;
&lt;li&gt;Click &lt;strong&gt;I&#39;ve done this&lt;/strong&gt; for the DNS settings change&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Continue&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Wait for the changes to take effect. You can use &lt;strong&gt;Refresh status&lt;/strong&gt; to view progress or &lt;code&gt;curl -vI https://www.yourdomain.com&lt;/code&gt; for more detail&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now the SSL certificate should be applied to your domain.&lt;/p&gt;
&lt;h3&gt;Rails&lt;/h3&gt;
&lt;ol start=&quot;17&quot;&gt;
&lt;li&gt;Edit &lt;code&gt;config&#92;environments&#92;production.rb&lt;/code&gt; in your Rails app&lt;/li&gt;
&lt;li&gt;Find the &lt;code&gt;config.force_ssl&lt;/code&gt; toggle&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;config.force.ssl = true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Deploy the change&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now if you go to yourdomain.com or www.yourdomain.com you&#39;ll be brought to https://www.yourdomain.com and the browser&#39;s certificate verification will indicate a secure connection.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Why your EV3 might appear to shut down arbitrarily</title>
    <link href="https://wilcovanes.ch/articles/why-your-ev3-might-appear-to-shut-down-arbitrarily/"/>
    <updated>2017-04-17T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/why-your-ev3-might-appear-to-shut-down-arbitrarily/</id>
    <content type="html">&lt;p&gt;The EV3 programmable brick should be powered with 6 1.5 V AA batteries for a total voltage of 9 V.&lt;/p&gt;
&lt;p&gt;When total voltage drops below 5 V, you will get strange issues such as the programmable brick spontaneously shutting down.&lt;/p&gt;
&lt;p&gt;Connecting to Wi-Fi can quickly drop the voltage.&lt;/p&gt;
&lt;p&gt;If you use &lt;a href=&quot;http://www.ev3dev.org/&quot;&gt;ev3dev&lt;/a&gt;, you can make it easier to learn when the EV3 is dipping below 5 V. The following is an example using ev3dev with Python.&lt;/p&gt;
&lt;p&gt;In a shell or script, use &lt;code&gt;PowerSupply()&lt;/code&gt; to access the power_supply class:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;ps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; PowerSupply&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then get the battery&#39;s voltage at the time of measuring through:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;ps&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;measured_volts&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can have the EV3 give you a warning if the voltage drops below a threshold you specify. For example:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; ps&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;measured_volts &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    ALL_LEDS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Leds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LEFT&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Leds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RIGHT&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; l &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; ALL_LEDS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      Leds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;set_color&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;l&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Leds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RED&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    Sound&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;play&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Feed me. Feed me. Feed me.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read more about the available methods in the &lt;a href=&quot;http://ev3dev-lang.readthedocs.io/projects/python-ev3dev/en/stable/other.html&quot;&gt;ev3dev documentation&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Basic process for running Python scripts on the LEGO Mindstorms EV3</title>
    <link href="https://wilcovanes.ch/articles/basic-process-for-running-python-scripts-on-the-lego-mindstorms-ev3/"/>
    <updated>2017-04-15T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/basic-process-for-running-python-scripts-on-the-lego-mindstorms-ev3/</id>
    <content type="html">&lt;p&gt;The &lt;a href=&quot;http://www.ev3dev.org/docs/getting-started/&quot;&gt;official ev3dev Getting Started guide&lt;/a&gt; provides very clear and thorough instructions on how to load ev3dev (an OS based on Debian Linux which comes with Python bindings included) on your EV3.&lt;/p&gt;
&lt;p&gt;Example setup:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;LEGO Mindstorms EV3 (typical UK price: £229.99)&lt;/li&gt;
&lt;li&gt;SanDisk 16 GB MicroSDHC card (typical UK price: £7.99)&lt;/li&gt;
&lt;li&gt;COTOP Card Reader Hub (typical UK price: £16.99)&lt;/li&gt;
&lt;li&gt;TP-Link TL-WN821N Wi-Fi dongle (typical UK price: £9.87)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once you&#39;re set up with the EV3 on, ev3dev booted, and a connection established (whether it be Ethernet, Wi-Fi, or Bluetooth), you have three options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run commands from a Python shell on ev3dev&lt;/li&gt;
&lt;li&gt;Write a Python script on ev3dev&lt;/li&gt;
&lt;li&gt;Write a Python script on your PC and transfer it to ev3dev&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&#39;s go over the three options.&lt;/p&gt;
&lt;h3&gt;Run commands from a Python shell on ev3dev&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Start an SSH client, such as &lt;a href=&quot;http://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html&quot;&gt;PuTTY&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create a session with:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Host Name: &lt;em&gt;ev3dev&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Port: &lt;em&gt;22&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Make the connection, and when prompted authenticate with:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Username (default): &lt;em&gt;robot&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Password (default): &lt;em&gt;maker&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Start Python with &lt;code&gt;python3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;In the shell, load the ev3dev library with &lt;code&gt;from ev3dev.ev3 import *&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start using &lt;a href=&quot;https://wilcovanes.ch/articles/basic-process-for-running-python-scripts-on-the-lego-mindstorms-ev3/#example-commands&quot;&gt;commands&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Write a Python script on ev3dev&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Start an SSH client, such as &lt;a href=&quot;http://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html&quot;&gt;PuTTY&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create a session with:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Host Name: &lt;em&gt;ev3dev&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Port: &lt;em&gt;22&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Make the connection, and when prompted authenticate with:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Username (default): &lt;em&gt;robot&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Password (default): &lt;em&gt;maker&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Start writing a script with &lt;code&gt;nano example-script.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Begin the script with &lt;code&gt;#!/usr/bin/env python3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Import the ev3dev library with &lt;code&gt;from ev3dev.ev3 import *&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Write your Python script&lt;/li&gt;
&lt;li&gt;To exit and save the script, use &lt;em&gt;CTRL&lt;/em&gt; + &lt;em&gt;X&lt;/em&gt; and select &lt;em&gt;Y&lt;/em&gt;. Press Enter when satisfied with the filename.&lt;/li&gt;
&lt;li&gt;Make the script executable through &lt;code&gt;chmod +x example-script.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Execute the script through &lt;code&gt;python3 example-script.py&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Write a Python script on your PC and transfer it to ev3dev&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Start writing a script in your editor of choice, such as IDLE or &lt;a href=&quot;https://atom.io/&quot;&gt;Atom&lt;/a&gt; or &lt;a href=&quot;https://notepad-plus-plus.org/&quot;&gt;Notepad++&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Begin the script with &lt;code&gt;#!/usr/bin/env python3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Import the ev3dev library with &lt;code&gt;from ev3dev.ev3 import *&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Save the script somewhere on your local drive.&lt;/li&gt;
&lt;li&gt;Launch an FTP client, such as &lt;a href=&quot;https://winscp.net/eng/download.php&quot;&gt;WinSCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the programmable brick, go to &lt;strong&gt;Wireless and Networks&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Navigate to the network you&#39;re connected to&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Status&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Note the IP.&lt;/li&gt;
&lt;li&gt;In your FTP client, create a new connection with:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;File protocol: &lt;em&gt;SFTP&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Host name: the IP you noted down&lt;/li&gt;
&lt;li&gt;Port number: &lt;em&gt;22&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Username (default): &lt;em&gt;robot&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Username (default): &lt;em&gt;maker&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;11&quot;&gt;
&lt;li&gt;Once connected, transfer the file(s) to &lt;code&gt;/home&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Make the script executable through &lt;code&gt;chmod +x example-script.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Execute the script through &lt;code&gt;python3 example-script.py&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Example commands&lt;/h3&gt;
&lt;h4&gt;Assigning the EV3 components to variables&lt;/h4&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;ts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TouchSensor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
cs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ColorSensor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
us &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; UltrasonicSensor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# always to be connected to `in4`&lt;/span&gt;
ir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; InfraredSensor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
gs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; GyroSensor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

l1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; LargeMotor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;outC&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# if connected to `outC`&lt;/span&gt;
l2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; LargeMotor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;outD&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# if connected to `outD`&lt;/span&gt;
mm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MediumMotor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;outA&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# if connected to `outA`&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Text-to-speech&lt;/h4&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;ev3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Sound&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;speak&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Hello. I am a robot.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wait&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Keep active until cancelled&lt;/h4&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# code&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Run a motor for a specific time at a specific speed&lt;/h4&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run_timed&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time_sp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; speed_sp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;time_sp&lt;/strong&gt;: time in milliseconds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;speed_sp&lt;/strong&gt;: tacho ticks per second = 1.4 rotations per second = 1 tick per degree = 500 degrees per second&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Keep running motor&lt;/h4&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run_forever&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;speed_sp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;900&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Run multiple motors at once&lt;/h4&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;l1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; LargeMotor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;outC&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
l2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; LargeMotor&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;outD&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
motors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;l1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; l2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; m &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; motors&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run_forever&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;speed_sp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Stop motors&lt;/h4&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stop&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stop_action &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;coast&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Save debugging messages to a log&lt;/h4&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; logging

logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;basicConfig&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filename&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;runtime.log&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
level&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DEBUG&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; do_something&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  logging&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Starting to do something.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Shell commands&lt;/h3&gt;
&lt;h4&gt;Get kernel version&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;uname -r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The uname command reports basic information about a computer&#39;s software and hardware.
The -r option gives the kernel version number and release level.&lt;/p&gt;
&lt;p&gt;Or, in Brickman, select About and scroll down.&lt;/p&gt;
&lt;h4&gt;Get volume&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;amixer get Playback,0
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Change volume&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;amixer set Playback,0 100%
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Improving your testing by improving your thinking</title>
    <link href="https://wilcovanes.ch/articles/improving-your-testing-by-improving-your-thinking/"/>
    <updated>2017-04-09T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/improving-your-testing-by-improving-your-thinking/</id>
    <content type="html">&lt;p&gt;Testing is an analytical activity. You form running hypotheses about how a product works, what kind of problems could occur, what the cause of an observed problem could be, what the impact of the problem would be to the user and the business, and you verify or falsify one hypothesis after another by gathering and evaluating information.&lt;/p&gt;
&lt;p&gt;Training a tester&#39;s analytical skill is typically done through exercises where the tester is encouraged to generate testing ideas and ask questions. This helps testers be better investigators and is fun to do.&lt;/p&gt;
&lt;p&gt;However, another aspect of being a better investigator is to examine your thinking. Our brains are vulnerable to assumptions, biases, and logical fallacies. The more we&#39;re willing to admit this and learn to recognise them, the less time and effort will be wasted following up on inaccurate notions.&lt;/p&gt;
&lt;h3&gt;Examples from the email marketing world to drive the point home&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;A marketing email is sent out every month. Last month it included a discount code. The analytics show more people unsubscribed from the email than at any other time. A policy is introduced that discount codes should never be used again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When we hint at a discount code in an email subject line the open rate is likely to be higher and accordingly the absolute number of people unsubscribing may be higher whereas the unsubscribe ratio may be the same as usual or in fact lower.&lt;/li&gt;
&lt;li&gt;How do we know other differences in the email send were not responsible for the increased number of unsubscribers?&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;A project team commits to meeting the WCAG 2.0 accessibility guidelines. &lt;a href=&quot;https://www.paciellogroup.com/resources/contrastanalyser/&quot;&gt;Colour Contrast Analyser&lt;/a&gt; is used to determine contrast ratio for meeting success criterion 1.4.3. The tool shows a subheading fails all 4 checks (AA small text, AA large text, AAA small text, AAA large text). The developer is asked to fix this. The developer increases contrast of the font colour versus the background colour. It now fails 3 out of 4 checks. It is reworked again and fails again. Eventually a decision is made that, now and in future campaigns, 1 out of 4 checks should pass.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is no basis for deciding 1 out of 4 checks should pass. What is the font size of the text? Are you committing to AA or AAA level conformance?&lt;/li&gt;
&lt;li&gt;The reason for the guideline is forgotten. It&#39;s to ensure text is legible for people with reduced visual acuity. Tools should be used intelligently to inform whether or not you&#39;ve succeeded in doing this.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Email open statistics show that 7% of users use a new email client with limited CSS support. Considering the CSS issues, the project team says &amp;quot;no-one uses it, we won&#39;t support it&amp;quot;. When faced with the usage statistics, the project team says &amp;quot;we&#39;ll increase the support threshold to 10%&amp;quot;. When faced with the consequence that this means no more support for desktop or web clients, the project team says &amp;quot;what is the usage share when we divide it by OS and OS version?&amp;quot;. This goes on until the data supports the decision.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The decision to support an email client should be based on the data rather than the other way around.&lt;/li&gt;
&lt;li&gt;If you know you can&#39;t support something, justify it by the cost of trying instead of with massaged data. You can then offer it as a choice to the business: this % of additional users catered for versus this % of additional development and testing cost and effort.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The respective thinking errors in these examples&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Illusory correlation&lt;/em&gt;. Our brains allow us to act quickly on a perceived correlation. We saw a connection, a plausible explanation, and acted. Yet, since it was not a life-or-death situation, we could have investigated the issue. Not sending discount codes might mean we weaken the conversion rate. It would have been worth looking into, even if it would end up leading to the same conclusion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Special pleading&lt;/em&gt;. An exception is made without a reasonable justification for that exception. This, in turn, is because of an arbitrary goalpost. Which, in turn, is caused by an ill-understood requirement. If as a tester you report issues identified by a tool, you should learn why they are issues.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Motivated reasoning&lt;/em&gt;. You start from the conclusion and try to justify it instead of letting the evidence guide you. In the example, bad decisions are eagerly suggested and distract from the obvious solution: to present the forbiddingly high cost of supporting a difficult email client to the business stakeholder and let them either agree to exclude it or accept the additional budget and delays.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We should be aware of logical fallacies such as these to be able to monitor our own thinking and make better decisions. This applies to everyone, of course, not just to testers.&lt;/p&gt;
&lt;p&gt;In addition all the examples could be said to have an element of knowledge insufficiency. A tester benefits from learning about the product domain, the technical underpinnings of the product, the business strategy, and linguistic knowledge (language comprehension and expression).&lt;/p&gt;
&lt;h3&gt;Further reading&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_fallacies&quot;&gt;Wikipedia has a good master list of logical fallacies&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.researchgate.net/profile/Fuqun_Huang/publication/270160662_A_Taxonomy_System_to_Identify_Human_Error_Causes_for_Software_Defects/links/54a198dd0cf256bf8baf75d1/A-Taxonomy-System-to-Identify-Human-Error-Causes-for-Software-Defects.pdf&quot;&gt;A publication from Beihang University provides a taxonomy of human error causes for software defects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.amazon.co.uk/dp/B000SEJ89W/&quot;&gt;James Reason covers rational processing errors in his book Human Error&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>400 Bad Request for a POST request to a WCF service</title>
    <link href="https://wilcovanes.ch/articles/400-bad-request-for-a-post-request-to-a-wcf-service/"/>
    <updated>2017-03-30T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/400-bad-request-for-a-post-request-to-a-wcf-service/</id>
    <content type="html">&lt;h3&gt;Problem&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{&amp;quot;ErrorDetail&amp;quot;:&amp;quot;Incoming messages must have a body format
of Raw or Xml. Is a ContentTypeMapper set on the
WebHttpBinding?&amp;quot;,&amp;quot;Status&amp;quot;:&amp;quot;INVALID_ARGUMENTS&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Background&lt;/h3&gt;
&lt;p&gt;A POST request to a WCF service through Postman (though a request through curl gives the same result).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://client-webservice.provider.com/WebService/api/rest/
category/getInterestingDetails?ParamID=123456)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Username and password are provided through Basic Auth, which adds another param in addition to the &lt;em&gt;ParamID&lt;/em&gt; example parameter.&lt;/p&gt;
&lt;p&gt;The service can return JSON. Content-Type is set to application/json.&lt;/p&gt;
&lt;h3&gt;Solution&lt;/h3&gt;
&lt;p&gt;Solution in my case (in Postman) was to empty the params beyond the authentication details and place those parameters in the raw request body instead.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Access-Control-Allow-Origin header JS issue in Watir</title>
    <link href="https://wilcovanes.ch/articles/access-control-allow-origin-header-js-issue-in-watir/"/>
    <updated>2017-03-28T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/access-control-allow-origin-header-js-issue-in-watir/</id>
    <content type="html">&lt;h3&gt;Problem&lt;/h3&gt;
&lt;p&gt;Using Watir to drive a Chrome browser, I found a button was not clickable which was clickable just fine when driving Firefox or when starting Chrome directly rather than through Watir.&lt;/p&gt;
&lt;p&gt;The Chrome Developer Tools console displayed these JS errors:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://wilcovanes.ch/assets/img/access-control-allow-origin-js-error.png&quot; alt=&quot;No Access-Control-Allow-Origin header present on the requested resource&quot;&gt;&lt;/figure&gt;
&lt;h3&gt;Cause&lt;/h3&gt;
&lt;p&gt;It looks like Chrome does not trust the POST request the button triggers, since it did not originate from the same domain. In our case that&#39;s just because we&#39;re testing from localhost.&lt;/p&gt;
&lt;h3&gt;Solution&lt;/h3&gt;
&lt;p&gt;To do this POST request you&#39;d need to add the Access-Control-Allow-Origin header to the request, but we don&#39;t want to muck around with custom requests. Instead, for local testing purposes, we can make Chrome stop enforcing the same-origin policy.&lt;/p&gt;
&lt;p&gt;If you look at the &lt;a href=&quot;https://sites.google.com/a/chromium.org/chromedriver/capabilities&quot;&gt;ChromeDriver Capabilities&lt;/a&gt; under &lt;strong&gt;chromeOptions object&lt;/strong&gt;, you will see you can feed &lt;strong&gt;args&lt;/strong&gt; into Chrome when starting it.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://peter.sh/experiments/chromium-command-line-switches/&quot;&gt;full list of possible arguments you can send along&lt;/a&gt; includes &lt;code&gt;--disable-web-security&lt;/code&gt;. This sounds dodgy, but is fine for our local testing.&lt;/p&gt;
&lt;h3&gt;Using it in Watir&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;@browser&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Watir&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Browser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:chrome&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:switches&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;--disable-websecurity&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the switches have to be provided as an array of strings, even if it&#39;s a single value.&lt;/p&gt;
&lt;h3&gt;Real life use case&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;Before &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;token variable&quot;&gt;@browser&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Watir&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Browser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:chrome&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:switches&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
	&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;%w(--ignore-certificate-errors --disable-web-security
	--disable-translate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(In Ruby, &lt;code&gt;%w(foo bar)&lt;/code&gt; is equivalent to &lt;code&gt;[&amp;quot;foo&amp;quot;, &amp;quot;bar&amp;quot;]&lt;/code&gt;.)&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Regression testing SOAP web services using SoapUI</title>
    <link href="https://wilcovanes.ch/articles/regression-testing-soap-web-services-using-soapui/"/>
    <updated>2017-03-22T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/regression-testing-soap-web-services-using-soapui/</id>
    <content type="html">&lt;p&gt;Before you can get started you will need:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The WSDL document describing the web service, e.g. https://client-uat-webservice.com/WebService/api/service.svc?wsdl&lt;/li&gt;
&lt;li&gt;Any required credentials to access the web services&lt;/li&gt;
&lt;li&gt;Any additional parameter test values you may need to send along with your requests (e.g. If you&#39;re going to access a method for finding a user by their member id, you&#39;ll of course need member id test values).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In SoapUI, start a new project&lt;/li&gt;
&lt;li&gt;In the SoapUI project, load in the WSDL to retrieve the API endpoints and automatically generate requests&lt;/li&gt;
&lt;li&gt;For each SOAP request, in the &lt;strong&gt;Request Properties&lt;/strong&gt; select a path for &lt;strong&gt;Dump File&lt;/strong&gt; so as to store the response&lt;/li&gt;
&lt;li&gt;Execute the SOAP requests&lt;/li&gt;
&lt;li&gt;Verify the responses are as expected&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The resultant response logs are your baseline.&lt;/p&gt;
&lt;p&gt;After an update takes place which shouldn&#39;t affect the web service:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure you&#39;ve moved the previous results to a separate location, so that you won&#39;t override them&lt;/li&gt;
&lt;li&gt;Re-run the same suite of SOAP requests&lt;/li&gt;
&lt;li&gt;Verify the responses returned match your baseline responses&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also useful:&lt;/p&gt;
&lt;h3&gt;Creating a dynamic Dump File path&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Select your TestSuite&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Custom Properties&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Add a new Property&lt;/li&gt;
&lt;li&gt;Add Property &lt;strong&gt;Name&lt;/strong&gt;: &lt;em&gt;OutputPath&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Add Property &lt;strong&gt;Value&lt;/strong&gt;: path to your project workspace, e.g. &lt;em&gt;C:/SoapUI/ClientAPI/&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;For each Test Step where you want to save the response to file:
&lt;ol&gt;
&lt;li&gt;Select the &lt;strong&gt;Test Step&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;TestRequest Properties&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For the &lt;strong&gt;Dump File&lt;/strong&gt; property, enter &lt;em&gt;${#TestSuite#OutputPath}${=request.name}.xml&lt;/em&gt;
(reads the OutputPath property in the Test Suite scope, appends the name of the current request, and appends an extension)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now when you execute a Test Cases, files will be generated in the selected location.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>&quot;RbReadline: HOME&quot; environment variable issue on Windows</title>
    <link href="https://wilcovanes.ch/articles/rbreadline-home-environment-variable-issue-on-windows/"/>
    <updated>2017-03-20T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/rbreadline-home-environment-variable-issue-on-windows/</id>
    <content type="html">&lt;p&gt;Using Ruby on Windows, you may receive this error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:/Ruby23-x64/lib/ruby/site_ruby/2.3.0/
rbreadline.rb:1097:in `&amp;lt;module:RbReadline&amp;gt;&#39;:
HOME environment variable (or HOMEDRIVE and HOMEPATH)
must be set and point to a directory (RuntimeError)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix it, check your HOME, HOMEDRIVE, and HOMEPATH environment variables. To list them in a command prompt, type:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set home
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example result:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HOME=C:&#92;windows&#92;system32&#92;config&#92;systemprofile
HOMEDRIVE=C:
HOMEPATH=&#92;Users&#92;firstname.lastname
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HOMEDRIVE + HOMEPATH (e.g. C:&#92;Users&#92;firstname.lastname) would have to be a valid path that you can access and which doesn&#39;t have special characters in firstname or lastname.&lt;/p&gt;
&lt;p&gt;The HOME path also needs to be a valid path that you can access. In my case, I found that accessing the path required administrator rights. I had to go to the config folder, gain access by entering admin details, go to the systemprofile folder, gain access by entering admin details. Then I tried irb and was able to start it without issue.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Render comma-separated partials for an array of objects in Rails</title>
    <link href="https://wilcovanes.ch/articles/render-comma-separated-partials-for-an-array-of-objects-in-rails/"/>
    <updated>2017-03-18T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/render-comma-separated-partials-for-an-array-of-objects-in-rails/</id>
    <content type="html">&lt;p&gt;Say you have Posts which have Tags associated with them. In the Post index view you display all Posts, with the Tags for each.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Title: Cities of Africa
Tags: Cape TownFesNairobiKigaliKhartoum

Title: Cities of South America
Tags: Buenos AiresRio de JaneiroQuitoSantiagoBogotá
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ll notice a problem here. All the tags are listed without any kind of separation. The reason is because in the view we are rendering a partial for each tag whereby each partial simply lists and links the tag name, and these are then listed out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tags&#92;_tag.html.erb&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;%= link_to tag.name, tag_path(tag) %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;posts&#92;index.html.erb&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%=&lt;/span&gt; render post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tags &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To solve this problem, We can&#39;t just join the tags through &lt;code&gt;render post.tags.join(&amp;quot;, &amp;quot;)&lt;/code&gt;. That would make Rails look for a partial by the name of our joined tags. It would work without the partials through &lt;code&gt;post.tags.join(&amp;quot;, &amp;quot;)&lt;/code&gt;, but to keep using our partial we need to join the tags separately per post.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;posts&#92;index.html.erb&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%=&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; render t &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;, &quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;html_safe &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us a nicely comma-separated list, whilst maintaining the tag links.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Title: Cities of Africa
Tags: Cape Town, Fes, Nairobi, Kigali, Khartoum

Title: Cities of South America
Tags: Buenos Aires, Rio de Janeiro, Quito, Santiago, Bogotá
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Five fundamentals in devising a Support Matrix</title>
    <link href="https://wilcovanes.ch/articles/five-fundamentals-in-devising-a-support-matrix/"/>
    <updated>2017-03-07T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/five-fundamentals-in-devising-a-support-matrix/</id>
    <content type="html">&lt;p&gt;When designing your compatibility testing strategy, you&#39;re likely to formalise the environments that are going to be developed and tested against. You may call the result your Support Matrix, or Browser Compatibility Matrix, or simply your Supported Environments.&lt;/p&gt;
&lt;p&gt;Here are five of the fundamentals in reaching an effective Support Matrix.&lt;/p&gt;
&lt;h3&gt;Use targeted tracking data to inform your decisions&lt;/h3&gt;
&lt;p&gt;An obvious one, yet you often see a Support Matrix which simply lists the newest devices and software versions out in the market at the time of writing. Yet new devices or software can take a long time to reach the same uptake as their predecessors (Android 7 for example), and if one of your (or your client&#39;s) primary markets is in, say, China, you&#39;re going to make your product harder or impossible to use for a significant portion of potential users if you don&#39;t take older versions of Internet Explorer or Android into account.&lt;/p&gt;
&lt;p&gt;It&#39;s all about the &lt;em&gt;size&lt;/em&gt; of the &lt;em&gt;quality&lt;/em&gt; audience you &lt;em&gt;can&lt;/em&gt; cater for. Meaning your product is going to be more successful the more users you&#39;re able to present with a great experience — where those users are the ones you care about (for example, those likely to convert) — in relation to the development and testing effort.&lt;/p&gt;
&lt;p&gt;So if the tracking data for a client&#39;s existing digital presence in a market has a significant and stable percentage of users using Internet Explorer on an older version of Windows, there is value in putting development and testing time against it. It would make sense to ignore minor rendering issues which do not obscure content, and in some cases even to create a simplified version to direct older platforms to, but you cannot simply ignore an older platform because it is old.&lt;/p&gt;
&lt;p&gt;For the same reasons, if a vendor stops support for their product, that in itself doesn&#39;t provide adequate justification for dropping it from your Support Matrix (unless you work for that vendor). If people keep using it - remember how corporations kept paying for Windows XP support after the official end of support - it could be valuable or essential to keep accommodating those users for a while longer if they are indeed your users.&lt;/p&gt;
&lt;p&gt;When you have tracking data available, use it. Don&#39;t say &amp;quot;nobody uses device X&amp;quot; if you only know what you and your friend use. Evidence first.&lt;/p&gt;
&lt;h3&gt;Don&#39;t support everything&lt;/h3&gt;
&lt;p&gt;Not everyone involved in a project might be aware of the sheer number of combinations of devices, OS versions, browser versions, email clients, etc. there exist in the world. They might expect a product to get tested against a nebulous Everything. It may be wise to pre-empt this by communicating your support strategy, conveying how you use targeted data to maximise return on investment.&lt;/p&gt;
&lt;p&gt;One tactic for reducing the number of supported environments is to establish a support threshold. You could employ an arbitrary but useful cut-off point of, for example, 5% of total usage share and support only those environments which are used by at least 5% of your target audience. Where you put this threshold depends on how much your organisation or client are willing to spend on accommodating their users and how much it would cost you to do so.&lt;/p&gt;
&lt;p&gt;In some cases you&#39;ll want to advocate for comprehensive support more strongly than in others. A global eCommerce web build is a different beast than a static HTML5 banner.&lt;/p&gt;
&lt;h3&gt;Understand the technology&lt;/h3&gt;
&lt;p&gt;If the two most used devices in your tracking data are iPhone 6s and iPhone 7, it&#39;s a given that you&#39;d support both, right? Not necessarily. iPhone 6, 6s, and 7 have the same screen size, screen resolution, and pixel ratio. If you&#39;re testing emails, that should tell you they are essentially identical if the iOS version is the same. In this case, supporting iPhone 7 would also cover iPhone 6s and 6. In the context of a demanding mobile app, however, the minor differences in hardware capabilities could be important.&lt;/p&gt;
&lt;p&gt;Understanding the devices, browsers, and OSes in your tracking data will help you decide which to support and how to support them.&lt;/p&gt;
&lt;p&gt;Another example is browser auto-updating. Chrome, Firefox, and Edge (through Windows Updates) update themselves. Whilst it is possible to be behind on an update or to disable auto-updating altogether, the vast majority of users will be moved to a new version of these browsers whenever it gets a new stable release. This should tell you your Support Matrix can be out of date as soon as you create it if you include specific versions of these browsers. Either support the latest version or - if you have the need and capacity - the latest and the version before it.&lt;/p&gt;
&lt;p&gt;There are numerous more examples. Aside from learning about the technology on the environments people use, also learn about the technology you use to develop the product, and the technology you use to gather your tracking data.&lt;/p&gt;
&lt;h3&gt;Share with your project team&lt;/h3&gt;
&lt;p&gt;Having a Support Matrix to test against is going to be a painful experience if the product under test wasn&#39;t developed to support the same environments. We&#39;re not playing a game of &amp;quot;guess what rules I will fail your work against&amp;quot;, we&#39;re working together to get something of value out the door. Before development even starts, it should be understood by everyone what environments the product will need to deliver a great experience in (whatever that means in your project) and to what extent.&lt;/p&gt;
&lt;p&gt;Make sure you get involved in kick-offs for projects and ensure you have or get access to appropriate tracking data to base a Support Matrix on. Keep an eye on resource requests for your design department. What they are working on is likely to go into development soon.&lt;/p&gt;
&lt;p&gt;If the work is for a client, also find out what environment(s) they are going to use when signing off on the deliverables. Then either add that to your supported environments or request that the client stakeholders use an environment that fits what their target audience use. Even if you do, experience shows that someone will pop up from somewhere with a list of issues in an unsupported environment. When that happens, you&#39;ll be happier if your account handler is already equipped to handle it, namely by understanding your explanation on why only certain environments are supported and what it&#39;s going to cost if they want to add additional support retroactively.&lt;/p&gt;
&lt;h3&gt;Actually be able to support them&lt;/h3&gt;
&lt;p&gt;What if 12% of the target audience use a low spec Android tablet and you don&#39;t have one? Do you sell it as &amp;quot;88% of people DON&#39;T use it&amp;quot; and ignore it? You could, but not having it is a weak justification for not supporting it. Either you exclude it for a valid reason or you find a way of covering the environment with a representative device.&lt;/p&gt;
&lt;p&gt;There are many options for building compatibility testing capacity.&lt;/p&gt;
&lt;p&gt;You can create a physical test lab, with a range of test desktops, virtual machines, laptops, and mobile devices which you manage yourself in terms of software versions, configuration, purchasing, and availability.&lt;/p&gt;
&lt;p&gt;You can use cloud-based platforms like &lt;a href=&quot;https://www.browserstack.com/&quot;&gt;BrowserStack&lt;/a&gt; or &lt;a href=&quot;https://saucelabs.com/&quot;&gt;Sauce Labs&lt;/a&gt; (or a number of other options) for desktop and mobile browsers or &lt;a href=&quot;https://litmus.com/&quot;&gt;Litmus&lt;/a&gt; or &lt;a href=&quot;https://www.emailonacid.com/&quot;&gt;Email on Acid&lt;/a&gt; (or a few other options) for email clients.&lt;/p&gt;
&lt;p&gt;You can use a 3rd party testing service provider who already have a mature physical or cloud-based test lab.&lt;/p&gt;
&lt;p&gt;You can use a 3rd party testing service provider who have a distributed network of testers they can use to cover any range of environments.&lt;/p&gt;
&lt;p&gt;Or you can use a combination of these. The world is your oyster, if you can afford it.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Examples of LUA usage in LoadImpact test scenarios</title>
    <link href="https://wilcovanes.ch/articles/examples-of-lua-usage-in-loadimpact-test-scenarios/"/>
    <updated>2017-03-07T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/examples-of-lua-usage-in-loadimpact-test-scenarios/</id>
    <content type="html">&lt;h3&gt;GET request&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;http.request_batch({
	{&amp;quot;GET&amp;quot;, &amp;quot;http://subdomain.domain.co.uk/&amp;quot;,
  auto_decompress=true}
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To take the effort out of gathering all relevant page requests, you can use LoadImpact&#39;s User Scenario Recorder and manually adjust the requests.&lt;/p&gt;
&lt;h3&gt;POST request&lt;/h3&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request_batch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;https://subdomain.domain.co.uk/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	headers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;X-Requested-With&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;XMLHttpRequest, XMLHttpRequest&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&quot;application&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;
	x&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;www&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;form&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;urlencoded&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; charset&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;UTF&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&quot;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&quot;X&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Requested&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;With&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  XMLHttpRequest&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;nonJsSearch&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;searchTerm&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&quot;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; auto_decompress&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Log in&lt;/h3&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request_batch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;https://subdomain.domain.co.uk/AjaxSignIn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	headers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;X-Requested-With&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&quot;XMLHttpRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	XMLHttpRequest&lt;span class=&quot;token string&quot;&gt;&quot;,[&quot;&lt;/span&gt;Content&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Type&quot;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
	&quot;application&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;x&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;www&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;form&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;urlencoded&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  charset&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;UTF&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&quot;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&quot;Password&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;userpassword&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;RememberMe&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;SignInUrl&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;2F&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;UserName&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;useremail
  &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;X&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Requested&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;With&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;XMLHttpRequest&quot;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  auto_decompress&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Sleep&lt;/h3&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course the duration can be adjusted at will. It is measured in seconds.&lt;/p&gt;
&lt;h3&gt;Identify the boundaries of a page metric.&lt;/h3&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;page_start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Page 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

page requests go here

http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;page_end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Page 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Looping&lt;/h3&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; loopCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
	something ten times
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; loopCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
	something ten times&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; enumerated as &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt; to &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;String interpolation&lt;/h3&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; loopCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
	http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;page_start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Page .. loopCount .. &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	page requests go here
	http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;page_end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Page .. loopCount ..&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Redirect HTTP &amp;amp; HTTPS traffic to specific server IP&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;util.dns_remap(&amp;quot;http://subdomain.domain.co.uk&amp;quot;,
&amp;quot;95.95.95.95&amp;quot;)
util.dns_remap(&amp;quot;http://subdomain.domain.co.uk:443&amp;quot;,
&amp;quot;95.95.95.95&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Preventing SessionNotCreatedError in WebDriver when using Appium</title>
    <link href="https://wilcovanes.ch/articles/preventing-sessionnotcreatederror-in-webdriver-when-using-appium/"/>
    <updated>2017-02-21T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/preventing-sessionnotcreatederror-in-webdriver-when-using-appium/</id>
    <content type="html">&lt;p&gt;Example error (for test using Appium with Selenium WebDriver and Rspec) when a previous test was aborted before a clean exit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Failures:

  1) The APPNAME app displays SCREEN when user selects OPTION
  Failure/Error: driver =
  Appium::Driver.new(options).start_driver

     Selenium::WebDriver::Error::SessionNotCreatedError:
       Requested a new session but one was in progress
     # ./spec/spec_helper.rb:20:in `block (2 levels)
       in &amp;lt;top (required)&amp;gt;&#39;

Finished in 0.06314 seconds
(files took 0.73578 seconds to load)
1 example, 1 failure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To prevent this error in subsequent test runs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stop the Appium server if it is running&lt;/li&gt;
&lt;li&gt;In Appium, go to &lt;strong&gt;Preferences&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;General&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;Override Existing Sessions&lt;/strong&gt; checkbox&lt;/li&gt;
&lt;li&gt;Start the Appium server&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Finding the appPackage and appActivity values for an app on a connected device</title>
    <link href="https://wilcovanes.ch/articles/finding-the-apppackage-and-appactivity-values-for-an-app-on-a-connected-device/"/>
    <updated>2017-02-21T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/finding-the-apppackage-and-appactivity-values-for-an-app-on-a-connected-device/</id>
    <content type="html">&lt;p&gt;Say you&#39;re looking to run Appium tests against an app on an Android device connected to your desktop and you need to know the appPackage and appActivity values for the app.&lt;/p&gt;
&lt;p&gt;Assuming you have the Android SDK installed (and you have the SDK&#39;s &lt;code&gt;platform-tools&lt;/code&gt; directory in your &lt;code&gt;PATH&lt;/code&gt; environment variable, if you&#39;re on Windows), you can run the following from the command line.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;adb devices&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This should return the hostname of the device you have connected.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;adb shell&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This will allow us to send commands to the device.&lt;/p&gt;
&lt;p&gt;Make sure that the app under test is started and in focus on the device, then run the following.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dumpsys window windows | grep -E ‘mCurrentFocus|mFocusedApp’&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mCurrentFocus=Window{31ee112 u0 d0
com.Client.Appname/com.unity3d.player.
UnityPlayerNativeActivity}
mFocusedApp=AppWindowToken{b5717d6 token=Token{65b7ff1
ActivityRecord{2c5b98 u0
com.Client.Appname/com.unity3d.player.
UnityPlayerNativeActivity t39}}}`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this example, the values for appPackage and appActivity are:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;appPackage&lt;/strong&gt;: com.Client.Appname&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;appActivity&lt;/strong&gt;: com.unity3d.player.UnityPlayerNativeActivity&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Google Analytics API requests using Ruby on Windows</title>
    <link href="https://wilcovanes.ch/articles/google-analytics-api-requests-using-ruby-on-windows/"/>
    <updated>2017-01-18T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/google-analytics-api-requests-using-ruby-on-windows/</id>
    <content type="html">&lt;p&gt;The hardest part of making Google API requests via Ruby on Windows is authentication. For this simple example, we&#39;ll use an API key rather than an OAuth 2.0 process.&lt;/p&gt;
&lt;h3&gt;Retrieve the API key&lt;/h3&gt;
&lt;p&gt;These steps assume you have a project folder including a Gemfile, and that you have a browser open and are logged in with the Google account for the Google Analytics you&#39;re interested in.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In your Gemfile, add &lt;code&gt;gem &#39;google-api-client&#39;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;bundle install&lt;/code&gt; from a command prompt&lt;/li&gt;
&lt;li&gt;From the &lt;a href=&quot;https://console.developers.google.com&quot;&gt;API Manager&lt;/a&gt;, select &lt;strong&gt;Enable API&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Analytics API&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;In the top bar, click &lt;strong&gt;Project&lt;/strong&gt; (or &lt;em&gt;Name of project&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;From the dropdown, select &lt;strong&gt;Create project&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enter a name and click &lt;strong&gt;Create&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;From the IAM &amp;amp; Admin menu, select &lt;strong&gt;Service accounts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create service account&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enter a name for the service account&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Role&lt;/strong&gt;, select &lt;strong&gt;Project&lt;/strong&gt; followed by &lt;strong&gt;Viewer&lt;/strong&gt; (for the purposes of this example, since we&#39;re not interested in making changes)&lt;/li&gt;
&lt;li&gt;Copy the service account ID to Notepad&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;Furnish a new private key&lt;/strong&gt; checkbox&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Add the downloaded private key to your project folder&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Allow service account to access your Google Analytics&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;In Google Analytics, select &lt;strong&gt;Admin&lt;/strong&gt; (has cog icon)&lt;/li&gt;
&lt;li&gt;Select the account you want to be using&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;User Management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Take the service account ID from Notepad and add it to the &lt;strong&gt;Add permission for&lt;/strong&gt; input field&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Get Mozilla&#39;s CA Bundle certificate&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to &lt;code&gt;C:&#92;Ruby23-x64&#92;lib&#92;ruby&#92;2.3.0&#92;rubygems&#92;ssl_certs&lt;/code&gt; &lt;em&gt;(adjust to fit your Ruby install path)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Download &lt;a href=&quot;https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt&quot;&gt;Mozilla&#39;s CA Bundle Certificate&lt;/a&gt; as &lt;strong&gt;ca-bundle.crt&lt;/strong&gt; into the folder&lt;/li&gt;
&lt;li&gt;Assuming &lt;code&gt;openssl&lt;/code&gt; is in your &lt;code&gt;PATH&lt;/code&gt;, from the command line, in the same folder, run:
&lt;code&gt;openssl x509 -in ca-bundle.crt -out MozBundleCA.pem -outform PEM&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If &lt;code&gt;openssl&lt;/code&gt; is not in your &lt;code&gt;PATH&lt;/code&gt;, ensure &lt;code&gt;openssl&lt;/code&gt; is installed and add the installation directory (e.g. &lt;code&gt;C:&#92;openssl&#92;bin&lt;/code&gt;) to your &lt;code&gt;PATH&lt;/code&gt; environment variable. Make sure you restart your command prompt afterwards, so that the environment variables are reloaded.&lt;/p&gt;
&lt;h3&gt;Create a simple example script&lt;/h3&gt;
&lt;p&gt;Create a new &lt;code&gt;.rb&lt;/code&gt; file and add the following dependency:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;google/apis/analytics_v3&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Google Analytics, find the view you want to retrieve data for by navigating through Accounts &amp;gt; Properties &amp;amp; Apps &amp;gt; Views. It will have an 8 digit number. Replace the hash signs below with this number and add it to your script.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;account &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;ga:########&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add the following variables:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;start_date &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;today &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strftime&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y-%m-%d&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s
end_date &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;today&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s
metric_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;%w(ga:sessions)&lt;/span&gt;&lt;/span&gt;
dimension_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;%w(ga:screenResolution)&lt;/span&gt;&lt;/span&gt;
filter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;ga:deviceCategory=~mobile&#39;&lt;/span&gt;&lt;/span&gt;
sort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;-ga:sessions&#39;&lt;/span&gt;&lt;/span&gt;
results &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will give us the number of sessions per screen resolution in the last 90 days for mobile devices only, from most used to least used, limited to 10 results once we do the following:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;resolutions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get_ga_data&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;account&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; start_date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
end_date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metric_list&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;dimensions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; dimension_list&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token symbol&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; filter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; sort&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can do anything you want with the resolutions variable, and build something new using the documentation for the &lt;a href=&quot;https://github.com/google/google-api-ruby-client&quot;&gt;Google API Ruby Client&lt;/a&gt; and the &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/v4/&quot;&gt;Google Analytics API&lt;/a&gt;. Note that my example uses v3 and the API has since moved on to v4. However, v3 is still supported.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Receiving email via SendGrid and Namecheap</title>
    <link href="https://wilcovanes.ch/articles/receiving-email-via-sendgrid-and-namecheap/"/>
    <updated>2017-01-09T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/receiving-email-via-sendgrid-and-namecheap/</id>
    <content type="html">&lt;p&gt;If you have a domain with Namecheap and want to relay messages received on that domain to SendGrid so you can then process them as objects (for example via Griddler in a Rails app), read on.&lt;/p&gt;
&lt;h3&gt;SendGrid&lt;/h3&gt;
&lt;p&gt;We&#39;ll whitelabel your domain, then we&#39;ll return to set up an inbound parse.&lt;/p&gt;
&lt;h4&gt;Whitelabel&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Log in to your SendGrid account (or sign up, if you don&#39;t have one)&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Whitelabels&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add Domain&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Choose a subdomain, e.g. &lt;em&gt;email&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Enter your domain, e.g. &lt;em&gt;examplesite.com&lt;/em&gt; (no protocol)&lt;/li&gt;
&lt;li&gt;Allow &lt;strong&gt;Automated Security&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This will need to be validated, after we&#39;ve configured Namecheap and the DNS updates have propagated.&lt;/p&gt;
&lt;h3&gt;Namecheap&lt;/h3&gt;
&lt;p&gt;First, go to your advanced DNS settings. Steps below if needed.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log in to your Namecheap account&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Account&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Dashboard&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Manage&lt;/strong&gt; for the domain you want to use&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Advanced DNS&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You may already have some host records, for example a CNAME record for www and a URL Redirect Record for @. Either way, we can add some additional ones without conflict. Add the following records, replacing the X and # characters with your own details from SendGrid.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;TTL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CNAME Record&lt;/td&gt;
&lt;td&gt;email.&lt;/td&gt;
&lt;td&gt;X#######.XX###.sendgrid.net.&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNAME Record&lt;/td&gt;
&lt;td&gt;s1._domainkey&lt;/td&gt;
&lt;td&gt;s1.domainkey.X#######.XX###.sendgrid.net.&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNAME Record&lt;/td&gt;
&lt;td&gt;s2._domainkey&lt;/td&gt;
&lt;td&gt;s2.domainkey.X#######.XX###.sendgrid.net.&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The full stop placement is essential.&lt;/p&gt;
&lt;p&gt;Now we need to set an MX Record. Under &lt;strong&gt;Mail Settings&lt;/strong&gt; select &lt;strong&gt;Custom MX&lt;/strong&gt; and add the following record.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Mail Server&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;TTL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MX Record&lt;/td&gt;
&lt;td&gt;@&lt;/td&gt;
&lt;td&gt;mx.sendgrid.net.&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Note that here I&#39;m relaying &lt;em&gt;all&lt;/em&gt; email to SendGrid. This is not necessarily what you want to do.&lt;/p&gt;
&lt;p&gt;Once you&#39;ve added these records, it can take up to 48 hours for the changes to propagate. In my case it took about 2 minutes. For a quick check on whether you&#39;re there or not, you can use MxToolbox&#39;s &lt;a href=&quot;https://mxtoolbox.com/&quot;&gt;MX Lookup&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Inbound Parse&lt;/h4&gt;
&lt;p&gt;Back in the SendGrid settings, make sure you validate the Whitelabel settings. If you don&#39;t get a nice green checkmark, either the settings in Namecheap and SendGrid are incorrect or you need to be patient for the DNS changes to propagate.&lt;/p&gt;
&lt;p&gt;Once validated:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Inbound Parse&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add Host &amp;amp; URL*&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Leave the subdomain empty&lt;/li&gt;
&lt;li&gt;For domain, select your whitelabelled domain&lt;/li&gt;
&lt;li&gt;As destination URL, choose the URL on your domain which is going to handle incoming emails, e.g. &lt;code&gt;http://www.examplesite.com/email_processor&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now repeat these steps again, but with &lt;code&gt;www&lt;/code&gt; for the subdomain.&lt;/p&gt;
&lt;p&gt;Once done, you can test everything is working by sending an email to your domain. In SendGrid, this should then show up in the Activity Feed (&lt;strong&gt;Activity&lt;/strong&gt; menu item).&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Configuring the wkhtmltoimage path for IMGKit on Windows</title>
    <link href="https://wilcovanes.ch/articles/configuring-the-wkhtmltoimage-path-for-imgkit-on-windows/"/>
    <updated>2016-10-14T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/configuring-the-wkhtmltoimage-path-for-imgkit-on-windows/</id>
    <content type="html">&lt;p&gt;If you&#39;d like to use &lt;a href=&quot;https://github.com/csquared/IMGKit&quot;&gt;IMGKit&lt;/a&gt;, you&#39;ll also have to install &lt;a href=&quot;http://wkhtmltopdf.org/downloads.html&quot;&gt;wkhtmltopdf&lt;/a&gt;, which includes wkhtmltoimage - a command line tool to generate images from an HTML page.&lt;/p&gt;
&lt;p&gt;From IMGKit&#39;s documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&#39;re on Windows or you installed wkhtmltoimage by hand to a location other than /usr/local/bin
you will need to tell IMGKit where the binary is.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you&#39;re using Rails, you can define this in config/initializers/imgkit.rb
If you&#39;re not using Rails, you can use the same definition where you&#39;re requiring imgkit.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IMGKit.configure do |config|
  config.wkhtmltoimage = &#39;/path/to/wkhtmltoimage&#39;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Windows, that path has to include the filename and extension. If you don&#39;t add it, you&#39;ll receive a NoExecutableError or a &amp;quot;Permission Denied&amp;quot; error.&lt;/p&gt;
&lt;p&gt;Example path:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C:&#92;&#92;Program Files&#92;&#92;wkhtmltopdf&#92;&#92;bin&#92;&#92;wkhtmltoimage.exe&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can test the configuration in irb like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;irb(main):003:0&amp;gt; html = &amp;quot;http://example.com/index.html&amp;quot;
=&amp;gt; &amp;quot;http://example.com/index.html&amp;quot;

irb(main):004:0&amp;gt; kit = IMGKit.new(html, :quality =&amp;gt; 50)
=&amp;gt; #&amp;lt;IMGKit:0x00000002fa0120
@source=#&amp;lt;IMGKit::Source:0x00000002fa00d0
@source=&amp;quot;http://example.com/index.html&amp;quot;&amp;gt;, @stylesheets=[],
@javascripts=[], @options={:height=&amp;gt;0, :quality=&amp;gt;50}&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it doesn&#39;t work, try adding the path to your PATH environment variable.&lt;/p&gt;
&lt;p&gt;If it still doesn&#39;t work, you can amend the path in the gem&#39;s configuration file directly.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# C:&#92;Ruby##-x64&#92;lib&#92;ruby&#92;gems&#92;#.#.#&#92;
gems&#92;imgkit-#.#.#&#92;lib&#92;imgkit&#92;configuration.rb
def wkhtmltoimage
  @wkhtmltoimage ||= begin
    path = (using_bundler? ? `bundle exec which wkhtmltoimage`
    : `which wkhtmltoimage`).chomp
    path =
    &#39;C:&#92;&#92;Program Files&#92;&#92;wkhtmltopdf&#92;&#92;bin&#92;&#92;wkhtmltoimage.exe&#39;
    if path.strip.empty?
# Fallback
    path
  end
end
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Using RSpec to verify a result matches one of multiple expectations</title>
    <link href="https://wilcovanes.ch/articles/using-rspec-to-verify-a-result-matches-one-of-multiple-expectations/"/>
    <updated>2016-09-29T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/using-rspec-to-verify-a-result-matches-one-of-multiple-expectations/</id>
    <content type="html">&lt;p&gt;Say you want to verify that a method returns either &#39;Yes&#39; or &#39;Maybe&#39;.&lt;/p&gt;
&lt;p&gt;You could of course do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;valid = [&amp;quot;Yes&amp;quot;, &amp;quot;Maybe&amp;quot;]
expect(valid).to include(response)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This turns the expectation around to check that the valid responses include the returned value.&lt;/p&gt;
&lt;p&gt;However, this is likely inconsistent with your other RSpec expectations and is not as semantically lucid as the following option.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;expect(response).to eq(&amp;quot;Yes&amp;quot;).or eq(&amp;quot;Maybe&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find out more about &lt;a href=&quot;https://relishapp.com/rspec/rspec-expectations/docs/compound-expectations&quot;&gt;compound expectations in the official RSpec documentation&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Cannot load such file -- 2.3/pg_ext (LoadError)</title>
    <link href="https://wilcovanes.ch/articles/cannot-load-such-file-23/pg_ext-loaderror/"/>
    <updated>2016-09-03T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/cannot-load-such-file-23/pg_ext-loaderror/</id>
    <content type="html">&lt;p&gt;Setting up PostgreSQL with Ruby 2.3, you may encounter this error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LoadError: cannot load such file -- 2.3/pg_ext
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is due to the native extensions being built for Ruby 2.2 instead.&lt;/p&gt;
&lt;p&gt;How I solved this:&lt;/p&gt;
&lt;h3&gt;Uninstall the pg gem&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem uninstall pg&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Instead of pg gem v0.18 install a pre-release version of v0.19&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem install pg &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;pre&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;In your Gemfile, ensure it will pick up the pre-release version&lt;/h3&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pg&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&gt;= 0.19.0.beta&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Why a .present? might fail on an exact match</title>
    <link href="https://wilcovanes.ch/articles/why-a-present-might-fail-on-an-exact-match/"/>
    <updated>2016-07-31T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/why-a-present-might-fail-on-an-exact-match/</id>
    <content type="html">&lt;p&gt;Say you have an element for which you are checking &lt;code&gt;.present?&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;. It returns &lt;code&gt;false&lt;/code&gt;, but you can see it&#39;s definitely in the DOM and it returns &lt;code&gt;true&lt;/code&gt; for &lt;code&gt;.exists?&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is because the element is not visible.&lt;/p&gt;
&lt;p&gt;One possible way this might happen is that the element is in a hidden section. The HTML might, for example, have a duplicate section that is conditionally hidden and has identical structure, classes, and text.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.exists?&lt;/code&gt; will pass for elements in this section.
&lt;code&gt;.present?&lt;/code&gt; will not.&lt;/p&gt;
&lt;p&gt;In this case you&#39;ll want to determine which element should be the one that&#39;s visible and whether you&#39;re specifically selecting that element rather than its hidden duplicate.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attributes making elements invisible&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visibility: hidden;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;display: none;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;width: 0;&lt;/code&gt; and &lt;code&gt;height: 0;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;opacity: 0;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;hidden&amp;quot;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;webkit-transform&lt;/code&gt; properties on element or parent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content>
  </entry>
  
  <entry>
    <title>Creating persistent device category filters in Google Analytics</title>
    <link href="https://wilcovanes.ch/articles/creating-persistent-device-category-filters-in-google-analytics/"/>
    <updated>2016-07-30T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/creating-persistent-device-category-filters-in-google-analytics/</id>
    <content type="html">&lt;p&gt;Here&#39;s how to save filters for metrics divided by device category (mobile, tablet, desktop) in Google Analytics. For a simpler, throw-away method have a look at &lt;a href=&quot;https://wilcovanes.ch/articles/filtering-for-mobile-and-tablet-data-in-google-analytics&quot;&gt;Filtering for mobile and tablet data in Google Analytics&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;OS&#92;Browser - Desktop&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;In the main navigation, select &lt;strong&gt;Customization&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;+New Custom Report&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Give a title and name(s) to the report and tab(s) as you please, for - example: &lt;em&gt;OS&#92;Browser - Desktop&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Type&lt;/strong&gt;, select &lt;strong&gt;Flat Table&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Dimensions&lt;/strong&gt;, select &lt;strong&gt;+add dimension&lt;/strong&gt; and add &lt;strong&gt;Browser Version&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Dimensions&lt;/strong&gt;, select &lt;strong&gt;+add dimension&lt;/strong&gt; and add &lt;strong&gt;Operating System Version&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Metrics&lt;/strong&gt;, select &lt;strong&gt;+add metric&lt;/strong&gt; and add &lt;strong&gt;User&lt;/strong&gt; &amp;gt; &lt;strong&gt;Pages / Session&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Filters&lt;/strong&gt; select &lt;strong&gt;+add filter&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Users&lt;/strong&gt;, select &lt;strong&gt;Device Category&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Exact&lt;/strong&gt; and enter &lt;em&gt;desktop&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Device&#92;Resolution&#92;OS - Mobile&#92;Tablet&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Under the created filter, select &lt;strong&gt;Edit&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;+add report tab&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;You could call this one &lt;em&gt;Device&#92;Resolution&#92;OS&lt;/em&gt;, or whatever you wish&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Type&lt;/strong&gt;, select &lt;strong&gt;Flat Table&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Dimensions&lt;/strong&gt;, select &lt;strong&gt;+add dimension&lt;/strong&gt; and add &lt;strong&gt;Mobile Device Model&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Dimensions&lt;/strong&gt;, select &lt;strong&gt;+add dimension&lt;/strong&gt; and add &lt;strong&gt;Screen Resolution&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Dimensions&lt;/strong&gt;, select &lt;strong&gt;+add dimension&lt;/strong&gt; and add &lt;strong&gt;Operating System Version&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Metrics&lt;/strong&gt;, select &lt;strong&gt;+add metric&lt;/strong&gt; and add &lt;strong&gt;User&lt;/strong&gt; &amp;gt; &lt;strong&gt;Pages / Session&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Filters&lt;/strong&gt;, we see a copy of our existing Device Category filter&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Filters&lt;/strong&gt;, change dropdown selection &lt;strong&gt;Exact&lt;/strong&gt; to &lt;strong&gt;Regex&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Filters&lt;/strong&gt;, change Device Category value from &lt;em&gt;desktop&lt;/em&gt; to &lt;em&gt;mobile|tablet&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Navigate to the newly created tab&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Combining multiple path sections with os.path.join()</title>
    <link href="https://wilcovanes.ch/articles/combining-multiple-path-sections-with-ospathjoin/"/>
    <updated>2016-07-24T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/combining-multiple-path-sections-with-ospathjoin/</id>
    <content type="html">&lt;p&gt;Assume these variables are supplied:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;base_path = &amp;quot;C:&#92;&#92;Python34&#92;&#92;myscript&#92;&#92;&amp;quot;
filename = &amp;quot;sassykitten.jpg&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This won&#39;t work:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;base_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/images&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The variable &lt;code&gt;path&lt;/code&gt; will now contain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;C:/images&#92;&#92;sassykitten.jpg&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is due to the slash in &lt;code&gt;/images&lt;/code&gt;, which will make the rest of the join start at root. You can join any number of strings, but they should not include slashes.&lt;/p&gt;
&lt;p&gt;Do this instead:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;base_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;images&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filename&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will get the desired:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;C:&#92;&#92;Python34&#92;&#92;myscript&#92;&#92;images&#92;&#92;sassykitten.jpg&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Accessing an iframe loading an Angular app within a Web Form</title>
    <link href="https://wilcovanes.ch/articles/accessing-an-iframe-loading-an-angular-app-within-a-web-form/"/>
    <updated>2016-04-26T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/accessing-an-iframe-loading-an-angular-app-within-a-web-form/</id>
    <content type="html">&lt;p&gt;A bespoke CMS I worked on included a Web Form containing iframes of which one contained an Angular app. I wanted to access this Angular app using Watir-WebDriver and perform actions on its elements.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
    &lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token included-cdata&quot;&gt;&lt;span class=&quot;token cdata&quot;&gt;&amp;lt;![CDATA[&lt;/span&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
    Sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WebForms&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PageRequestManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;_initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;ctl00$ContentPlaceHolderInner$_scriptManager1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;aspnetForm&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;ctl00&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token cdata&quot;&gt;]]&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;iframe&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;form-iframe&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://weneedtogodeeper/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; none&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;1069px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;iframe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whilst the method for accessing iframes with Watir-WebDriver used to be &lt;code&gt;frames&lt;/code&gt;, I couldn&#39;t access the desired iframe with &lt;code&gt;@b.frame(:id =&amp;gt; &amp;quot;form-iframe&amp;quot;)&lt;/code&gt; and &lt;code&gt;@b.frames&lt;/code&gt; was empty.&lt;/p&gt;
&lt;p&gt;Instead, use &lt;code&gt;@b.iframes&lt;/code&gt;. For example, &lt;code&gt;@i = @b.iframes(:id =&amp;gt; &amp;quot;form-iframe&amp;quot;)&lt;/code&gt; allowed me to work with the desired iframe as a new instance variable &lt;code&gt;@i&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Restoring a PostgreSQL database from production in Windows</title>
    <link href="https://wilcovanes.ch/articles/restoring-a-postgresql-database-from-production-in-windows/"/>
    <updated>2016-02-19T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/restoring-a-postgresql-database-from-production-in-windows/</id>
    <content type="html">&lt;p&gt;Create a backup of a production PostgreSQL database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSH into your server (e.g. by using PuTTY).&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;pg_dump&lt;/code&gt; with the correct user and database. Example:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pg_dump --username &amp;quot;myadminuser&amp;quot; --format custom --blobs --verbose --file &amp;quot;mybackupfile.sql&amp;quot; &amp;quot;mydatabase&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Submit the correct password when prompted.&lt;/li&gt;
&lt;li&gt;The file is created in the directory you executed &lt;code&gt;pg_dump&lt;/code&gt; from unless you specified a path.&lt;/li&gt;
&lt;li&gt;You can pick this file up via an FTP client or by using pscp from a command prompt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Restore the backup to a local development database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;psql&lt;/code&gt; with the correct user, file and database. Example:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;psql --username &amp;quot;mylocaladmin&amp;quot; &amp;quot;mydatabase&amp;quot; &amp;lt;&amp;lt; &amp;quot;mybackupfile.sql&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>&quot;Could not access directory&quot; PostgreSQL error in Windows</title>
    <link href="https://wilcovanes.ch/articles/could-not-access-directory-postgresql-error-in-windows/"/>
    <updated>2016-02-19T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/could-not-access-directory-postgresql-error-in-windows/</id>
    <content type="html">&lt;p&gt;After upgrading from PostgreSQL 9.2 to 9.3.4, I was no longer able to access my database from psql or pgAdmin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FATAL:  could not access directory
&amp;quot;pg_tblspc/16639/PG_9.3_201306121/16843&amp;quot;: Permission denied
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;This directory exists.&lt;/li&gt;
&lt;li&gt;All user groups have full permissions to the directory as well as all its parent directories.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PostgreSQL now requires a NETWORK SERVICE account, which it did not create. Since I was using PostgreSQL on the Home version of Windows 7 (64-bit), I cannot access the Local Group Policy Editor.&lt;/p&gt;
&lt;p&gt;One possibility is to transfer your database to a different OS.&lt;/p&gt;
&lt;p&gt;I solved it by &lt;a href=&quot;https://wilcovanes.ch/articles/restoring-a-postgresql-database-from-production-in-windows&quot;&gt;restoring my database from production&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Test automation beyond end-to-end regression checks</title>
    <link href="https://wilcovanes.ch/articles/test-automation-beyond-end-to-end-regression-checks/"/>
    <updated>2016-02-06T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/test-automation-beyond-end-to-end-regression-checks/</id>
    <content type="html">&lt;p&gt;In discussions about test automation, I&#39;ve found test automation to be typically understood as building and maintaining suites of end-to-end UI-level regression checks using Selenium WebDriver.&lt;/p&gt;
&lt;p&gt;It can be that. It can also be many other things, without broadening the term to the use of test tools in general.
In less time and with relatively little programming skill, you can still increase the speed, coverage, and reliability of some checks that otherwise would have to be done manually.&lt;/p&gt;
&lt;p&gt;Examples I&#39;ve used in a professional environment where basic scripting skills brought value outweighing the time invested:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build validation through: checksum comparisons, filename comparisons, checking pattern of folders and files, checking for empty files.&lt;/li&gt;
&lt;li&gt;API testing. Calling endpoints via GET and POST requests, checking the HTTP response code and performing pattern matching on the response body.&lt;/li&gt;
&lt;li&gt;Build Verification Test, running a priority subset of functional regression checks triggered on every completed build.&lt;/li&gt;
&lt;li&gt;Fetch email campaigns from test accounts, parse the HTML, and compare links, tracking, and copy against consistently formatted content matrices.&lt;/li&gt;
&lt;li&gt;Check pages against brand guidelines, uniquely identify sections and check them for colours, font size, font style, character limits, etc.&lt;/li&gt;
&lt;li&gt;Read a list of links from a spreadsheet, visit them all, parse all links on the pages, check the HTTP status on GET requests on them.&lt;/li&gt;
&lt;li&gt;Compare spreadsheets to find items that should have been added or removed from a dropdown, then check dropdown values against it.&lt;/li&gt;
&lt;li&gt;Fill in form fields with test data from a CSV file and submit the form, asserting the entered values are sent and returning the response.&lt;/li&gt;
&lt;li&gt;For a decision tree, calculate what the product recommendations would be based on scoring conditions for each type of question answered.&lt;/li&gt;
&lt;li&gt;Follow all distinct journeys through a decision tree and compare product recommendations at the end of it to the expected recommendations.&lt;/li&gt;
&lt;li&gt;Verify links and tracking specifications against a list of mistakes commonly found in them.&lt;/li&gt;
&lt;li&gt;Spoof the browser&#39;s geolocation repeatedly for a large number of lat/long coordinates and assert a location-based module responds correctly on being loaded from these locales.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also use scripting for collateral tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Produce metrics from issue trackers or test reports.&lt;/li&gt;
&lt;li&gt;Produce live dashboard of progress on and status of current projects.&lt;/li&gt;
&lt;li&gt;Allow non-technical people to generate test seeds lists via a simple form.&lt;/li&gt;
&lt;li&gt;Use tracking suite APIs (or web scraping) to retrieve tracking data.&lt;/li&gt;
&lt;li&gt;Merge major browser versions in tracking data.&lt;/li&gt;
&lt;li&gt;Identify iOS devices from their logical screen resolutions in tracking data.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Ruby Dir methods being performed on the current directory</title>
    <link href="https://wilcovanes.ch/articles/ruby-dir-methods-being-performed-on-the-current-directory/"/>
    <updated>2016-02-03T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/ruby-dir-methods-being-performed-on-the-current-directory/</id>
    <content type="html">&lt;p&gt;Dir operations are performed on the directory they&#39;re called from, regardless of what directory you feed in. So &lt;code&gt;Dir.glob&lt;/code&gt; or &lt;code&gt;Dir.foreach&lt;/code&gt; or &lt;code&gt;Dir.entries&lt;/code&gt; on &lt;code&gt;(base_dir + &#39;*.something&#39;).count&lt;/code&gt; won&#39;t return expected results.&lt;/p&gt;
&lt;p&gt;You&#39;ll need to change directory, then perform the operation, e.g.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chdir&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;base_dir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;glob&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;*.something&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Find files through Ruby excluding those with a specific extension</title>
    <link href="https://wilcovanes.ch/articles/find-files-through-ruby-excluding-those-with-a-specific-extension/"/>
    <updated>2016-02-02T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/find-files-through-ruby-excluding-those-with-a-specific-extension/</id>
    <content type="html">&lt;p&gt;If &lt;code&gt;Dir.pwd&lt;/code&gt; is the path you want to search including its subdirectories:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;ignore_ext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.xlsx&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

Find&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;file&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; ignored_ext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extname&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;directory&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  puts file
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>How Nokogiri&#39;s type method corresponds to document types</title>
    <link href="https://wilcovanes.ch/articles/how-nokogiris-type-method-corresponds-to-document-types/"/>
    <updated>2016-01-22T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/how-nokogiris-type-method-corresponds-to-document-types/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;Update 2026:&lt;/em&gt; This is no longer the way Nokogiri works. As per the &lt;a href=&quot;https://nokogiri.org/rdoc/Nokogiri/XML/Node.html#method-i-html-3F&quot;&gt;Nokogiri documentation&lt;/a&gt;, you can now get the document type by simply using &lt;code&gt;.html?&lt;/code&gt; or &lt;code&gt;.xml?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Say you have a Nokogiri document, for example through:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;doc = Nokogiri::HTML(open(index.html))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you now call &lt;code&gt;doc.type&lt;/code&gt;, a digit will be returned, in this case &lt;code&gt;13&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What document type this translates to can be seen here:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(link that was here, but no longer valid: http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#DOCUMENT_TYPE_NODE-constant)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Notable examples:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;9 - XML document
13 - HTML document
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Making ElasticSearch work in a Django app on WebFaction</title>
    <link href="https://wilcovanes.ch/articles/making-elasticsearch-work-in-a-django-app-on-webfaction/"/>
    <updated>2015-05-06T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/making-elasticsearch-work-in-a-django-app-on-webfaction/</id>
    <content type="html">&lt;p&gt;These are some things I had to do to make ElasticSearch work in a Django app I deployed to WebFaction. After telnetting to the server:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Set ES_HEAP_SIZE environment variable to lower than 512 MB, e.g.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set ES_HEAP_SIZE=256mb
export ES_HEAP_SIZE=256mb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Check ES_HEAP_SIZE environment variable is set:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;printenv ES_HEAP_SIZE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Switch to Java 1.8:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &amp;quot;PATH=&#92;$HOME/jdk1.8.0_45/bin/:&#92;$PATH&amp;quot; &amp;gt;&amp;gt; .bash_profile
source .bash_profile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Run ElasticSearch as daemon:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;From elasticsearch-1.5.2 directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bin/elasticsearch -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Inspect memory used:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ps aux --sort -rss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Top RSS value should be 255640 or thereabouts (i.e. less than 256 MB).&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>&quot;Unable to obtain stable firefox connection error&quot; in Selenium WebDriver</title>
    <link href="https://wilcovanes.ch/articles/unable-to-obtain-stable-firefox-connection-error-in-selenium-webdriver/"/>
    <updated>2015-04-02T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/unable-to-obtain-stable-firefox-connection-error-in-selenium-webdriver/</id>
    <content type="html">&lt;p&gt;The error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unable to obtain stable firefox connection in 60 seconds (127.0.0.1:7055)
(Selenium::WebDriver::Error::WebDriverError)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I&#39;ve received this error, it has been due to Firefox updating itself to a new version and Selenium WebDriver not having been updated to support this new version.&lt;/p&gt;
&lt;p&gt;The solution:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem update selenium-webdriver
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you use Bundler, do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle update selenium-webdriver
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Still doesn&#39;t work? Check &lt;a href=&quot;http://code.google.com&quot;&gt;code.google.com&lt;/a&gt; with your firefox/selenium-webdriver version combination to see if there are any updates about it.&lt;/p&gt;
&lt;p&gt;Meanwhile, you can get dev updates for the selenium-webdriver gem from:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://rubygems.org/gems/selenium-webdriver/versions/&quot;&gt;https://rubygems.org/gems/selenium-webdriver/versions/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Last resort: downgrade Firefox.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Time zone / tzinfo-data issue running Rails on 64-bit Windows</title>
    <link href="https://wilcovanes.ch/articles/time-zone-/-tzinfo-data-issue-running-rails-on-64-bit-windows/"/>
    <updated>2015-02-08T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/time-zone-/-tzinfo-data-issue-running-rails-on-64-bit-windows/</id>
    <content type="html">&lt;p&gt;When running a Rails server on a 64-bit Windows installation, you may encounter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rescue in create_default_data_source&#39;: No timezone data source could be found. To resolve this, either install TZInfo::Data (e.g. by runninggem install tzinfo-data) or specify a zoneinfo directory usingTZInfo::DataSource.set(:zoneinfo, zoneinfo_path)`. (TZInfo::DataSourceNotFound)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To solve this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensure bundler is installed (&lt;code&gt;gem install bundler&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;In your Rails project&#39;s root folder, edit the Gemfile.&lt;/li&gt;
&lt;li&gt;Find the tzinfo-data line in the Gemfile.&lt;/li&gt;
&lt;li&gt;Change the line to: &lt;code&gt;gem &#39;tzinfo-data&#39;, platforms: [:mingw, :mswin, :x64_mingw]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;From the command line from within the same folder, run &lt;code&gt;bundle&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If Windows&#39; security settings won&#39;t allow you to save the Gemfile change, edit permissions of the folder by right-clicking it and selecting:
&lt;strong&gt;Properties&lt;/strong&gt; &amp;gt; &lt;strong&gt;Security&lt;/strong&gt; &amp;gt; &lt;strong&gt;Edit&lt;/strong&gt; &amp;gt; enter administrator details &amp;gt; give &lt;strong&gt;Users&lt;/strong&gt; account write permissions &amp;gt; &lt;strong&gt;Apply&lt;/strong&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Add &quot;WAVE&quot; accessibility checks to Web Developer Toolbar</title>
    <link href="https://wilcovanes.ch/articles/add-wave-accessibility-checks-to-web-developer-toolbar/"/>
    <updated>2015-02-03T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/add-wave-accessibility-checks-to-web-developer-toolbar/</id>
    <content type="html">&lt;p&gt;The &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/web-developer/&quot;&gt;Web Developer Toolbar&lt;/a&gt; add-on adds various convenient tools to Firefox for development and testing purposes.&lt;/p&gt;
&lt;p&gt;It includes accessibility checks under &lt;strong&gt;Tools &amp;gt; Validate &lt;a class=&quot;Footnotes__ref&quot; href=&quot;https://wilcovanes.ch/articles/add-wave-accessibility-checks-to-web-developer-toolbar/#section-508-note&quot; id=&quot;section-508-ref&quot; aria-describedby=&quot;footnotes-label&quot; role=&quot;doc-noteref&quot;&gt;Section 508&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;Tools &amp;gt; Validate WAI&lt;/strong&gt;. These link to Cynthia Says pages which stopped working in 2013, but are still present in the toolbar and can be easily replaced with functioning accessibility checks.&lt;/p&gt;
&lt;p&gt;In Firefox, with the Web Developer Toolbar enabled:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Tools&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Edit Tools...&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Validate Section 508&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Delete&lt;/strong&gt; and confirm deletion&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Validate WAI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Delete&lt;/strong&gt; and confirm deletion&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add...&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Replace the &lt;strong&gt;Description value&lt;/strong&gt; with &lt;strong&gt;Validate with WAVE&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;URL&lt;/strong&gt; radio button&lt;/li&gt;
&lt;li&gt;Replace the &lt;strong&gt;URL&lt;/strong&gt; value with &lt;strong&gt;http://wave.webaim.org/report#/&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OK&lt;/strong&gt; to save the new entry&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OK&lt;/strong&gt; to close the Edit Tools dialog&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now you&#39;re all done. Visit a website and try &lt;strong&gt;Tools &amp;gt; Validate with WAVE&lt;/strong&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Target pattern contains no `%&#39;</title>
    <link href="https://wilcovanes.ch/articles/target-pattern-contains-no-%60percent/"/>
    <updated>2015-01-21T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/target-pattern-contains-no-%60percent/</id>
    <content type="html">&lt;p&gt;When updating gems in Windows for Ruby 1.9.3 (or earlier), you may get an error message like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;creating Makefile

make  clean
Makefile:165: *** target pattern contains no `%&#39;.  Stop.

make
Makefile:165: *** target pattern contains no `%&#39;.  Stop.

make failed, exit code 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix this, downgrade your RubyGems version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem update --system 1.8.29
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Fixing a &quot;certificate verify failed&quot; error in Ruby</title>
    <link href="https://wilcovanes.ch/articles/fixing-a-certificate-verify-failed-error-in-ruby/"/>
    <updated>2015-01-15T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/fixing-a-certificate-verify-failed-error-in-ruby/</id>
    <content type="html">&lt;pre&gt;&lt;code&gt;Updating installed gems
ERROR: While executing gem ...
(Gem::RemoteFetcher::FetchError) SSL_connect returned=1
errno=0 state=SSLv3 read server certificate B: certificate
verify failed (https://api.rubygems.org/specs.4.8.gz)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ERROR: Could not find a valid gem &#39;activesupport&#39; (&amp;gt;= 0),
here is why: Unable to download data from
https://rubygems.org/ - SSL_connect returned=1 errno=0
state=SSLv3 read server certificate B: certificate
verify failed (https://api.rubygems.org/specs.4.8.gz)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix this (in Windows):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Save the &lt;a href=&quot;https://curl.haxx.se/ca/cacert.pem&quot;&gt;cacert.pem&lt;/a&gt; file to a folder, e.g. &lt;code&gt;C:&#92;Tools&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go into your System Environment Variables, and create a new environment variable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;SSL_CERT_FILE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Value: &lt;code&gt;C:&#92;Tools&#92;cacert.pem&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart your terminal/console&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If it still doesn&#39;t work, are you trying to access a Google API? In that case, use &lt;a href=&quot;https://raw.githubusercontent.com/google/google-api-ruby-client/master/lib/cacerts.pem&quot;&gt;the cacerts.pem from the Google API Client library &lt;/a&gt; instead. Note it&#39;s called &lt;em&gt;cacerts.pem&lt;/em&gt;, so you either save/rename it as &lt;em&gt;cacert.pem&lt;/em&gt; or update &lt;em&gt;SSL_CERT_FILE&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To fix this (in OS X):&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;http://stackoverflow.com/questions/4528101/ssl-connect-returned-1-errno-0-state-sslv3-read-server-certificate-b-certificat/5973759#5973759&quot;&gt;this StackOverflow answer&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>&quot;The native gem requires installed build tools&quot;</title>
    <link href="https://wilcovanes.ch/articles/the-native-gem-requires-installed-build-tools/"/>
    <updated>2014-12-29T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/the-native-gem-requires-installed-build-tools/</id>
    <content type="html">&lt;p&gt;When you&#39;re installing a gem with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem install GEM
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you get the message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The &#39;GEM&#39; native gem requires installed build tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure you have DevKit installed and that it is registered:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open a terminal / command prompt&lt;/li&gt;
&lt;li&gt;Navigate to the folder where you placed DevKit, e.g. &lt;code&gt;DevKit-mingw64&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;ruby dk.rb init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Edit the &lt;code&gt;config.yml&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Add the Ruby path you want to use DevKit with&lt;/li&gt;
&lt;li&gt;Save the file&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;ruby dk.rb install&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>“Invalid configuration or no Rubies listed” error</title>
    <link href="https://wilcovanes.ch/articles/invalid-configuration-or-no-rubies-listed-error/"/>
    <updated>2014-12-29T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/invalid-configuration-or-no-rubies-listed-error/</id>
    <content type="html">&lt;p&gt;When you install DevKit or edit the &lt;code&gt;config.yml&lt;/code&gt; for DevKit, you may get the following error.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Invalid configuration or no Rubies listed.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix this, edit &lt;code&gt;config.yml&lt;/code&gt; and ensure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The paths are correct&lt;/li&gt;
&lt;li&gt;Each path has a hyphen in front of it&lt;/li&gt;
&lt;li&gt;The paths you want to use have no hash symbol in front of them&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Filtering for mobile and tablet data in Google Analytics</title>
    <link href="https://wilcovanes.ch/articles/filtering-for-mobile-and-tablet-data-in-google-analytics/"/>
    <updated>2014-12-29T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/filtering-for-mobile-and-tablet-data-in-google-analytics/</id>
    <content type="html">&lt;p&gt;In Google Analytics you can separate data by device category (desktop, tablet, mobile).&lt;/p&gt;
&lt;p&gt;Depending on what you&#39;re measuring, you may want to combine data for tablet and mobile.&lt;/p&gt;
&lt;p&gt;In the advanced filtering options for Google Analytics, filters are joined by &lt;code&gt;AND&lt;/code&gt; and this cannot be changed to &lt;code&gt;OR&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, you can use regular expressions to accomplish the same thing.&lt;/p&gt;
&lt;p&gt;In the &#39;Reporting&#39; section, view a report for a dimension (for example, on the left-hand side, select Audiences followed by Technology followed by Browser &amp;amp; OS).&lt;/p&gt;
&lt;p&gt;There should be an &#39;advanced&#39; option (at the time of writing it is located next to the search field). Click it and set the value for &lt;strong&gt;Containing&lt;/strong&gt; to &lt;code&gt;/(mobile|tablet)&lt;/code&gt;. This will exclude data for desktop browsers.&lt;/p&gt;
&lt;p&gt;This is a quick and throw-away method. If you want the filter to persist and configure it further, see &lt;a href=&quot;https://wilcovanes.ch/articles/creating-persistent-device-category-filters-in-google-analytics&quot;&gt;Creating persistent device category filters in Google Analytics&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Doing an Active Record migration in Rails</title>
    <link href="https://wilcovanes.ch/articles/doing-an-active-record-migration-in-rails/"/>
    <updated>2014-12-29T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/doing-an-active-record-migration-in-rails/</id>
    <content type="html">&lt;p&gt;To change your Rails models, you should create a migration containing the change and execute the migration.&lt;/p&gt;
&lt;p&gt;First, generate the migration. From within your app directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rails g migration DESCRIPTION
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit the file, which has been created in &lt;code&gt;db/migrate&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Define your desired changes. You can place them in a function change (with reversible if you want to be able to undo the change), or place them in functions &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt;, where &lt;code&gt;up&lt;/code&gt; defines what should be changed and &lt;code&gt;down&lt;/code&gt; defines how to return to the previous state if you decide to roll back this migration.&lt;/p&gt;
&lt;p&gt;If you want to make the change as a SQL query, you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;execute &amp;lt;&amp;lt;-SQL
 YOUR SQL QUERY
SQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then on the command line run:&lt;/p&gt;
&lt;p&gt;(OS X Terminal) &lt;code&gt;rake db:migrate&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;(Windows CMD) &lt;code&gt;ruby bin/rake db:migrate&lt;/code&gt;
(Heroku CLI) &lt;code&gt;heroku run rake db:migrate&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;More information can be found on the &lt;a href=&quot;http://edgeguides.rubyonrails.org/active_record_migrations.html&quot;&gt;Active Record Migrations section of the RailsGuides site&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Comment syntax across languages</title>
    <link href="https://wilcovanes.ch/articles/comment-syntax-across-languages/"/>
    <updated>2014-12-29T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/comment-syntax-across-languages/</id>
    <content type="html">&lt;h2&gt;CSS&lt;/h2&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* Comment (in-line) */&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
Comment (multi-line)
*/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;JavaScript, Java, Swift, C&lt;/h2&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Comment (in-line)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/*
Comment (multi-line)
*/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Python&lt;/h2&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Comment (in-line)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;
Comment (multi-line)
&quot;&quot;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ruby&lt;/h2&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Comment (in-line)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;=begin
Comment (multi-line)
=end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Lua&lt;/h2&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Comment (in-line)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-lua&quot;&gt;&lt;code class=&quot;language-lua&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;--[[
Comment (multi-line)
]]&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;--&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Compiler version per Visual C++ version</title>
    <link href="https://wilcovanes.ch/articles/compiler-version-per-visual-c-version/"/>
    <updated>2014-03-16T00:00:00Z</updated>
    <id>https://wilcovanes.ch/articles/compiler-version-per-visual-c-version/</id>
    <content type="html">&lt;p&gt;When you need to compile a Python extension, you&#39;ll need to know which version of Visual C++ to use.&lt;/p&gt;
&lt;p&gt;First, find out the compiler version for the version of Python you&#39;re looking to use the extension with.&lt;/p&gt;
&lt;p&gt;On the command line, simply execute &lt;code&gt;python&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Sample output:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Python &lt;span class=&quot;token number&quot;&gt;3.4&lt;/span&gt;.0 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v3.4.0:04f714765c13, Mar &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2014&lt;/span&gt;, &lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;:25:23&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;MSC v.1600 &lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt; b D64&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; on win32
Type &lt;span class=&quot;token string&quot;&gt;&quot;help&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;copyright&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;credits&quot;&lt;/span&gt; or &lt;span class=&quot;token string&quot;&gt;&quot;license&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;more&lt;/span&gt; information.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it does not list the Python version you&#39;re going to add the extension to, switch to the version you need.&lt;/p&gt;
&lt;p&gt;Then take note of the MSC value. Find it in the table below and there you have the Visual C++ version you need.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Compiler version&lt;/th&gt;
&lt;th&gt;Visual C++ version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1000&lt;/td&gt;
&lt;td&gt;Visual C++ 4.x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1100&lt;/td&gt;
&lt;td&gt;Visual C++ 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1200&lt;/td&gt;
&lt;td&gt;Visual C++ 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1300&lt;/td&gt;
&lt;td&gt;Visual C++ .NET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1310&lt;/td&gt;
&lt;td&gt;Visual C++ .NET 2003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1400&lt;/td&gt;
&lt;td&gt;Visual C++ 2005&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1500&lt;/td&gt;
&lt;td&gt;Visual C++ 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1600&lt;/td&gt;
&lt;td&gt;Visual C++ 2010&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1700&lt;/td&gt;
&lt;td&gt;Visual C++ 2012&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSC v.1800&lt;/td&gt;
&lt;td&gt;Visual C++ 2013&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content>
  </entry>
</feed>