<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Karel De Smet</title>
    <description>The latest articles on DEV Community by Karel De Smet (@carlosds).</description>
    <link>https://dev.to/carlosds</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F280568%2Fe0a35153-d6ad-4838-8aec-006088875b27.jpeg</url>
      <title>DEV Community: Karel De Smet</title>
      <link>https://dev.to/carlosds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carlosds"/>
    <language>en</language>
    <item>
      <title>Loading scripts with async / defer</title>
      <dc:creator>Karel De Smet</dc:creator>
      <pubDate>Sat, 26 Dec 2020 18:47:54 +0000</pubDate>
      <link>https://dev.to/carlosds/loading-scripts-with-async-defer-3e7m</link>
      <guid>https://dev.to/carlosds/loading-scripts-with-async-defer-3e7m</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: prism
&lt;/h2&gt;

&lt;p&gt;To style code blocks on my personal website, I use &lt;a href="https://prismjs.com/"&gt;Prism&lt;/a&gt;. It's dead simple, lightweight and looks great everywhere. Plus, you can select just the languages and themes you need, so there's little to no overhead.&lt;/p&gt;

&lt;p&gt;Until recently, I included the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag for Prism &lt;strong&gt;just before the closing&lt;/strong&gt; &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; &lt;strong&gt;tag&lt;/strong&gt;. Just like everyone always told us to do, right? Execution is synchronous, and you don't want your scripts blocking the rest of your page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Async &amp;amp; defer
&lt;/h2&gt;

&lt;p&gt;However, the &lt;strong&gt;attributes&lt;/strong&gt; &lt;code&gt;async&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;defer&lt;/code&gt; allow you to reach the same goal, namely to make sure that fetching and/or executing scripts does not block parsing.&lt;/p&gt;

&lt;p&gt;As a simplified example, let's assume my page contains the following code:&lt;/p&gt;

&lt;pre&gt;
&lt;code&gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;Async &amp;amp; defer&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/p&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Somewhere in this code, we need to include the script for Prism:&lt;/p&gt;

&lt;pre&gt;
&lt;code&gt;
&amp;lt;script src="/assets/js/prism.js"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;What are our options?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Include it in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section. But we know that's just a bad practice.&lt;/li&gt;
&lt;li&gt;Include it just before the &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag.&lt;/li&gt;
&lt;li&gt;Include it with &lt;code&gt;async&lt;/code&gt; in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section.&lt;/li&gt;
&lt;li&gt;Include it with &lt;code&gt;defer&lt;/code&gt; in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be fair, I can't make the differences any clearer than &lt;a href="https://flaviocopes.com/javascript-async-defer/"&gt;Flavio Copes&lt;/a&gt; can. This means we can skip the detailed comparison here and go straight to how I approached this: with &lt;code&gt;defer&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defer
&lt;/h2&gt;

&lt;p&gt;Deferred scripts are fetched asynchronously and executed immediately after parsing of the DOM.&lt;/p&gt;

&lt;p&gt;Our simplified example now looks like this:&lt;/p&gt;

&lt;pre&gt;
&lt;code&gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;script defer src="/assets/js/prism.js"&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;title&amp;gt;Async &amp;amp; defer&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet&amp;lt;/p&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;The flow here is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parsing starts&lt;/li&gt;
&lt;li&gt;Parser encounters deferred script ➡️ fetching starts&lt;/li&gt;
&lt;li&gt;Parsing continues in parallel with fetching&lt;/li&gt;
&lt;li&gt;Fetching completes&lt;/li&gt;
&lt;li&gt;Parsing completes&lt;/li&gt;
&lt;li&gt;Script is executed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event"&gt;DOMContentLoaded&lt;/a&gt; event is fired&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Async
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;async&lt;/code&gt;, the flow would be a little different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parsing starts&lt;/li&gt;
&lt;li&gt;Parser encounters asynchronous script ➡️ fetching starts&lt;/li&gt;
&lt;li&gt;Parsing continues in parallel with fetching&lt;/li&gt;
&lt;li&gt;Fetching completes&lt;/li&gt;
&lt;li&gt;Script is executed&lt;/li&gt;
&lt;li&gt;Parsing completes&lt;/li&gt;
&lt;li&gt;DOMContentLoaded event is fired&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that &lt;strong&gt;execution occurs immediately after fetching has completed&lt;/strong&gt;. As opposed to &lt;code&gt;defer&lt;/code&gt;, which executes only after the parsing has fully completed. Because we rely on the whole DOM being present before Prism does its magic, I preferred to defer the Prism script.&lt;/p&gt;

&lt;p&gt;Another difference is that deferred scripts are executed in the order in which they were encountered. That's not the case for asynchronous scripts, making them a &lt;strong&gt;prime candidate for standalone scripts&lt;/strong&gt; (e.g. ads, analytics ...).&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the juice worth the squeeze?
&lt;/h2&gt;

&lt;p&gt;Well, &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;defer&lt;/code&gt; are &lt;strong&gt;not fully compatible&lt;/strong&gt; with IE9 and older. However, I sincerely hope that's not relevant for you.&lt;/p&gt;

&lt;p&gt;They also &lt;strong&gt;don't guarantee faster loading times&lt;/strong&gt;. In my case, the time it takes before DOMContentLoaded is fired differs just a couple of miliseconds when the script is deferred.&lt;/p&gt;

&lt;p&gt;The best piece of advice: &lt;strong&gt;perform tests&lt;/strong&gt; if you plan to switch things up. The number of factors is just too high to provide a one-size-fits-all approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if parsing ends before the fetching of your script?&lt;/li&gt;
&lt;li&gt;What if you combine multiple approaches?&lt;/li&gt;
&lt;li&gt;How much of your bandwidth is dedicated to fetching compared to other tasks that require bandwidth?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then why go through all the trouble?&lt;/p&gt;

&lt;p&gt;For me, using asynchronous or deferred scripts is about &lt;strong&gt;cleaning up your code&lt;/strong&gt;. Because our scripts are meant to be somewhere in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;. Placing them anywhere else was just a &lt;strong&gt;cheeky workaround&lt;/strong&gt; in the endeavour for maximum performance.&lt;/p&gt;

