<?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: sania-dsouza</title>
    <description>The latest articles on DEV Community by sania-dsouza (@saniadsouza).</description>
    <link>https://dev.to/saniadsouza</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%2F278541%2Fed23f27f-80c8-4e89-a60c-6b42eeea9fd0.png</url>
      <title>DEV Community: sania-dsouza</title>
      <link>https://dev.to/saniadsouza</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saniadsouza"/>
    <language>en</language>
    <item>
      <title>Alexa custom skill workflow using Github Actions</title>
      <dc:creator>sania-dsouza</dc:creator>
      <pubDate>Thu, 09 Dec 2021 05:24:05 +0000</pubDate>
      <link>https://dev.to/saniadsouza/alexa-custom-skill-workflow-using-github-actions-543p</link>
      <guid>https://dev.to/saniadsouza/alexa-custom-skill-workflow-using-github-actions-543p</guid>
      <description>&lt;h3&gt;
  
  
  My Workflow
&lt;/h3&gt;

&lt;p&gt;Project link: &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sania-dsouza"&gt;
        sania-dsouza
      &lt;/a&gt; / &lt;a href="https://github.com/sania-dsouza/devto-gha-hack2021"&gt;
        devto-gha-hack2021
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
DevTo - Github Actions : Hackathon 2021&lt;/h1&gt;
&lt;p&gt; This repo and project was created as an entry into the DevTo-Github Hackthon 2021, which focused on integrating Github Actions in a project. &lt;/p&gt;
&lt;p&gt; Category chosen: &lt;b&gt; Interesting IoT &lt;/b&gt; &lt;/p&gt;
&lt;h2&gt;
Getting Started&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Ensure you have node installed. Find out: &lt;code&gt;node -v&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clone the repo : &lt;code&gt;git clone &lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install dependencies : &lt;code&gt;npm install&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;This project uses Jovo to create the Alexa skill. Jovo provides a 'debugger' that helps to view and test interactions with Alexa. Run it using &lt;code&gt;jovo run&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click Launch to start the app&lt;/li&gt;
&lt;li&gt;The Nutri Planner app must open and allow you to interact with this skill&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note: The app currently just allows the user to choose from a given list of responses. This is extendable to accept any custom user input to mimic regular Alexa behavior.&lt;/p&gt;
&lt;h2&gt;
Github Actions integration&lt;/h2&gt;
&lt;p&gt;The Github Actions workflow is the &lt;code&gt;.github/workflows/node.js.yml&lt;/code&gt; file
The workflow is designed to function…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/sania-dsouza/devto-gha-hack2021"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;License: MIT &lt;/p&gt;

&lt;h4&gt;
  
  
  What is this about?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;This project is the development of a custom Alexa skill using the Jovo platform that 'enables professional teams to build and run apps that work across smart speakers, the web, mobile, and more.'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;The app is a nutrition planner that takes meal inputs from the user; the app currently accepts input from the user for breakfast, lunch and dinner and provides hard-coded replies for the same right now (see picture below as seen in the Jovo debugger) &lt;/li&gt;
&lt;li&gt;While not implemented in this project, the goal is to save the user's input and count the number of calories consumed by the user in a day, for health monitoring. &lt;/li&gt;
&lt;li&gt;Aside from creating the app and using Github Actions for the workflow, the skill was built and deployed to the Alexa Developer Console too for registration and further testing (a screenshot is attached later in this post).&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now for where Github Actions comes in! &lt;br&gt;
The following Actions have been used throughout the workflow: &lt;br&gt;
~ &lt;code&gt;checkout@v2&lt;/code&gt; : To checkout code &lt;br&gt;
~ &lt;code&gt;setup-node@v2&lt;/code&gt; : To get node installed &lt;br&gt;
~ &lt;code&gt;upload-artifact@v2&lt;/code&gt; : To upload important artifacts such as the whole skill code and test reports to the workflow run &lt;br&gt;
~ &lt;code&gt;codecov-action@v2&lt;/code&gt; : To report test coverage code to Codecov -- the code coverage tool &lt;br&gt;
~ &lt;code&gt;appleboy/lambda-action@master&lt;/code&gt; : To deploy zipped code to AWS Lambda &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A number of secrets have been used to run the workflow as expected such as the AWS_ACCESS_KEY_ID, AWS_REGION etc. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The list of jobs in the workflow are below: &lt;br&gt;
~ &lt;code&gt;build&lt;/code&gt;&lt;br&gt;
~ &lt;code&gt;static-code-analysis&lt;/code&gt;&lt;br&gt;
~ &lt;code&gt;unit-test&lt;/code&gt;&lt;br&gt;
~ &lt;code&gt;code-coverage&lt;/code&gt;&lt;br&gt;
~ &lt;code&gt;virtual-alexa-tests&lt;/code&gt;&lt;br&gt;
~ &lt;code&gt;deploy-skill&lt;/code&gt;&lt;br&gt;
~ &lt;code&gt;store-artifacts&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codecov: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zeT3Fzu8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devto-gha-hack2021-dev-serverlessdeploymentbucket-cyop4jq1fr3i.s3.amazonaws.com/Screen%2BShot%2B2021-12-09%2Bat%2B12.22.25%2BAM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zeT3Fzu8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devto-gha-hack2021-dev-serverlessdeploymentbucket-cyop4jq1fr3i.s3.amazonaws.com/Screen%2BShot%2B2021-12-09%2Bat%2B12.22.25%2BAM.png" alt="Codecov" width="880" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Jovo Debugger: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QxF2rr73--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devto-gha-hack2021-dev-serverlessdeploymentbucket-cyop4jq1fr3i.s3.amazonaws.com/Screen%2BShot%2B2021-12-08%2Bat%2B11.30.34%2BPM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QxF2rr73--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devto-gha-hack2021-dev-serverlessdeploymentbucket-cyop4jq1fr3i.s3.amazonaws.com/Screen%2BShot%2B2021-12-08%2Bat%2B11.30.34%2BPM.png" alt="Jovo Debugger Test run" width="880" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Github Actions run: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tEHomSZP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devto-gha-hack2021-dev-serverlessdeploymentbucket-cyop4jq1fr3i.s3.amazonaws.com/Screen%2BShot%2B2021-12-08%2Bat%2B11.26.55%2BPM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tEHomSZP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devto-gha-hack2021-dev-serverlessdeploymentbucket-cyop4jq1fr3i.s3.amazonaws.com/Screen%2BShot%2B2021-12-08%2Bat%2B11.26.55%2BPM.png" alt="Latest Workflow run" width="880" height="415"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Submission Category :
&lt;/h3&gt;
&lt;h4&gt;
  
  
  &lt;em&gt;Interesting IoT&lt;/em&gt;