</description>
      <category>html</category>
    </item>
    <item>
      <title>Course review: "Introduction to software testing" (Coursera)</title>
      <dc:creator>Karel De Smet</dc:creator>
      <pubDate>Sat, 28 Nov 2020 19:42:25 +0000</pubDate>
      <link>https://dev.to/carlosds/course-review-introduction-to-software-testing-coursera-3dfe</link>
      <guid>https://dev.to/carlosds/course-review-introduction-to-software-testing-coursera-3dfe</guid>
      <description>&lt;p&gt;A couple of months ago, I took a course on Coursera named &lt;em&gt;&lt;a href="https://www.coursera.org/learn/introduction-software-testing"&gt;"Introduction to software testing"&lt;/a&gt;&lt;/em&gt;, which is a part of the &lt;em&gt;&lt;a href="https://www.coursera.org/specializations/software-testing-automation"&gt;"Software testing and automation"&lt;/a&gt;&lt;/em&gt; specialization. And I'd like to add my 2 cents.&lt;/p&gt;

&lt;p&gt;Before we dive in, I'll TL;DR it for you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The good&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Theoretical concepts are properly explained.&lt;/li&gt;
&lt;li&gt;Provides a solid foundation for future learning about software testing.&lt;/li&gt;
&lt;li&gt;Knowledgeable instructors from a respected university.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The bad&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interaction with students and teachers is next to nothing.&lt;/li&gt;
&lt;li&gt;Course content feels outdated or out of place at times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although I'm not the biggest fan, you can't blame this course for not doing what it says on the tin: provide an &lt;em&gt;introduction to software testing&lt;/em&gt;. It touches on a fair amount of theoretical concepts which testers should understand. And even developers will find there's a lot in it for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The theory behind testing
&lt;/h2&gt;

&lt;p&gt;Module 1 starts off by answering the questions &lt;em&gt;"What is a test?"&lt;/em&gt;, &lt;em&gt;"Why should we test?"&lt;/em&gt; and &lt;em&gt;"How can we test well?"&lt;/em&gt;. The course then discusses the differences between validation and verification, and progresses towards &lt;a href="https://en.wikipedia.org/wiki/V-Model_(software_development)"&gt;the V-Model&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Although a strict application of the V-Model is perhaps rather unusual, being conscious of it is still beneficial towards a better understanding of software testing.&lt;/p&gt;

&lt;p&gt;In the second module, topics such as &lt;strong&gt;dependability&lt;/strong&gt;, &lt;strong&gt;structural testing&lt;/strong&gt;, &lt;strong&gt;mutation testing&lt;/strong&gt; and &lt;strong&gt;error-prone aspects&lt;/strong&gt; (null pointers, boundaries ...) are also brought up.&lt;/p&gt;

&lt;p&gt;Up to here, I was very enthusiastic. There's a lot of valuable information in these two modules. Even more for testers who don't have a background in software engineering, such as myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test plan(ning)
&lt;/h2&gt;

&lt;p&gt;However, the third module is where I started to feel lost. Some advice from the videos is definitely actionable (e.g. "also document what can not be tested"), but the content here feels outdated and too comprehensive. More specifically, the part about &lt;strong&gt;defect reporting&lt;/strong&gt; seems like something I could've made in half an hour of browsing through JIRA tickets.&lt;/p&gt;

&lt;p&gt;To make matters worse, the exercise we were given was an absolute mess. You are asked to &lt;strong&gt;draft a test plan&lt;/strong&gt; for a fictional application by applying what you've learned, then submit it for review by your fellow students. I'm sorry to say that the requirements of the application under test make it feel like we're back at MS-DOS. Hello, 2020?!&lt;/p&gt;

&lt;p&gt;After submission, you're also asked to &lt;strong&gt;review test plans&lt;/strong&gt; from other students, using a list of multiple choice questions (e.g. "does the test plan include information about X?"). Unfortunately, based on the examples I've seen, a lot of students don't take the exercise (both writing and reviewing) serious and just want to make it through. I also seriously doubt whether the grading system truly evaluated the quality of a submission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing unit tests
&lt;/h2&gt;

&lt;p&gt;The fourth and final module covers &lt;strong&gt;writing unit tests&lt;/strong&gt; (&lt;a href="https://junit.org/"&gt;JUnit&lt;/a&gt;, &lt;a href="https://site.mockito.org/"&gt;Mockito&lt;/a&gt;) and &lt;strong&gt;code coverage&lt;/strong&gt; (&lt;a href="https://www.eclemma.org/jacoco/"&gt;JaCoCo&lt;/a&gt;). You're asked to write unit tests and these should cover a specific percentage of the code and reveal the bugs in the code you were given. These assignments are graded via automated scripts, which is a fairly good process and I really enjoyed working on this.&lt;/p&gt;

&lt;p&gt;Despite my enjoyment, it didn't feel like the right time and place for practical exercises about unit tests. Neither is it detailed enough to really grasp unit testing. Shouldn't this be incorporated into another course of this specialization?&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I would very much recommend the first and second module of this course. But the third and fourth module just didn't feel right.&lt;/p&gt;

&lt;p&gt;That holds me back from taking the next course in the specialization, which currently also has a lower review score. When you add the lack of interaction with other students and teachers, it's not encouraging me to continue. But it was well worth the effort!&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>Detecting if your user is online or offline</title>
      <dc:creator>Karel De Smet</dc:creator>
      <pubDate>Mon, 19 Oct 2020 05:58:59 +0000</pubDate>
      <link>https://dev.to/carlosds/is-the-user-online-or-offline-nk3</link>
      <guid>https://dev.to/carlosds/is-the-user-online-or-offline-nk3</guid>
      <description>&lt;p&gt;At work, we're developing a PWA (amongst others) and as a new feature, we needed to &lt;strong&gt;warn users when they are offline&lt;/strong&gt;. This should incentivise them to first restore their network connection before taking further action, avoiding possible unwanted side effects from being offline.&lt;/p&gt;

&lt;p&gt;After testing, I had a look at how it was implemented and was pleasantly surprised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigator.onLine
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/Online_and_offline_events"&gt;Navigator object&lt;/a&gt;, which you can access in the browser via &lt;span&gt;window.navigator&lt;/span&gt;, has an &lt;span&gt;onLine&lt;/span&gt; property. This boolean is prone to false positives, but we weren't looking for perfect coverage on this one.&lt;/p&gt;

&lt;p&gt;Mind the capital 'L' though!&lt;/p&gt;

&lt;pre&gt;
  &lt;code&gt;
  window.navigator.online
  // undefined

  window.navigator.onLine
  // true
  &lt;/code&gt;
&lt;/pre&gt;

&lt;h2&gt;
  
  
  Online and offline events
&lt;/h2&gt;

&lt;p&gt;So we know how to check if a user is online or offline. But how can we &lt;strong&gt;detect changes&lt;/strong&gt;? Writing custom events has never been anyone's favorite, but luckily the &lt;span&gt;online&lt;/span&gt; and &lt;span&gt;offline&lt;/span&gt; events are readily available.&lt;/p&gt;

&lt;p&gt;Want to try it out? Copy and paste the following code into an HTML file and open it in the browser. Then play with your network connection (in your OS or your browser's DevTools) to see it in action.&lt;/p&gt;

&lt;pre&gt;
  &lt;code&gt;
    &amp;lt;!DOCTYPE html&amp;gt;
    &amp;lt;html&amp;gt;
      &amp;lt;head&amp;gt;
        &amp;lt;meta charset="UTF-8" /&amp;gt;
        &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
        &amp;lt;title&amp;gt;Online/offline&amp;lt;/title&amp;gt;
      &amp;lt;/head&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;h1 id="status"&amp;gt;&amp;lt;/h1&amp;gt;
      &amp;lt;/body&amp;gt;
      &amp;lt;script&amp;gt;
        function userIsOnline() {
          document.getElementById("status").textContent = "User is online";
        }

        function userIsOffline() {
          document.getElementById("status").textContent = "User is offline";
        }

        if (window.navigator.onLine) {
          userIsOnline();
        } else {
          userIsOffline();
        }

        window.addEventListener("online", userIsOnline);
        window.addEventListener("offline", userIsOffline);
      &amp;lt;/script&amp;gt;
    &amp;lt;/html&amp;gt;
  &lt;/code&gt;
&lt;/pre&gt;

&lt;h2&gt;
  
  
  NetworkInformation
&lt;/h2&gt;

&lt;p&gt;Although out of scope for our use case, it was interesting to discover &lt;span&gt;window.navigation.connection&lt;/span&gt; contained more network goodies, in the form of a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation"&gt;NetworkInformation instance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my case, it contained the following:&lt;/p&gt;

&lt;pre&gt;
  &lt;code&gt;
  downlink: 10,
  effectiveType: "4g",
  rtt: 50,
  saveData: false
  &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Huh, my downlink seems slow as hell? It's supposed to represent my download speed in Mbps but it's not even close to what I would expect.&lt;/p&gt;

&lt;p&gt;Well, it seems like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink"&gt;Chrome arbitrarily caps this value&lt;/a&gt; at 10 as an anti-&lt;a href="https://cyware.com/news/what-is-cybersecurity-fingerprinting-de718f94"&gt;fingerprinting&lt;/a&gt; measure.&lt;/p&gt;

&lt;p&gt;Well, thank you Google for keeping me safe and secure ;)&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>How to develop a Chrome extension</title>
      <dc:creator>Karel De Smet</dc:creator>
      <pubDate>Sun, 20 Sep 2020 18:11:48 +0000</pubDate>
      <link>https://dev.to/carlosds/how-to-develop-a-chrome-extension-2kj7</link>
      <guid>https://dev.to/carlosds/how-to-develop-a-chrome-extension-2kj7</guid>
      <description>&lt;p&gt;&lt;span&gt;Cover photo by &lt;a href="https://unsplash.com/@sigmund?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Sigmund&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/browser?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I've developed a fairly simple Chrome extension called "Mistake" and have shared the source code on &lt;a href="https://github.com/carlos-ds/mistake"&gt;Github&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To find out what it does and try it yourself, follow the instructions on Github or &lt;a href="https://www.kareldesmet.be/assets/vid/mistake-chrome-extension-demo.mp4"&gt;watch this video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's shed some more light on why and how this was developed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;At work, I'm often confronted with the fact that it's outright dangerous to open &lt;strong&gt;multiple browser tabs&lt;/strong&gt; containing the &lt;strong&gt;same application&lt;/strong&gt;, but in different environments. For obvious reasons, you don't want to perform test actions on your production environment.&lt;/p&gt;

&lt;p&gt;There are soms ways to avoid this, with one of the most common ones is to use &lt;strong&gt;environment variables&lt;/strong&gt; for &lt;strong&gt;styling&lt;/strong&gt; certain elements. For example, the production environment has a &lt;span&gt;green&lt;/span&gt; background color for the navigation bar or document body, whilst the test environments have a &lt;span&gt;red&lt;/span&gt; background color.&lt;/p&gt;

&lt;p&gt;Unfortunately, the current application I'm working with doesn't have that feature. And after I almost performed an unwanted action on the production environment, thinking it was QA, I went looking for solutions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: I contemplated working with Angular or React, but decided it just wasn't worth it. It definitely could have made my life easier, but I'm just not comfortable enough with it (yet) and I decided to go plain Javascript. This was something I actually needed, so I wanted to have a functional version ASAP.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Out of the box: Stylish
&lt;/h2&gt;

&lt;p&gt;The first thing I found was &lt;a href="https://chrome.google.com/webstore/detail/stylish-custom-themes-for/fjnbnpbmkenffdnngjfgmeleoegfcffe/related?hl=en"&gt;Stylish&lt;/a&gt;. It lets you choose customized styles/themes for popular websites. But you can also write some of your own styles and apply it to URLs that match certain patterns.&lt;br&gt;
Which sparked the idea to build something similar, which would allow me to display a custom message at the top of certain webpages. These messages could then serve as an indication of the environment in which I'm currently working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started on a custom solution
&lt;/h2&gt;

&lt;p&gt;The first thing we need to do is create &lt;em&gt;manifest.json&lt;/em&gt;. Here, we declare general application information and some configuration basics.&lt;/p&gt;