&lt;/h4&gt;
&lt;h3&gt;
  
  
  Yaml File:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Node.js CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest
    name: Build
    strategy:
      matrix:
        node-version: [14.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npm install

  static-code-analysis: 
    runs-on: ubuntu-latest
    name: Static code analysis 
    needs: build
    steps: 
    - name: Checkout
      uses: actions/checkout@v2
    - run: |
        npm install
        npm run eslint

  unit-test: 
    runs-on: ubuntu-latest
    name: Unit test using Jest
    needs: static-code-analysis
    steps: 
    - name: Checkout
      uses: actions/checkout@v2
    - run: |
        npm install
        npm run test
    - name: Upload results
      uses: actions/upload-artifact@v2
      with:
        name: unit-test-report
        path: reports/test-report.html

  code-coverage: 
    runs-on: ubuntu-latest
    name: Code Coverage using Codecov
    needs: unit-test
    steps:
    - name: Checkout
      uses: actions/checkout@v2
    - run: |
        npm install
        npm run codecov
    - name: Codecov push results
      uses: codecov/codecov-action@v2
      with:
          token: ${{ secrets.CODECOV_TOKEN }} 

  virtual-alexa-tests: 
    runs-on: ubuntu-latest
    name: Test on Virtual Alexa
    needs: code-coverage
    steps: 
    - name: Checkout
      uses: actions/checkout@v2
    - run: |
        npm install
        npm run test-virtual
    - name: Upload results
      uses: actions/upload-artifact@v2
      with:
        name: virtual-test-report
        path: reports/test-report.html  

  deploy-skill: 
    name: Build and deploy lambda
    needs: virtual-alexa-tests
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Install dependencies and zip folder contents
        run: |
          npm install &amp;amp;&amp;amp; zip -r bundle.zip .
      - name: deploy zip to aws lambda
        uses: appleboy/lambda-action@master
        with:
          aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws_region: ${{ secrets.AWS_REGION }}
          function_name: 'devto-gha-hack2021-dev-handler'
          zip_file: bundle.zip

  store-artifacts:
    name: Store skill code
    if: always()
    runs-on: ubuntu-latest
    needs: deploy-skill
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Upload code
        uses: actions/upload-artifact@v2
        with:
          name: code
          path: ${{ github.workspace }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;A screenshot of the skill in action on the Alexa Developer Console: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0f0f52NP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devto-gha-hack2021-dev-serverlessdeploymentbucket-cyop4jq1fr3i.s3.amazonaws.com/Screen%2BShot%2B2021-12-09%2Bat%2B12.10.04%2BAM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0f0f52NP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devto-gha-hack2021-dev-serverlessdeploymentbucket-cyop4jq1fr3i.s3.amazonaws.com/Screen%2BShot%2B2021-12-09%2Bat%2B12.10.04%2BAM.png" alt="Alexa Developer skill" width="880" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Author: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__user ltag__user__id__278541"&gt;
  
    .ltag__user__id__278541 .follow-action-button {
      background-color: #a33bc9 !important;
      color: #000000 !important;
      border-color: #a33bc9 !important;
    }
  
    &lt;a href="/saniadsouza" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LD-nHE25--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--ohvDvfi---/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/278541/ed23f27f-80c8-4e89-a60c-6b42eeea9fd0.png" alt="saniadsouza image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/saniadsouza"&gt;sania-dsouza&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/saniadsouza"&gt;..finding my balance between passion and fulfillment..&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>actionshackathon21</category>
      <category>aws</category>
      <category>javascript</category>
      <category>iot</category>
    </item>
    <item>
      <title>Automate emails using Google API</title>
      <dc:creator>sania-dsouza</dc:creator>
      <pubDate>Sat, 05 Jun 2021 01:42:07 +0000</pubDate>
      <link>https://dev.to/saniadsouza/automate-emails-using-google-api-1hk2</link>
      <guid>https://dev.to/saniadsouza/automate-emails-using-google-api-1hk2</guid>
      <description>&lt;p&gt;The Gmail API provides a neat way to automate email tasks such as reading and sending among others. This piece illustrates how you could read emails and create and send them, all automatically. &lt;/p&gt;

&lt;p&gt;Automated emails could be used as a part of a larger process of automating routine maintenance tasks for example. In my case, the intent is to download a CSV file which is sent in an email, process it and send the result as an email. &lt;/p&gt;

&lt;h3&gt;
  
  
  Steps:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Set-up your system to work with the Gmail API&lt;/li&gt;
&lt;li&gt;Read an email and download CSV from it &lt;/li&gt;
&lt;li&gt;Create email body and send email to concerned recipient&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  First, Set-up the Gmail API
&lt;/h4&gt;

&lt;p&gt;You can read about all of it from &lt;a href="https://developers.google.com/gmail/api/guides" rel="noopener noreferrer"&gt;here&lt;/a&gt;. There are language-specific guides to enable the Gmail API on your Google ID; I chose Node. &lt;/p&gt;

&lt;p&gt;Open the terminal in your machine or use a code editor (I am using VSCode). &lt;em&gt;Familiarity with node is assumed.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install the Google API library and required libraries:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install googleapis@39 --save
npm install fs readline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create an initial file with the code below. Call this file &lt;code&gt;index.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// index.js

const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
const SCOPES = [
    'https://www.googleapis.com/auth/gmail.readonly',
    'https://www.googleapis.com/auth/gmail.modify',
    'https://www.googleapis.com/auth/gmail.compose',
    'https://www.googleapis.com/auth/gmail.send'
  ];
const TOKEN_PATH = 'token.json';

// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) =&amp;gt; {
  if (err) return console.log('Error loading client secret file:', err);
  // Authorize a client with credentials, then call the Gmail API.
  authorize(JSON.parse(content), getAuth);
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  const {client_secret, client_id, redirect_uris} = credentials.installed;
  // console.log(redirect_uris);
  const oAuth2Client = new google.auth.OAuth2(
      client_id, client_secret, redirect_uris[0]);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, (err, token) =&amp;gt; {
    if (err) return getNewToken(oAuth2Client, callback);
    oAuth2Client.setCredentials(JSON.parse(token));
    callback(oAuth2Client);
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getNewToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) =&amp;gt; {
    rl.close();
    oAuth2Client.getToken(code, (err, token) =&amp;gt; {
      if (err) return console.error('Error retrieving access token', err);
      oAuth2Client.setCredentials(token);
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) =&amp;gt; {
        if (err) return console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

function getAuth(auth) {
    //var check = new Check(auth);

    console.log("Auth'ed")
  }

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

&lt;/div&gt;



&lt;h5&gt;
  
  
  Let's see what's happening here.
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;'Require' the libraries &lt;/li&gt;
&lt;li&gt;Declare the scopes: these are the activities we are going to use the Gmail API for. There is a list of them in the documentation linked before.&lt;/li&gt;
&lt;li&gt;Read the credentials for the Gmail account that is going to be used to send and read the emails. The &lt;code&gt;credentials.json&lt;/code&gt; file is derived from Google Cloud Platform. &lt;/li&gt;
&lt;li&gt;The following lines use the credentials from step 3 and then create an OAUTH2 client and authorize the client to perform the desired activity.&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  To create the &lt;code&gt;credentials.json&lt;/code&gt; file:
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;Login to &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;GCP&lt;/a&gt; using your account&lt;/li&gt;
&lt;li&gt;Create a project (Click the little downward arrow near the Google Cloud Platform logo on the left) &lt;/li&gt;
&lt;li&gt;Once the project is created, select it and then click Credentials on the sidebar.&lt;/li&gt;
&lt;li&gt;From the top menu, select Create Credentials and OAuth Client ID from the resultant drop-down. You will have to configure a few things depending on your situation:&lt;/li&gt;
&lt;li&gt;An app name&lt;/li&gt;
&lt;li&gt;the scopes that you saw from the &lt;code&gt;index.js&lt;/code&gt; file will have to be selected here also. For our needs, be sure to manually add the scopes if you don't see them in the table.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuj63qzrtkqooqqchjh9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuj63qzrtkqooqqchjh9.png" alt="Screen Shot 2021-04-12 at 1.26.31 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;That didn't create a set of credentials if you have been following with the process. It just creates an application under the project. To create a set of credentials, select Create Credentials again. 
Then select the application type; I selected the Web application as I intended to use the email reader utility as a web app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzxe34nlfp6xj7ybjgmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzxe34nlfp6xj7ybjgmz.png" alt="Screen Shot 2021-04-12 at 1.29.47 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add information pertaining to this new set of credentials. Make sure all the fields are filled as they are required for the utility to work. The fields have helpful tool-tips to guide you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukhbagz65w5mfctwkusr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukhbagz65w5mfctwkusr.png" alt="Screen Shot 2021-04-12 at 1.32.31 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Create. A pop-up with the Client ID and Client Secret shows up. You can close the pop-up and instead select the Download button on the row created on your Credentials page to download the credentials JSON file. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafk82o0tfkco8dv6ku3e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafk82o0tfkco8dv6ku3e.png" alt="Screen Shot 2021-04-12 at 1.36.16 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rename the file to &lt;code&gt;credentials.json&lt;/code&gt; and move it to your project folder.  &lt;/p&gt;

&lt;p&gt;My file looks like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmxnpdhnpbmenpgb7ezi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmxnpdhnpbmenpgb7ezi.png" alt="Screen Shot 2021-04-12 at 1.41.21 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that out of the way, you can now test the Gmail API setup. &lt;br&gt;
[UPDATE: You will have to rename "web" in the credentials file to "installed"]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the file
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Follow the instructions on the terminal. You will have to grant access to the email ID you want to use and then get the code from the resultant page (or address bar) and paste that on the terminal. (Side note: This took me a while to figure out but look out for a code in the aforementioned places and you can proceed).&lt;/p&gt;

&lt;p&gt;Once authentication is complete, you should see something like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4oe2agxwo2vg1uu4pqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4oe2agxwo2vg1uu4pqi.png" alt="Screen Shot 2021-04-12 at 1.51.23 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A file &lt;code&gt;token.json&lt;/code&gt; is now created in your folder which will be used hereafter by your application to read emails and send them. &lt;/p&gt;
&lt;h4&gt;
  
  
  Second, Read emails
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Install the necessary libraries
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install js-base64 cheerio open dotenv https fs mailparser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Create another file &lt;code&gt;readMail.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// readEmail.js

const {google} = require('googleapis');
var base64 = require('js-base64').Base64;
const cheerio = require('cheerio');
var open = require('open');
const dotenv = require('dotenv');
const https = require('https');
const fs = require('fs');
var Mailparser = require('mailparser').MailParser;

dotenv.config();

class Check{

    //auth is the constructor parameter.
    constructor(auth){
        this.me = process.env.GMAIL_USER;
        this.gmail = google.gmail({version: 'v1', auth});
        this.auth = auth;
    }

    //Check for emails
    checkForEmails(){
        var query = "from:support@example.com is:unread";
        // console.log(this.me);
        this.gmail.users.messages.list({
            userId: this.me,
            q: query 
        }, (err, res) =&amp;gt; {
            if(!err){
                //mail array stores the mails.
                var mails = res.data.messages;
                // console.log(mails);
                this.getMail(mails[0].id);
                // console.log(mails[0].id)
            }
            else{
                console.log(err);
            }
        });        
    }

    // read mail 
    getMail(msgId){
        //This api call will fetch the mailbody
        this.gmail.users.messages.get({
            userId: this.me,
            id: msgId
        }, (err, res) =&amp;gt; {
            if(!err){
                // console.log(res.data.payload);
                var body = res.data.payload.body.data;
                var htmlBody = base64.decode(body.replace(/-/g, '+').replace(/_/g, '/'));
                // console.log(htmlBody);
                var mailparser = new Mailparser();

                mailparser.on("end", (err,res) =&amp;gt; {
                    if(err) {
                        console.log(err);
                    }
                })

                mailparser.on('data', (dat) =&amp;gt; {
                    if(dat.type === 'text'){
                        const $ = cheerio.load(dat.textAsHtml);
                        var links = [];
                        // Get all links in the HTML
                        $('a').each(function(i) {
                            links[i] = $(this).attr('href');
                        });

                        console.log("Email read!");
                        // You can further process the email and parse for necessary information
                    }
                })

                mailparser.write(htmlBody);
                mailparser.end();
            }
        });
    }

module.exports = Check;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What is happening here? &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Require&lt;/code&gt; the libraries&lt;/li&gt;
&lt;li&gt;Initialize a &lt;code&gt;.env&lt;/code&gt; file which stores your Gmail Username and password. This is then used in the Check class constructor. &lt;/li&gt;
&lt;li&gt;Unread emails from the address &lt;code&gt;support@example.com&lt;/code&gt; are checked for. Replace this with the sender whose emails you want to read. Here, the first email (the latest) will be read.&lt;/li&gt;
&lt;li&gt;The mail body of the first email is read. You can parse and process this mail body. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But how would you run this file? Make a few changes to the &lt;code&gt;index.js&lt;/code&gt; file. The updated code is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// index.js

const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
const Check = require('./readEmail');
const SCOPES = [
    'https://www.googleapis.com/auth/gmail.readonly',
    'https://www.googleapis.com/auth/gmail.modify',
    'https://www.googleapis.com/auth/gmail.compose',
    'https://www.googleapis.com/auth/gmail.send'
  ];
const TOKEN_PATH = 'token.json';

// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) =&amp;gt; {
  if (err) return console.log('Error loading client secret file:', err);
  // Authorize a client with credentials, then call the Gmail API.
  authorize(JSON.parse(content), getAuth);
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  const {client_secret, client_id, redirect_uris} = credentials.installed;
  // console.log(redirect_uris);
  const oAuth2Client = new google.auth.OAuth2(
      client_id, client_secret, redirect_uris[0]);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, (err, token) =&amp;gt; {
    if (err) return getNewToken(oAuth2Client, callback);
    oAuth2Client.setCredentials(JSON.parse(token));
    callback(oAuth2Client);
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getNewToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) =&amp;gt; {
    rl.close();
    oAuth2Client.getToken(code, (err, token) =&amp;gt; {
      if (err) return console.error('Error retrieving access token', err);
      oAuth2Client.setCredentials(token);
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) =&amp;gt; {
        if (err) return console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

function getAuth(auth) {
    var check = new Check(auth);

    console.log("Auth'ed");
    check.checkForEmails();
  }

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Run the file again
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output on the console:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v81t2he6cqdtzqqkoaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v81t2he6cqdtzqqkoaj.png" alt="Screen Shot 2021-04-12 at 2.59.58 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can do a number of things with the resultant mail body like parse it, get a download link etc. &lt;/p&gt;

&lt;p&gt;Kudos on getting here! Now for the last part: create an email and send it! &lt;/p&gt;
&lt;h5&gt;
  
  
  Third, Compose email and send
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Install the library
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install nodemailer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Create a file &lt;code&gt;composeEmail.js&lt;/code&gt; and copy this code :
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// composeEmail.js

const {google} = require('googleapis');
const mailComposer = require('nodemailer/lib/mail-composer');
var base64 = require('js-base64').Base64;
const dotenv = require('dotenv');
dotenv.config();

class CreateMail{

    constructor(auth, to, sub, body){
        this.me = process.env.GMAIL_USER;    
        this.auth = auth;
        this.to = to;
        this.sub = sub;
        this.body = body;
        this.gmail = google.gmail({version: 'v1', auth});
    }

    // Construct the mail
    makeBody(){

        let mail = new mailComposer({
            to: this.to,
            text: this.body,
            subject: this.sub,
            textEncoding: "base64"
        });

    //Compiles and encodes the mail.
    mail.compile().build((err, msg) =&amp;gt; {
    if (err){
        return console.log('Error compiling email ' + error);
        } 

    const encodedMessage = Buffer.from(msg)
              .toString('base64')
              .replace(/\+/g, '-')
              .replace(/\//g, '_')
              .replace(/=+$/, '');


        this.sendMail(encodedMessage);
      });
       }

    //Send the message to specified receiver
    sendMail(encodedMessage){
        this.gmail.users.messages.send({
            userId: process.env.GMAIL_USER,
            resource: {
                raw: encodedMessage,
            }
         }, (err, result) =&amp;gt; {
            if(err){
                return console.log('NODEMAILER - Returned an error: ' + err);
            }

            console.log("NODEMAILER - Sending email reply:", result.data);
        });
    }
}

module.exports = CreateMail;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What's happening here ? &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;'Require' the libraries&lt;/li&gt;
&lt;li&gt;Construct the email body using base64 &lt;/li&gt;
&lt;li&gt;Send the email using nodemailer to the recipient selected&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But again, how would you run this? Let's update the file &lt;code&gt;readEmail.js&lt;/code&gt; to call &lt;code&gt;composeEmail.js&lt;/code&gt;. The final code for &lt;code&gt;readEmail.js&lt;/code&gt; is below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const {google} = require('googleapis');
var base64 = require('js-base64').Base64;
const cheerio = require('cheerio');
var open = require('open');
const dotenv = require('dotenv');
const https = require('https');
const fs = require('fs');
var Mailparser = require('mailparser').MailParser;
const Email = require('./composeEmail');

dotenv.config();

class Check{

    //auth is the constructor parameter.
    constructor(auth){
        this.me = process.env.GMAIL_USER;
        this.gmail = google.gmail({version: 'v1', auth});
        this.auth = auth;
    }

    //Check for emails
    checkForEmails(){
        var query = "from:support@figma.com is:unread";
        // console.log(this.me);
        this.gmail.users.messages.list({
            userId: this.me,
            q: query 
        }, (err, res) =&amp;gt; {
            if(!err){
                //mail array stores the mails.
                var mails = res.data.messages;
                // console.log(mails);
                this.getMail(mails[0].id);
                // console.log(mails[0].id)
            }
            else{
                console.log(err);
            }
        });        
    }

    // read mail 
    getMail(msgId){
        //This api call will fetch the mailbody
        this.gmail.users.messages.get({
            userId: this.me,
            id: msgId
        }, (err, res) =&amp;gt; {
            if(!err){
                // console.log(res.data.payload);
                var body = res.data.payload.body.data;
                var htmlBody = base64.decode(body.replace(/-/g, '+').replace(/_/g, '/'));
                // console.log(htmlBody);
                var mailparser = new Mailparser();

                mailparser.on("end", (err,res) =&amp;gt; {
                    if(err) {
                        console.log(err);
                    }
                })

                mailparser.on('data', (dat) =&amp;gt; {
                    if(dat.type === 'text'){
                        const $ = cheerio.load(dat.textAsHtml);
                        var links = [];
                        var modLinks = [];
                        // Get all links in the HTML
                        $('a').each(function(i) {
                            links[i] = $(this).attr('href');
                        });

                        console.log("Email read!");
                    }
                })

                mailparser.write(htmlBody);
                mailparser.end();

                // Finally send the email 
                this.sendEmail("This is where the email's body goes.");
            }
        });
    }

    sendEmail(mail_body) {    
        var makeEmail = new Email(this.auth, &amp;lt;recipient_email_address&amp;gt;, 'Test subject', mail_body);

        // send the email
        makeEmail.makeBody();
    }
}

module.exports= Check;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Remember to replace the recipient's address in the sendEmail function above&lt;/em&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now run &lt;code&gt;index.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This in turn runs &lt;code&gt;readEmail.js&lt;/code&gt; which lastly runs &lt;code&gt;composeEmail.js&lt;/code&gt;. Phew!&lt;/p&gt;

&lt;p&gt;My console output looked like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbimvrfxhqca5o9kh5mh1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbimvrfxhqca5o9kh5mh1.png" alt="Screen Shot 2021-04-12 at 3.16.56 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally, the email!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkuv943lqi3sf1tenc6yp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkuv943lqi3sf1tenc6yp.png" alt="Screen Shot 2021-04-12 at 3.18.47 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Automating emails has many use cases and I hope this helped. Thanks and feedback is welcome!&lt;/p&gt;

</description>
      <category>automation</category>
      <category>node</category>
      <category>email</category>
      <category>google</category>
    </item>
    <item>
      <title>Rest your eye with AyeSpy -- Automated visual testing</title>
      <dc:creator>sania-dsouza</dc:creator>
      <pubDate>Sat, 27 Mar 2021 01:35:35 +0000</pubDate>
      <link>https://dev.to/saniadsouza/rest-your-eye-with-ayespy-visual-testing-2a36</link>
      <guid>https://dev.to/saniadsouza/rest-your-eye-with-ayespy-visual-testing-2a36</guid>
      <description>&lt;p&gt;A lesser known but still useful tool to automate visual testing for your project -- &lt;a href="https://github.com/newsuk/AyeSpy" rel="noopener noreferrer"&gt;Aye Spy&lt;/a&gt; can save precious time between development iterations. &lt;/p&gt;

&lt;h4&gt;
  
  
  Quick facts:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Requires Selenium Grid to run &lt;/li&gt;
&lt;li&gt;Inspired by Wraith and Backstop -- both popular visual regression testing tools&lt;/li&gt;
&lt;li&gt;According to its creators, the USP of this tool is its performance improvement over other visual testing tools (runs 40 screenshot comparisons in a minute)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  A sample test:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Install Aye Spy
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g aye-spy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install the Docker Selenium Grid image (Note that you will need Docker installed on your system already):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d -p 4444:4444  --name selenium-hub selenium/hub:3.141.59-titanium
docker run -d -P -p 5900:5900 --link selenium-hub:hub -v /dev/shm:/dev/shm selenium/node-chrome-debug:3.141.59-titanium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run these, one after the other. These install images of Selenium Grid and the Chrome browser on the Docker machine, respectively.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;ayespy init&lt;/code&gt; to generate the initial config file. This includes basic test configuration and test scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a sample config file:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nutj9f327q87t132wyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nutj9f327q87t132wyk.png" alt="Screen Shot 2021-03-26 at 9.10.29 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;gridUrl&lt;/em&gt; is a required field and specifies the address of the Docker Selenium grid image running on your computer. That is followed by the folders for baseline, test and difference screenshots respectively. &lt;em&gt;report&lt;/em&gt; holds the generated report which is an html file. &lt;br&gt;
&lt;em&gt;scenarios&lt;/em&gt; holds the test scenarios. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the test
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ayespy snap --browser chrome --config ayespy-config.json --run "Home"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Home&lt;/em&gt; is the scenario label from the config file (ayespy-config.json here) &lt;br&gt;
This creates a snapshot in a folder &lt;code&gt;latest&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo228aer7vzs12g80nhdo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo228aer7vzs12g80nhdo.png" alt="ayespy1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwdak07sqxix0npimcdc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwdak07sqxix0npimcdc.png" alt="Screen Shot 2021-03-26 at 9.17.45 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Save this snapshot as a baseline for further tests:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ayespy update-baseline --browser chrome --config ayespy-config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This updates the &lt;code&gt;baseline&lt;/code&gt; folder by copying the screenshot taken in the previous step to that folder. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo84lfolcoy4l6rnanguj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo84lfolcoy4l6rnanguj.png" alt="ayespy2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgujbxipqch5e7883zhku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgujbxipqch5e7883zhku.png" alt="ayespy3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compare the screenshots by running:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ayespy compare --browser chrome --config ayespy-config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a passing test, there would be no difference between the baseline and test screenshots and hence, no screenshot will be saved in the &lt;code&gt;generatedDiffs&lt;/code&gt; folder. Also, no report would be created in the &lt;code&gt;report&lt;/code&gt; folder. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rusu3xp07t6u1418apq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rusu3xp07t6u1418apq.png" alt="ayespy4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a failing test however, both folders will be created and have new files illustrating the difference. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4eu9ttea5kzino75oq43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4eu9ttea5kzino75oq43.png" alt="ayespy5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The html report is also written to the &lt;em&gt;report&lt;/em&gt; file and looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zv2eqi0rx3ng3mje7c0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zv2eqi0rx3ng3mje7c0.png" alt="ayespy6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Wins for Aye-spy:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Straight-forward setup &lt;/li&gt;
&lt;li&gt;Simple documentation&lt;/li&gt;
&lt;li&gt;Support for various viewports &lt;/li&gt;
&lt;li&gt;AWS S3 support to save images to &lt;/li&gt;
&lt;li&gt;Screenshots could be taken for multiple branches; this speeds up finding issues. &lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  What needs work:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;In order to run scripts before taking a screenshot, the run must have &lt;code&gt;selenium-webdriver&lt;/code&gt; and By exposed.&lt;/li&gt;
&lt;li&gt;Safari is not supported&lt;/li&gt;
&lt;li&gt;Does not support switching contexts to iFrames&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are other visual testing tools aside from Aye-spy; try these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;a href="https://dev.to/saniadsouza/visual-regression-testing-with-backstopjs-5509"&gt;BackstopJS&lt;/a&gt; and &lt;/li&gt;
&lt;li&gt;good ol' &lt;a href="https://dev.to/saniadsouza/test-for-visual-regression-with-jest-image-snapshot-4i54"&gt;Jest&lt;/a&gt;(jest-image-snapshot specifically).&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>beginners</category>
      <category>ux</category>
      <category>testing</category>
      <category>node</category>
    </item>
    <item>
      <title>Test for visual regression with Jest-image-snapshot</title>
      <dc:creator>sania-dsouza</dc:creator>
      <pubDate>Wed, 24 Mar 2021 15:54:12 +0000</pubDate>
      <link>https://dev.to/saniadsouza/test-for-visual-regression-with-jest-image-snapshot-4i54</link>
      <guid>https://dev.to/saniadsouza/test-for-visual-regression-with-jest-image-snapshot-4i54</guid>
      <description>&lt;p&gt;Jest has a feature called snapshot testing where a serializable value for the React tree is generated and then compared with a reference snapshot to check for differences.&lt;/p&gt;

&lt;p&gt;However, this article focuses instead on the more visual screenshot comparison that is provided by the &lt;a href="https://www.npmjs.com/package/jest-image-snapshot" rel="noopener noreferrer"&gt;jest-image-snapshot&lt;/a&gt; package. &lt;/p&gt;

&lt;h4&gt;
  
  
  Quick facts:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;It's a Jest matcher that performs image comparisons using pixelmatch &lt;/li&gt;
&lt;li&gt;jest-image-snapshot will not work with anything below Jest 20.x.x&lt;/li&gt;
&lt;li&gt;Could add a Gaussian blur for noise&lt;/li&gt;
&lt;li&gt;Once the snapshot is taken, it works exactly the same as Jest snapshots&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Sample test:
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;This test assumes you have Jest installed and have basic Jest know-how. The project was built using &lt;a href="https://reactjs.org/docs/create-a-new-react-app.html" rel="noopener noreferrer"&gt;Create React App&lt;/a&gt;. After this initial set-up, follow the steps below for visual testing goodness.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install the package : 
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;npm i --save-dev jest-image-snapshot&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
* Also install Puppeteer for user interaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;npm install puppeteer&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
* Test script: 

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

&lt;/div&gt;



&lt;p&gt;import { toMatchImageSnapshot } from 'jest-image-snapshot';&lt;br&gt;
const puppeteer = require('puppeteer');&lt;br&gt;
expect.extend({ toMatchImageSnapshot });&lt;/p&gt;

&lt;p&gt;it('CreateReactApp home', async () =&amp;gt; {&lt;br&gt;
    const browser = await puppeteer.launch();&lt;br&gt;
    const page = await browser.newPage();&lt;br&gt;
    await page.goto('&lt;a href="http://localhost:3000'" rel="noopener noreferrer"&gt;http://localhost:3000'&lt;/a&gt;);&lt;br&gt;
    const image = await page.screenshot();&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expect(image).toMatchImageSnapshot();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;})&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
* Run the test the first time: 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;npm run test&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This test opens the page running on the localhost, takes a snapshot and saves it in the folder `_image_snapshots_`.  

![jest2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4uu7fv6rxrgbdoxx6maq.png)
![jest3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l65kr2m563q7iukyz07j.png)

The screenshot:

![jest4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0mp3kbevpmp49e9thjvf.png)

* Make a change in the source code and re-run the test. 
The test fails this time. 

![jest5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s9k869xbf8iyf2jlx7ma.png)

Another sub-folder is created by the name `_diff_output_`.

![jest6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nbh43wz1vc3rziemnf0g.png)

The difference between the snapshot and the reference snapshot is shown marked: 

![jest7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyx3jx7pobpt6v9tc0j4.png)

This is a simple test. You could simulate more complicated user flows and take screenshots of sections of pages rather than full pages also. 

#### The good stuff:

* Easy setup once Jest is installed 
* Follows the Jest scaffolding of tests and can easily be integrated with existing functional tests 
* Multiple configuration options from the API
* Could set image difference sensitivity percentage 

#### Other things:

* No support for Typescript
* Outdated reference snapshots have to be removed manually and do not get cleared by using the `-u` flag of Jest. There is an environment variable that can be set up to remove the outdated snapshots but this utility must be used with caution. 

There are other paths for visual testing greatness:
1. [BackstopJS](https://dev.to/saniadsouza/visual-regression-testing-with-backstopjs-5509)
2. [Aye-spy](https://dev.to/saniadsouza/rest-your-eye-with-ayespy-visual-testing-2a36)





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

&lt;/div&gt;

</description>
      <category>testing</category>
      <category>ux</category>
      <category>node</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Prevent visual regression bugs with BackstopJS</title>
      <dc:creator>sania-dsouza</dc:creator>
      <pubDate>Mon, 22 Mar 2021 23:12:04 +0000</pubDate>
      <link>https://dev.to/saniadsouza/visual-regression-testing-with-backstopjs-5509</link>
      <guid>https://dev.to/saniadsouza/visual-regression-testing-with-backstopjs-5509</guid>
      <description>&lt;p&gt;Testing could be a lot of work if you are responsible for the visual (or even functional) feels of a website. And picking out visual differences after seemingly-unrelated code changes could be a pain. &lt;a href="https://github.com/garris/BackstopJS" rel="noopener noreferrer"&gt;BackstopJS&lt;/a&gt; is one of the tools that could help automate this bit. &lt;/p&gt;

&lt;p&gt;Following is a short tutorial on how to set up Backstop for your node project. &lt;/p&gt;

&lt;h4&gt;
  
  
  Quick facts:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;BackstopJS automates visual regression testing of a responsive web UI by comparing DOM screenshots over time.&lt;/li&gt;
&lt;li&gt;It includes an in-browser reporting feature, which allows you to check layout settings for print and screen, test approving, inspection, etc&lt;/li&gt;
&lt;li&gt;Docker rendering for cross-platform tests&lt;/li&gt;
&lt;li&gt;Simulating user interactions using Puppeteer&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  A quick test:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Install BackstopJS :
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g backstopjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Generate a Backstop config file:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backstop init 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file generated is &lt;code&gt;backstop.json&lt;/code&gt;. This has some default config settings that Backstop looks for when it runs. &lt;/p&gt;

&lt;p&gt;Some of the important config properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id : used for screenshot naming&lt;/li&gt;
&lt;li&gt;viewports : array of viewport sizes ; at least one must be specified&lt;/li&gt;
&lt;li&gt;scenarios: specifies different user flows for example. Section of the config file is shown below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4s1tclkbthgk8guih6y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4s1tclkbthgk8guih6y.png" alt="sampleinit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This test config file specifies a single scenario. All it does is navigate to the URL against &lt;code&gt;url&lt;/code&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the test
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backstop test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This first test fails since it didn’t find a reference image to compare the test screenshot with. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faz8wdmiblr99y06gm574.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faz8wdmiblr99y06gm574.png" alt="samplefail1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The report generated on browser (since the ‘report’ property was set to ‘browser’ in the config file) looks like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmzxq6bdnw39ep1wd80q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmzxq6bdnw39ep1wd80q.png" alt="samplefailreport1"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;You might've noticed that when you ran &lt;code&gt;backstop init&lt;/code&gt;, a set of folders was created. One of these was &lt;code&gt;bitmaps_test&lt;/code&gt;. This folder holds the test screenshots. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnid90k4wt7i7ux73jioa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnid90k4wt7i7ux73jioa.png" alt="Screen Shot 2021-03-22 at 1.00.58 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To make a test file the reference for future tests, run:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backstop approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This 'approves' the previous test screenshot as a standard or reference to compare future test screenshots with. It copies the screenshot from the &lt;code&gt;bitmaps_test&lt;/code&gt; folder to the &lt;code&gt;bitmaps_reference&lt;/code&gt; folder. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F377iddgi0wu1w6x3b8p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F377iddgi0wu1w6x3b8p4.png" alt="samplereference1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the test again:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backstop test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, the test passes. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zik4ftd7xpgt86gjhuf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zik4ftd7xpgt86gjhuf.png" alt="samplepass1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The corresponding browser report: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysa8fi1hi1e6i72o0uh7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysa8fi1hi1e6i72o0uh7.png" alt="samplepassreport1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Another test(with user interaction)
&lt;/h4&gt;

&lt;p&gt;Puppeteer is used to simulate user scenarios. &lt;br&gt;
Add another scenario to the &lt;code&gt;scenarios&lt;/code&gt; array in the config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
"scenarios": [
    {
      "label": "BackstopJS Homepage",
      "cookiePath": "backstop_data/engine_scripts/cookies.json",
      "url": "https://garris.github.io/BackstopJS/"
    },
     {
       "label": "BackStopJS Interaction scenario",
       "url": "https://garris.github.io/BackstopJS/",
       "clickSelector": ".cta"
     }
  ],
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the URL as specified by &lt;code&gt;url&lt;/code&gt; is opened and when the element specified by the selector &lt;code&gt;clickSelector&lt;/code&gt; is available, it is clicked; and then Backstop takes a screenshot. &lt;/p&gt;

&lt;p&gt;The first time you run &lt;code&gt;backstop test&lt;/code&gt;, the test will fail as it doesn't find the reference image. Run &lt;code&gt;backstop approve&lt;/code&gt; and &lt;code&gt;backstop test&lt;/code&gt; like before to get your test to pass. &lt;/p&gt;

&lt;p&gt;In my test, this second test fails as well because of a difference between the reference and the test screenshot. This is indicated by the hot pink text on the difference screenshot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n26o10gsb391yz3z8ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n26o10gsb391yz3z8ok.png" alt="samplefailreport2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, I could either fix what changed in the code or  update my reference using &lt;code&gt;backstop approve&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Backstop also has a scrubber utility that displays the difference between the reference and test screenshots by moving a mapper cursor, which is pretty intuitive. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghaaapiqo1kk63ywpmd1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghaaapiqo1kk63ywpmd1.png" alt="samplescrubber"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;You could automate more complex user flows, add more viewports, tune performance and other good stuff using Backstop; their documentation is great and they have a healthy user base also.&lt;/p&gt;

&lt;h4&gt;
  
  
  Backstop niceties:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Easy to set-up&lt;/li&gt;
&lt;li&gt;Good support for various viewports and Docker integration in case of cross-platform issues&lt;/li&gt;
&lt;li&gt;Intuitive reporting and inspection &lt;/li&gt;
&lt;li&gt;Easy Puppeteer scripts to simulate user interaction&lt;/li&gt;
&lt;li&gt;Variable image difference sensitivity &lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Not a con, but a housekeeping task:
&lt;/h4&gt;

&lt;p&gt;Outdated screenshots will have to be cleared manually or pushed to a &lt;code&gt;.gitignore&lt;/code&gt; file to ensure they don't make it into the remote repo.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PS: If you are anything (read curious) like me, that cutie on the Backstop logo is a ring-tailed lemur ;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Try these visual testing tools too: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/saniadsouza/test-for-visual-regression-with-jest-image-snapshot-4i54"&gt;Jest&lt;/a&gt;(jest-image-snapshot specifically)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/saniadsouza/rest-your-eye-with-ayespy-visual-testing-2a36"&gt;Aye-spy&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>beginners</category>
      <category>ux</category>
      <category>testing</category>
      <category>node</category>
    </item>
    <item>
      <title>Artillery -- Quick check your site's  performance </title>
      <dc:creator>sania-dsouza</dc:creator>
      <pubDate>Fri, 19 Mar 2021 01:14:00 +0000</pubDate>
      <link>https://dev.to/saniadsouza/artillery-quick-check-your-site-s-performance-kbf</link>
      <guid>https://dev.to/saniadsouza/artillery-quick-check-your-site-s-performance-kbf</guid>
      <description>&lt;p&gt;Jmeter is a great performance testing tool but has a steep learning curve and could take long to set up. Sometimes, there is just not enough time for that. Come in &lt;a href="https://artillery.io/" rel="noopener noreferrer"&gt;Artillery&lt;/a&gt; !&lt;/p&gt;

&lt;h4&gt;
  
  
  Quick facts:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Scripts written in YAML: which greatly reduces the code knowledge you need to have to set up tests. &lt;/li&gt;
&lt;li&gt;Designed for testing backend systems, such as API services, e-commerce backends, chat systems, game backends, databases, message brokers and queues, and anything else that can be communicated with over a network.&lt;/li&gt;
&lt;li&gt;Can't test frontends&lt;/li&gt;
&lt;li&gt;Two options: Artillery Core (free) and Artillery Pro (paid) &lt;/li&gt;
&lt;li&gt;Functional and load testing could be performed in one package&lt;/li&gt;
&lt;li&gt;Artillery Pro is used to run the performance tests on the Cloud i.e., AWS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Simple installation using NPM:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g artillery@1.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  A sample test
&lt;/h4&gt;

&lt;p&gt;This test (let's say it's called test-artillery.yml) :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runs against the URL: &lt;a href="https://blazedemo.com/" rel="noopener noreferrer"&gt;https://blazedemo.com/&lt;/a&gt;, &lt;/li&gt;
&lt;li&gt;follows a phased ramp-up/ramp-down set of steps: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;       &lt;em&gt;Warm-up&lt;/em&gt; : creates 1 virtual user each second for 5 seconds&lt;br&gt;
       &lt;em&gt;Ramp-up&lt;/em&gt; : creates 1 virtual user per second ramping up to 5 virtual users per second for 1 minute (60 seconds)&lt;br&gt;
       &lt;em&gt;Ramp-down&lt;/em&gt; : ramps down the number of concurrent virtual users to 0 over 15 seconds&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;asserts the aggregate p95 (95th percentile) latency is 200ms or less, and that the maximum error rate was less than 1%. &lt;/li&gt;
&lt;li&gt;includes a scenario which is used to test a virtual user flow
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config :
  target: "https://blazedemo.com/"
  plugins: 
    expect : {}
  phases:
    - duration: 5
      arrivalRate : 1
      name : Warm-up
    - duration: 60 
      arrivalRate: 1
      rampTo: 5
      name: Ramp up load
    - duration: 15
      arrivalRate: 1
      rampTo: 0
      name: Kill
  ensure:
    p95: 200
    maxErrorRate: 1
scenarios:
  - name: 'test scenario'
    flow:
      - get: 
          url: "/vacation.html"
          expect: 
            - statusCode: 200
      - think: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Run the test
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;artillery run test-artillery.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  A section of the full console output
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh8lai07vjg1dsdmdkru.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh8lai07vjg1dsdmdkru.png" alt="Screen Shot 2021-03-18 at 12.41.11 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Console output is not the most easily understood report of results so Artillery has a built-in html reporting feature which is easier on the eyes. &lt;/p&gt;

&lt;p&gt;First, create the report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;artillery run --output report.json test-artillery.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sample JSON generated
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9fgh2an0eikt8tnx6pd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9fgh2an0eikt8tnx6pd.png" alt="Screen Shot 2021-03-18 at 8.46.42 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, display that JSON as HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;artillery report --output report.html report.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sample HTML report
&lt;/h4&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/525887824" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;br&gt;
There is a ton of other stuff that Artillery can do including more complex user scenarios, running large-scale load tests, adding synthetic traffic in production to maintain a margin of safety against traffic spikes etc. &lt;br&gt;
This post only skimmed over Artillery Core; Artillery Pro which is a paid service, provides a seamless upgrade path from tests running on a developer’s machine, to scaling up &amp;amp; running the same test scripts from your organization’s AWS account.&lt;/p&gt;

&lt;h4&gt;
  
  
  What's to like about Artillery
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Scripts written in YAML; easy to learn&lt;/li&gt;
&lt;li&gt;Easy to setup and good documentation&lt;/li&gt;
&lt;li&gt;Good starting point for performance evaluation&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  What could improve
&lt;/h4&gt;

&lt;p&gt;Could do with more options for reporting; it currently supports Datadog (via agent or HTTP API), StatsD and InfluxDB with Telegraf + StatsD plugin while others like Prometheus are in progress.&lt;/p&gt;

&lt;p&gt;Happy testing! &lt;/p&gt;

</description>
      <category>performance</category>
      <category>beginners</category>
      <category>node</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to use environment variables at build using Github Actions</title>
      <dc:creator>sania-dsouza</dc:creator>
      <pubDate>Mon, 25 Jan 2021 18:10:08 +0000</pubDate>
      <link>https://dev.to/saniadsouza/how-to-use-environment-variables-at-build-using-github-actions-jha</link>
      <guid>https://dev.to/saniadsouza/how-to-use-environment-variables-at-build-using-github-actions-jha</guid>
      <description>&lt;p&gt;Hi!&lt;/p&gt;

&lt;p&gt;I recently ran into an issue when I needed to apply a continuous integration pipeline to my Gatsby portfolio site on Github. Github Actions seemed like a natural choice due to its ease of set-up. I used their readymade Node workflow action. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7zvksw7cg9zq2zhbfhm1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7zvksw7cg9zq2zhbfhm1.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting it up this way creates a .yml file that is then directly added to your root directory on your repository on the &lt;code&gt;.github/workflows&lt;/code&gt; path. &lt;/p&gt;

&lt;p&gt;This is what it initially looks like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Node.js CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [10.x, 12.x, 14.x, 15.x]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;My problem:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My site uses the Github API to pull my repos from my Github account. For this, I needed a personal access token. See how to create one &lt;a href="https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token" rel="noopener noreferrer"&gt;here&lt;/a&gt;. This value was placed in a .env file on my root folder which was ignored and not sent to Github (via a .gitignore file). This was for obvious security reasons. &lt;/p&gt;

&lt;p&gt;As a result, my workflow would fail at build when I pushed code, since the Github API couldn't find the key it needed to build the page. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fypc24agj6431r95kfd7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fypc24agj6431r95kfd7k.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The way to get around this was to make the environment variable in my .env to be available to the build without risking security. &lt;/p&gt;

&lt;p&gt;For this, I used an action &lt;a href="https://github.com/marketplace/actions/create-env-file" rel="noopener noreferrer"&gt;Create .env file&lt;/a&gt; which created an .env file at build and had a scope that would expire at build completion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [13.x]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}

    - name: Create .env file
      uses: SpicyPizza/create-envfile@v1
      with:
        envkey_GITHUB_LOGIN: "sania-dsouza"
        envkey_GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.SECRET_KEY }}

    - run: npm ci
    - run: npm run build
    - run: npm test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GITHUB_PERSONAL_ACCESS_TOKEN is the name of the key that the Github API is expecting to use in my code. &lt;/p&gt;

&lt;p&gt;I used the Secrets option on my repo to save the said token and referred to that secret (SECRET_KEY) in the .yml as above. Remember to not use GITHUB_ as a prefix while naming your secrets as those are reserved. &lt;br&gt;
Read &lt;a href="https://docs.github.com/en/actions/reference/encrypted-secrets" rel="noopener noreferrer"&gt;this&lt;/a&gt; for how to set secrets in Github. &lt;/p&gt;

&lt;p&gt;The result -- a passing build !&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8elcsoliflim9up21yk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8elcsoliflim9up21yk3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another solution would be to manually create a .env file at build (touch .env, push those environment variables to the file, cat .env). &lt;/p&gt;

&lt;p&gt;Hope this alleviates some of the pain in setting up a secure Github Actions pipeline for you. :) &lt;/p&gt;

&lt;p&gt;Also, feedback is most welcome.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>github</category>
    </item>
  </channel>
</rss>