&lt;pre&gt;
  &lt;code&gt;
  {
    "name": "Mistake",
    "version": "1.0",
    "description": "Avoid disaster in production by displaying a message on pages that meet the criteria you define.",
    "permissions": ["webNavigation", "storage"],
    "content_scripts": [
      {
        "matches": ["&amp;lt;all_urls&amp;gt;"],
        "js": ["content.js"],
        "run_at": "document_idle"
      }
    ],
    "manifest_version": 2,
    "options_page": "options.html"
  }
  &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;The most important thing here is &lt;strong&gt;declaring the right permissions&lt;/strong&gt;. For example, we need to tell Google Chrome that we need access to the &lt;em&gt;storage API&lt;/em&gt;. Because in order to save the message and its details, we need a place to store that information.&lt;/p&gt;

&lt;p&gt;Access to the &lt;em&gt;webNavigation API&lt;/em&gt; is required, because every time a user navigates in Chrome, we want to check if the page matches one of the rules he has described on the options page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elaborating the options page
&lt;/h2&gt;

&lt;p&gt;Next, we can get to work on the &lt;em&gt;options page&lt;/em&gt; (options.html). This page lets the user define certain options. Let's look at an example for this extension:&lt;br&gt;
e.g. &lt;em&gt;As a user, I want to display a message "This is your local environment!" on any URL that begins with "&lt;a href="https://localhost"&gt;https://localhost&lt;/a&gt;"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In short, we'll give users 3 options for pattern matching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL begins with&lt;/li&gt;
&lt;li&gt;URL contains&lt;/li&gt;
&lt;li&gt;URL ends with&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the following elements of the message should be customisable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text color&lt;/li&gt;
&lt;li&gt;Background color&lt;/li&gt;
&lt;li&gt;Text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll also add some info about our extension and place a button to add a new rule. It doesn't do anything yet but stick with it. Finally, we're loading Bootstrap from a CDN for easy styling.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;options.html&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;
  &lt;code&gt;
  &amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
      &amp;lt;title&amp;gt;Mistake - Options&amp;lt;/title&amp;gt;
      &amp;lt;link rel="stylesheet" href="./css/bootstrap.min.css"&amp;gt;
      &amp;lt;style&amp;gt;
        h2 {
          margin: 2rem 0;
        }

        p {
          font-size: 1.5rem;
        }

        #add {
          margin-top: 2rem;
          font-size: 1.5rem;
        }

        .rule {
          border-bottom: 1px solid black;
        }

        .rule:last-of-type {
          border-bottom: none;
        }

        button[data-toggle="collapse"] {
          border: none;
          background-color: #fff;
          margin-top: 2rem;
          margin-bottom: 1rem;
          color: black;
          display:block; 
          outline: none;
          font-weight: 600; 
          font-size: 1.5rem;
        }

        button[data-toggle="collapse"]:hover, 
        button[data-toggle="collapse"]:visited, 
        button[data-toggle="collapse"]:active,
        button[data-toggle="collapse"]:focus {
          background-color: unset !important;
          color: unset !important;
          border: none;
          outline: 0 !important;
          outline-offset: 0  !important;
          background-image: none  !important;
          -webkit-box-shadow: none !important;
          box-shadow: none  !important;
        }

        .btn-light:focus, .btn-light.focus {
          box-shadow: 0;
        }

        input[type="color"] {
          display: block;
          border-radius: 50%;
          width: 50px;
          height: 50px;
          border: none;
          outline: none;
          -webkit-appearance: none;
        }

        input[type="color"]::-webkit-color-swatch-wrapper {
          padding: 0;   
        }

        input[type="color"]::-webkit-color-swatch {
          border-radius: 50%;
        } 
      &amp;lt;/style&amp;gt;

    &amp;lt;/head&amp;gt;
    &amp;lt;body style="padding-top: 5rem;"&amp;gt;
      &amp;lt;div class="container"&amp;gt;
        &amp;lt;h2&amp;gt;What does Mistake do?&amp;lt;/h2&amp;gt;
        &amp;lt;p&amp;gt;Display a custom message at the top of any webpage that meets the criteria you define.&amp;lt;/p&amp;gt;
        &amp;lt;h2&amp;gt;Why would I want to do such a thing?&amp;lt;/h2&amp;gt;
        &amp;lt;p&amp;gt;Have you ever worked having &amp;lt;strong&amp;gt;multiple tabs of the same application&amp;lt;/strong&amp;gt; open, but in &amp;lt;strong&amp;gt;different environments&amp;lt;/strong&amp;gt;? Then you know how easy it is to live everyone's worst nightmare: screwing things up in production.&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;After yet another near miss, I decided to take matters into my own hands and design this plug-in. Now, when I'm in production, at least I'm significantly reducing the odds of making a &amp;lt;i&amp;gt;Mistake&amp;lt;/i&amp;gt;.&amp;lt;/p&amp;gt;
        &amp;lt;h2&amp;gt;How does it work?&amp;lt;/h2&amp;gt;
        &amp;lt;p&amp;gt;Start by adding a new rule using the button below. Add as many rules as you like.&amp;lt;br/&amp;gt;
        Now, whenever you open a tab with the URL that matches the pattern, your message will be displayed. Et voila!&amp;lt;/p&amp;gt;

        &amp;lt;button type="button" class="btn btn-primary" id="add"&amp;gt;
          Add a new rule
        &amp;lt;/button&amp;gt;

        &amp;lt;div id="rules" style="padding-top: 20px;"&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;script src="./js/jquery-3.5.1.slim.min.js"&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src="./js/popper.min.js"&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src="./js/bootstrap.min.js"&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src="config.js"&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src="helpers.js"&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;script src="options.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
  &amp;lt;/html&amp;gt;
  &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Now we can move on to writing some logic in the Javascript options file (options.js). It consists of 5 important functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;initializeRules&lt;/em&gt; gets any existing rules from storage on page load and displays them using the &lt;em&gt;displayRules&lt;/em&gt; function.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;createRule&lt;/em&gt; contains all the HTML and CSS for displaying one specific rule on the options page.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;saveRule&lt;/em&gt; saves the information about a rule to storage and displays an alert if it was successful.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;removeRule&lt;/em&gt; removes a rule from storage and from the screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;options.js&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;
  &lt;code&gt;
  const buttonAddNewRule = document.getElementById("add");
  const rulesList = document.getElementById("rules");

  window.onload = function () {
    initializeRules();
    buttonAddNewRule.addEventListener("click", createRule);
    rulesList.addEventListener("click", saveRule);
    rulesList.addEventListener("click", removeRule);
  };

  function initializeRules() {
    chrome.storage.sync.get(null, function (syncItems) {
      displayRules(syncItems);
    });
  }

  function displayRules(rules) {
    for (const value of Object.values(rules)) {
      createRule(
        value.type,
        value.expression,
        value.message,
        value.textColor,
        value.backgroundColor
      );
    }
  }

  function createRule(type, expression, message, textColor, backgroundColor) {
    removeActiveAlert();

    const newRule = document.createElement("div");
    newRule.classList.add("rule", "pt-3");
    newRule.setAttribute("data-index", getCurrentNumberOfRules());

    const toggleButton = document.createElement("button");
    toggleButton.classList.add("btn", "btn-light");
    toggleButton.setAttribute("type", "button");
    toggleButton.setAttribute("data-toggle", "collapse");
    toggleButton.setAttribute("data-target", "#collapse" + getCurrentNumberOfRules());
    toggleButton.setAttribute("aria-expanded", "false");
    toggleButton.setAttribute("aria-controls", "collapse" + getCurrentNumberOfRules());
    if (!type || !expression) { 
      toggleButton.innerText = "New rule (unsaved)";
    } else { 
      toggleButton.innerHTML = `${type} "${expression}" ↓`;
    }

    const collapseDiv = document.createElement("div");
    collapseDiv.classList.add("collapse", "show", "mb-5");
    collapseDiv.setAttribute("id", "collapse" + getCurrentNumberOfRules());

    const card = document.createElement("div");
    card.classList.add("card", "card-body");

    card.appendChild(createTypeButtonGroup(type));
    card.appendChild(createExpressionInput(expression));
    card.appendChild(createMessageInput(message));
    card.appendChild(createColorInput("textColor", textColor));
    card.appendChild(createColorInput("backgroundColor", backgroundColor));
    card.appendChild(createButton("save"));
    card.appendChild(createButton("remove"));

    collapseDiv.appendChild(card);
    newRule.appendChild(toggleButton);
    newRule.appendChild(collapseDiv);
    rulesList.appendChild(newRule);
  }

  function saveRule(rule) {
    if (rule.target.getAttribute("data-action") === "save") {
      try {
        const ruleTargetParent = rule.target.parentNode;
        const ruleIndex = ruleTargetParent.parentNode.parentNode.getAttribute("data-index");
        const typeArray = ruleTargetParent.getElementsByClassName("active");
        if (typeArray.length !== 1) {
          throw new Error(
            "One and only one rule type should be selected. Please refresh the page and try again."
          );
        }
        const type = typeArray[0].textContent;
        const expression = ruleTargetParent.querySelector('[data-input="expression"]').value;
        const message = ruleTargetParent.querySelector('[data-input="message"]').value;
        const textColor = ruleTargetParent.querySelector('[data-input="textColor"]').value;
        const backgroundColor = ruleTargetParent.querySelector('[data-input="backgroundColor"]').value;

        chrome.storage.sync.set({
          [ruleIndex]: {
            type,
            expression,
            message,
            textColor,
            backgroundColor,
          },
        });

        const toggleButton = ruleTargetParent.parentNode.parentNode.querySelector('[data-toggle="collapse"]');
        toggleButton.innerHTML = `${type} "${expression}" ↓`;

        displayAlert("success", "The rule was successfully saved!");
      } catch (error) {
        console.log(error);
        displayAlert(
          "danger",
          "The rule could not be saved. Please refresh the page and try again."
        );
      }
    }
  }

  function removeRule(rule) {
    if (rule.target.getAttribute("data-action") === "remove") {
      try {
        const ruleNode = rule.target.parentNode.parentNode.parentNode;
        chrome.storage.sync.remove(ruleNode.getAttribute("data-index"));
        ruleNode.remove();
        displayAlert("success", "The rule was successfully removed!");
      } catch (error) {
        console.log(error);
        displayAlert(
          "danger",
          "The rule could not be removed. Please refresh the page and try again."
        );
      }
    }
  }
  &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Our content script (content.js) represents the actual work being done by our extension. Every time we navigate to a page, it retrieves all rules from local storage and then checks if the URL of a page we are navigating to matches the pattern which we defined in a rule. If it does, then it will populate a paragraph element and insert it just after the opening &amp;lt;body&amp;gt; tag.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;content.js&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;
  &lt;code&gt;
  chrome.storage.sync.get(null, function (items) {
    Object.values(items).forEach(function (item) {
      const ruleType = item.type;
      const url = window.location.href;
      const expression = item.expression;
      if (
        (ruleType === "URL begins with" &amp;amp;&amp;amp; urlBeginsWith(url, expression)) ||
        (ruleType === "URL contains" &amp;amp;&amp;amp; urlContains(url, expression)) ||
        (ruleType === "URL ends with" &amp;amp;&amp;amp; urlEndsWith(url, expression))
      ) {
        document.body.prepend(
          createMessage(
            item.font,
            item.message,
            item.textColor,
            item.backgroundColor
          )
        );
      }
    });
  });

  function urlBeginsWith(url, expression) {
    const regex = new RegExp(expression + ".*");
    return regex.test(url);
  }

  function urlContains(url, expression) {
    const regex = new RegExp(".*" + expression + ".*");
    return regex.test(url);
  }

  function urlEndsWith(url, expression) {
    const regex = new RegExp(".*" + expression);
    return regex.test(url);
  }

  function createMessage(font, text, textColor, backgroundColor) {
    const paragraph = document.createElement("p");
    paragraph.style.backgroundColor = backgroundColor;
    paragraph.style.color = textColor;
    paragraph.style.fontFamily = font;
    paragraph.style.textAlign = "center";
    paragraph.style.padding = "1rem 0";
    paragraph.style.fontFamily = "Arial,Helvetica,sans-serif";
    paragraph.style.margin = "0 0 1rem 0";
    paragraph.innerText = text;
    return paragraph;
  }
  &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;In order to separate some of the element creation code, we also have a separate helpers file (helpers.js). The options.js file became too big and just wasn't easily scannable anymore. Those helper functions are mainly focused on creating the DOM elements for the options page.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;helpers.js&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;
  &lt;code&gt;
  function createTypeButtonGroup(value) {
    const typeButtonGroup = document.createElement("div");
    typeButtonGroup.classList.add("btn-group", "btn-group-toggle", "mb-3");
    typeButtonGroup.setAttribute("data-toggle", "buttons");
    typeButtonGroup.setAttribute("data-purpose", "type");

    // Create dropdown options based on RULE_TYPE_OPTIONS array
    for (i = 0; i &amp;lt; RULE_TYPE_OPTIONS.length; i++) {
      const typeOptionLabel = document.createElement("label");
      typeOptionLabel.classList.add("btn", "btn-secondary");
      typeOptionLabel.textContent = RULE_TYPE_OPTIONS[i];

      const typeOptionInput = document.createElement("input");
      typeOptionInput.setAttribute("type", "radio");
      typeOptionInput.setAttribute("name", "options");
      typeOptionInput.setAttribute("id", "option" + (i + 1));

      if (value === RULE_TYPE_OPTIONS[i]) {
        typeOptionInput.checked = true;
        typeOptionLabel.classList.add("active");
      }

      typeOptionLabel.appendChild(typeOptionInput);
      typeButtonGroup.appendChild(typeOptionLabel);
    }
    return typeButtonGroup;
  }

  function createExpressionInput(expression) {
    const inputGroup = document.createElement("div");
    inputGroup.classList.add("input-group", "mb-3");

    const inputGroupPrepend = document.createElement("div");
    inputGroupPrepend.classList.add("input-group-prepend");

    const inputGroupText = document.createElement("span");
    inputGroupText.classList.add("input-group-text");
    inputGroupText.innerText = "String:";
    inputGroupPrepend.appendChild(inputGroupText);

    const input = document.createElement("input");
    input.setAttribute("type", "text");
    input.setAttribute("class", "form-control");
    input.setAttribute("placeholder", "https://www.example.com");
    input.setAttribute("aria-label", "URL");
    input.setAttribute("minlength", "1");
    input.setAttribute("maxlength", "255");
    input.setAttribute("data-input", "expression");
    if (expression) {
      input.value = expression;
    }

    inputGroup.appendChild(inputGroupPrepend);
    inputGroup.appendChild(input);

    return inputGroup;
  }

  function createMessageInput(message) {
    const inputGroup = document.createElement("div");
    inputGroup.classList.add("input-group", "mb-3");

    const inputGroupPrepend = document.createElement("div");
    inputGroupPrepend.classList.add("input-group-prepend");

    const inputGroupText = document.createElement("span");
    inputGroupText.classList.add("input-group-text");
    inputGroupText.innerText = "Message:";
    inputGroupPrepend.appendChild(inputGroupText);

    const input = document.createElement("input");
    input.setAttribute("type", "text");
    input.setAttribute("class", "form-control");
    input.setAttribute("placeholder", "Hi there!");
    input.setAttribute("minlength", "1");
    input.setAttribute("maxlength", "255");
    input.setAttribute("aria-label", "Message");
    input.setAttribute("data-input", "message");
    if (message) {
      input.value = message;
    }

    inputGroup.appendChild(inputGroupPrepend);
    inputGroup.appendChild(input);
    return inputGroup;
  }

  function createColorInput(colorType, color) {
    const div = document.createElement("div");
    div.classList.add("mb-3");

    const label = document.createElement("label");
    const input = document.createElement("input");
    input.setAttribute("type", "color");
    input.setAttribute("width", "50");

    if (colorType === "textColor") {
      label.setAttribute("for", "textColor");
      label.innerText = "Text color:";
      input.setAttribute("data-input", "textColor");
      input.setAttribute("aria-label", "Text color");
      input.defaultValue = DEFAULT_TEXT_COLOR;
    }
    if (colorType === "backgroundColor") {
      label.setAttribute("for", "backgroundColor");
      label.innerText = "Background color:";
      input.setAttribute("data-input", "backgroundColor");
      input.setAttribute("aria-label", "Background color");
      input.defaultValue = DEFAULT_BACKGROUND_COLOR;
    }
    if (color) {
      input.value = color;
    }

    div.appendChild(label);
    div.appendChild(input);
    return div;
  }

  function createButton(type) {
    if (type === "save") {
      const saveButton = document.createElement("button");
      saveButton.innerText = "Save";
      saveButton.classList.add("btn", "btn-primary", "mb-3", "mt-3");
      saveButton.setAttribute("data-action", "save");
      return saveButton;
    }

    if (type === "remove") {
      const removeButton = document.createElement("button");
      removeButton.innerText = "Remove";
      removeButton.classList.add("btn", "btn-danger", "mb-3");
      removeButton.setAttribute("data-action", "remove", "mt-3");
      return removeButton;
    }
  }

  function displayAlert(type, text) {
    removeActiveAlert();
    const newAlert = document.createElement("div");
    newAlert.setAttribute("role", "alert");
    newAlert.innerText = text;
    if (type === "success") {
      newAlert.classList.add("alert", "alert-success");
    }
    if (type === "danger") {
      newAlert.classList.add("alert", "alert-danger");
    }
    document.body.prepend(newAlert);
    setTimeout(function () {
      newAlert.remove();
    }, 2000);
  }

  function removeActiveAlert() {
    const activeAlert = document.getElementsByClassName("alert");
    if (activeAlert.length &amp;gt; 0) {
      activeAlert[0].remove();
    }
  }

  function getCurrentNumberOfRules() {
    return parseInt(document.querySelectorAll(".rule").length, 10);
  }
  &lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Last but not least, we'll also add a config file (config.js) so we can easily extend with more patterns or change default values in the future.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;config.js&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;
  &lt;code&gt;
  const RULE_TYPE_OPTIONS = ["URL begins with", "URL contains", "URL ends with"];
  const DEFAULT_TEXT_COLOR = "#ffffff";
  const DEFAULT_BACKGROUND_COLOR = "#dc3545";
  &lt;/code&gt;
&lt;/pre&gt;

&lt;h2&gt;
  
  
  Extending the extension
&lt;/h2&gt;

&lt;p&gt;So that was basically all the code needed to develop this Chrome extension. Ofcourse, this is the most simple form it could take and there's a lot of room for improvement. To name a few possible adjustments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When adding a new rule, it should also check if there are any open tabs that match the pattern of that new rule and immediately insert the paragraph. Now, you'll need to refresh the page.&lt;/li&gt;
&lt;li&gt;Add more customization options: font family, font size, adding images ...&lt;/li&gt;
&lt;li&gt;The message is currently prepended to the &amp;lt;body&amp;gt;. That might produce unwanted results depending on the DOM structure. More testing on multiple (types of) websites and web applications is needed to discover gaps.&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope you liked this. Feel free to comment or ask questions.  &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>github</category>
    </item>
    <item>
      <title>Creating and using an SSH key pair on Windows</title>
      <dc:creator>Karel De Smet</dc:creator>
      <pubDate>Tue, 25 Aug 2020 18:54:48 +0000</pubDate>
      <link>https://dev.to/carlosds/creating-and-using-an-ssh-key-pair-on-windows-3g3m</link>
      <guid>https://dev.to/carlosds/creating-and-using-an-ssh-key-pair-on-windows-3g3m</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover photo by Jorien Loman on &lt;a href="https://unsplash.com/photos/nAZUJFrDlaY"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For some reason, I've struggled in the past with &lt;strong&gt;creating an SSH key pair&lt;/strong&gt; and &lt;strong&gt;adding it to an online service like Github&lt;/strong&gt;. This walkthrough focuses on just that for Github on Windows, but the process for other services or on another OS is quite similar.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SSH?
&lt;/h2&gt;

&lt;p&gt;SSH is a protocol for &lt;strong&gt;secure network communication&lt;/strong&gt;, based on the principle of &lt;strong&gt;public-key cryptography&lt;/strong&gt;. I like the analogy of one of my lecturers who said we need to see it as an unlimited stash of locks &lt;em&gt;(= public keys)&lt;/em&gt;. And we can provide any service with one of our locks and ask them to install it on their entrance door. We can then enter at any given time by using our &lt;em&gt;(private)&lt;/em&gt; key.&lt;/p&gt;

&lt;p&gt;So we'll now add a &lt;em&gt;lock&lt;/em&gt; to Github and use our key to open the (virtual) door. This will &lt;strong&gt;prevent us from having to authenticate with our username and password&lt;/strong&gt; every time we push, pull or otherwise interact with Github. &lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Windows 10&lt;/li&gt;
&lt;li&gt;OpenSSH installed and activated. This should be the case by default on the latest versions of Windows 10. If it's not, please refer to &lt;a href="https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse#installing-openssh-from-the-settings-ui-on-windows-server-2019-or-windows-10-1809"&gt;this page&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a new key
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Launch the command prompt.&lt;/li&gt;
&lt;li&gt;Type &lt;em&gt;ssh-keygen.exe&lt;/em&gt; and press &lt;em&gt;Enter&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Press &lt;em&gt;Enter&lt;/em&gt; again to save the key in the default file and folder (&lt;em&gt;c:\Users\yourUsername.ssh\id_rsa&lt;/em&gt;). I would advise against using another folder to store the key, as this might lead to permission issues. If you already have a key with the default name in that folder and you want to keep it, make sure to specify another file name (i.e. &lt;em&gt;c:\Users\yourUsername.ssh\id_rsa2&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;I would suggest to leave the passphrase empty. If you don't, you will be prompted to enter it every time you connect to the service. Just remember that you can add that extra layer of security if needed.&lt;/li&gt;
&lt;li&gt;Multiple confirmation messages will appear on your screen:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"Your identification has been saved in c:..."&lt;/em&gt;: This is the location of your private key.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Your public key has been saved in c:..."&lt;/em&gt;: Obviously, this is the location of your public key.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"The key fingerprint is: ..."&lt;/em&gt; and &lt;em&gt;"The key's randomart image is ..."&lt;/em&gt;: Just remember that both the fingerprint and the randomart image are simply a representation of your public key, making it easier to verify the authenticity of the server you are connecting to. Comparing the full key string, when in doubt, would be cumbersome. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vWhAIAeB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ygvlnd3rxj73zn8kq139.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vWhAIAeB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ygvlnd3rxj73zn8kq139.PNG" alt="SSH keygen example in Windows command prompt"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: if you'd like to learn more about randomart, &lt;a href="https://aarontoponce.org/drunken_bishop.pdf"&gt;"The Drunken Bishop"&lt;/a&gt; is an excellent paper that will tell you all about it. Thanks to &lt;a href="https://pthree.org/2013/05/30/openssh-keys-and-the-drunken-bishop/"&gt;Aaron Toponce&lt;/a&gt; for summarizing and archiving it.&lt;/em&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the key to Github
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Go to the folder containing your keypair (&lt;em&gt;c:\Users\username.ssh&lt;/em&gt; by default) and open the .pub file with a text editor.&lt;/li&gt;
&lt;li&gt;Copy the key but exclude the part after the final space (&lt;em&gt;username@device-name&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;In Github, go to &lt;em&gt;Settings -&amp;gt; SSH and GPG keys -&amp;gt; New SSH key&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Paste the key into the text area and click &lt;em&gt;"Add SSH key"&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yLXlsB6D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rt49p5snvb2sghnpknr0.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yLXlsB6D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rt49p5snvb2sghnpknr0.PNG" alt="Github settings page to add SSH key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it out
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When you're ready to test it out, make a commit on one of your local repositories and push the changes. Your terminal will state &lt;em&gt;"The authenticity of host 'github.com (140.82.118.3)' can't be established. RSA key fingerprint is SHA256:[...]. Are you sure you want to continue connecting (yes/no/[fingerprint])?"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Enter &lt;em&gt;"yes"&lt;/em&gt; to add Github to known_hosts, a file which resides in the same folder as your key pair. As a best practice, when connecting to a new service (or more specifically: server) via SSH, you should first add the domain name or IP address to this file, together with the public key. This ensures that a connection will only be established if the public key of the server you are connecting to equals the public key you have inserted into the known_hosts file for that server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Congratulations!&lt;/strong&gt; &lt;br&gt;
You can now securely connect to Github via SSH. You don't need to type in your username/password anymore to get going. &lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Never share your private key with anyone.&lt;/li&gt;
&lt;li&gt;Do not copy the key to another device. If you want to connect via SSH from another device, create a new key pair by following the same steps in this article.&lt;/li&gt;
&lt;li&gt;If any of your private keys are compromised, immediately delete the matching public key from all services/servers to which you are connecting via SSH.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>github</category>
    </item>
    <item>
      <title>Laying down the foundations for automated UI testing</title>
      <dc:creator>Karel De Smet</dc:creator>
      <pubDate>Wed, 05 Aug 2020 07:06:15 +0000</pubDate>
      <link>https://dev.to/carlosds/laying-down-the-foundations-for-automated-ui-testing-3bi8</link>
      <guid>https://dev.to/carlosds/laying-down-the-foundations-for-automated-ui-testing-3bi8</guid>
      <description>&lt;p&gt;Ah yes, automated UI testing. That thing you want to implement without really knowing why. Or how. But how difficult can it be?&lt;/p&gt;

&lt;p&gt;Well, quite difficult as it stands. It’s not just picking a tool and getting started, you need a solid foundation to make this work. Don’t make the mistake of building a proof of concept and presenting a flawless demo at the next management board. That will only heighten the astronomical expectations of your seniors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know what to achieve
&lt;/h2&gt;

&lt;p&gt;Some of those expectations might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reducing overall testing workload&lt;/li&gt;
&lt;li&gt;Reducing the amount of bugs that make it into the production environment&lt;/li&gt;
&lt;li&gt;Improving customer satisfaction&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's important to know what's expected. And don't just accept what they throw at you. Encourage your peers and seniors to be realistic. Test automation isn't going to bring world peace. &lt;/p&gt;

&lt;h2&gt;
  
  
  Make sure you know how to test it manually
&lt;/h2&gt;

&lt;p&gt;Don’t try to run before you can walk. It’s better to develop a &lt;strong&gt;sound manual testing flow&lt;/strong&gt; before you get to work on automation. For me personally, that means having a set of test cases per application in a spreadsheet (view an example here in Google ), with at least the following columns per test case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ID&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Category&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority&lt;/strong&gt;:
Keep it simple. “Low”, “Medium” and “High” is usually sufficient. You can use this to see which test cases can be skipped when there’s not enough time to test them all. If you sometimes experience a lack of time to execute all test cases, it’s best to always start with the high and medium priority cases. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scenario&lt;/strong&gt;:
Don’t describe the scenario with too much implementation detail. In my opinion, it’s better to write “log in” then “click on the username field, type in your username, click on the password field, type in your password, click on submit”. In most cases, that’s just too much detail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expected result&lt;/strong&gt;:
The same goes for the expected result. Too much implementation detail takes away the readability of your test cases. Yes, you’ll need that for automation. But you can detail that in a separate file the moment you need it. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status&lt;/strong&gt;:
I usually tend to go with 5 possible values:

&lt;ul&gt;
&lt;li&gt;Not completed&lt;/li&gt;
&lt;li&gt;Passed&lt;/li&gt;
&lt;li&gt;Failed&lt;/li&gt;
&lt;li&gt;Blocked&lt;/li&gt;
&lt;li&gt;Not applicable&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tester name&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extra information&lt;/strong&gt;: 
This could be a link to JIRA, Confluence, Stackoverflow or just plain text describing whatever extra information the tester should know. If the test case has a status of “Failed”, “Blocked” or “Not applicable”, you should elaborate why here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, you’ll probably want to &lt;strong&gt;collect input and feedback&lt;/strong&gt; from your team members on how these test cases fare. Are they clear enough? Too specific? Any important test cases missing? Ask your colleagues to execute (some of) these test cases and let them provide honest feedback. &lt;br&gt;
Then work through the feedback and discuss how you handled their questions and remarks. &lt;strong&gt;Re-iterate if necessary.&lt;/strong&gt; In the end, any colleague that understands the application from a business perspective should be able to finish the whole set without asking themselves too many questions. &lt;/p&gt;

&lt;h2&gt;
  
  
  Write down the testing agreements
&lt;/h2&gt;

&lt;p&gt;Next, define testing agreements so everyone’s on the same line. Some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which &lt;strong&gt;browser&lt;/strong&gt; should be used for desktop testing? &lt;/li&gt;
&lt;li&gt;Which &lt;strong&gt;device&lt;/strong&gt; should be used for mobile testing?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; should these test cases be executed? Daily, weekly, monthly, before or after every release ...? If the full set is relatively large, you could also indicate the frequency per category.&lt;/li&gt;
&lt;li&gt;In which &lt;strong&gt;environment&lt;/strong&gt; should these test cases be executed?&lt;/li&gt;
&lt;li&gt;What &lt;strong&gt;test data&lt;/strong&gt; can and should be used? It’s advisable to have a separate tab that contains all test data and update it regularly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, discuss these agreements together with your co-workers (and perhaps your seniors) to ensure you’re all on the same line. Communicate them clearly and include them in a separate tab of the spreadsheet that contains your test cases. &lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the time you spend on manual testing
&lt;/h2&gt;

&lt;p&gt;If you want to prove that automated UI testing is decreasing your workload, you need to figure out what manual testing incurs. So when you’re performing tests, just &lt;strong&gt;start the timer and note down how much time it took to complete&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Don’t just stick to the actual testing part though. Include everything that comes with it such as registering bugs, re-testing, discussing outcomes with your team members and communicating the results, as these are also tasks that could be (partially) automated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aim for the middle
&lt;/h2&gt;

&lt;p&gt;Et voila! You can now tell your boss you’re embarking on a test automation adventure, and you’ll return with automated test cases. How many automated test cases you ask? Well, that depends.&lt;/p&gt;

&lt;p&gt;Looking back at the intro, I warned against the “proof of concept” (POC) method to get some management backing for the time you’ll spend automating test cases. The problem with this approach is that most people will take easy-to-automate test cases and then management will think “Did he automate this in 2 days? Think about what he can do in 2 months!”.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;strong&gt;you usually can’t automate the whole set without getting some feedback in-between&lt;/strong&gt;. It’s too risky, and you might want to use that session to ask for more resources (e.g. extra software or assistance from developers). So it’s best to aim for the middle and take a reasonable number of test cases of average complexity. In my experience, somewhere between 5 to 15 percent of the total number should suffice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking a tool
&lt;/h2&gt;

&lt;p&gt;Now it's up you to &lt;strong&gt;make an educated decision on which tool to use&lt;/strong&gt;. Although I wouldn't get too hung up on the details. Just make sure you're not stuck by committing to a big investment or long-term contract, so you can still switch if you feel you need to. &lt;/p&gt;

&lt;p&gt;Good luck!&lt;/p&gt;

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