<?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>Forem: Beanworks</title>
    <description>The latest articles on Forem by Beanworks (@beanworks).</description>
    <link>https://forem.com/beanworks</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%2Forganization%2Fprofile_image%2F2761%2Ff53e797a-b169-489c-a9ab-0b70b7af5234.jpeg</url>
      <title>Forem: Beanworks</title>
      <link>https://forem.com/beanworks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/beanworks"/>
    <language>en</language>
    <item>
      <title>The Painful Parts of End-to-End Test Automation for your Windows Application</title>
      <dc:creator>pauliedoherty</dc:creator>
      <pubDate>Wed, 27 Jan 2021 21:58:39 +0000</pubDate>
      <link>https://forem.com/beanworks/the-painful-parts-of-end-to-end-test-automation-for-your-windows-application-1dde</link>
      <guid>https://forem.com/beanworks/the-painful-parts-of-end-to-end-test-automation-for-your-windows-application-1dde</guid>
      <description>&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;This post is targeted at all you test engineers who do not want to add C# and all the wonderful Visual Studio tools that come with it to your test automation stack. You’re thinking, since our web app and mobile app’s e2e testing are already implemented with NodeJS why should testing our windows app be any different?&lt;/p&gt;

&lt;p&gt;Here are the weapons we will be wielding to achieve this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A minimum of Windows 10 or Windows Server 2016 desktop&lt;/li&gt;
&lt;li&gt;An automation tool to drive your application under test (AUT) - &lt;a href="https://github.com/microsoft/WinAppDriver" rel="noopener noreferrer"&gt;WinAppDriver&lt;/a&gt; &amp;amp; &lt;a href="http://appium.io/" rel="noopener noreferrer"&gt;Appium&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A compatible automation client - &lt;a href="https://webdriver.io/" rel="noopener noreferrer"&gt;WebdriverIO&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A test runner - &lt;a href="https://mochajs.org/" rel="noopener noreferrer"&gt;Mocha&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A tool to run tests without manual user logon - &lt;a href="https://www.poweradmin.com/paexec/" rel="noopener noreferrer"&gt;paexec&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An Automation Server of sorts (details of this are out of scope of this post)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other handy extras:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/appium/appium-desktop" rel="noopener noreferrer"&gt;Appium Desktop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/win32/winauto/inspect-objects" rel="noopener noreferrer"&gt;Inspect.exe&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;...Note: Many parts in this post are only vaguely covered because there are already so many great resources out there. The parts that are described in detail were difficult to find good resources on, hence this post...&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Pain Point 1 - Preparing Test Environment for Automation (lil' painful)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  WinAppDriver &amp;amp; Appium:
&lt;/h2&gt;

&lt;p&gt;WinAppDriver is Microsoft's in-house developed tool to automate Universal Windows Platform (UWP), Windows Forms (WinForms), Windows Presentation Foundation (WPF), and Classic Windows (Win32) applications and is developed with Appium integration in mind. WinAppDriver is what does the heavy lifting behind the scenes. It carries out our mouse clicks and keyboard presses as we request. The image below shows the communication flow of our solution:&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%2Fbe24yizt6qgq6h2wt158.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%2Fbe24yizt6qgq6h2wt158.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Appium is a RESTful server that acts as a wrapper between WinAppDriver and your automation client. Appium accepts commands from WebdriverIO and forwards them to WinAppDriver. In response to a request, it returns a status code and logs to the automation client.&lt;/p&gt;

&lt;p&gt;To run WinAppDriver &amp;amp; Appium on our test environment we need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/Microsoft/WinAppDriver/releases" rel="noopener noreferrer"&gt;Download&lt;/a&gt; and install WinAppDriver&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development" rel="noopener noreferrer"&gt;Enable Developer mode&lt;/a&gt; on Windows&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;NodeJS&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With these prerequisites installed you are ready to automate using WebdriverIO’s test runner - &lt;a href="https://webdriver.io/docs/gettingstarted.html" rel="noopener noreferrer"&gt;wdio&lt;/a&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Pain Point 2 - Configuring WebdriverIO and running your first test (somewhat painful)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  WebdriverIO
&lt;/h2&gt;

&lt;p&gt;WebdriverIO (wdio) is a Selenium-like client service that sends requests to Appium via &lt;a href="https://w3c.github.io/webdriver/" rel="noopener noreferrer"&gt;W3C WebDriver Protocol&lt;/a&gt;. This is the framework we will use to write our test cases and specify how they will run.&lt;/p&gt;

&lt;p&gt;Before proceeding, please initialize a &lt;code&gt;nodejs&lt;/code&gt; project in your users home directory. If you're unsure how to do this &lt;a href="https://philna.sh/blog/2019/01/10/how-to-start-a-node-js-project/" rel="noopener noreferrer"&gt;Phil Nash's guide&lt;/a&gt; is excellent. I have creatively decided to call my project &lt;code&gt;my-project&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Taking from WebdriverIO's &lt;a href="https://webdriver.io/docs/clioptions.html" rel="noopener noreferrer"&gt;guide&lt;/a&gt; let's set up the test-runner by opening the Command Prompt and running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-project &amp;amp; &lt;span class="nb"&gt;cd &lt;/span&gt;my-project
npm &lt;span class="nb"&gt;install&lt;/span&gt; @wdio/cli
npx wdio config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will launch a helper utility where you choose what services and project structure you wish to use when running wdio. To get things started please mirror these selections:&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%2F03t84nzzj3p6tzae5l8o.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%2F03t84nzzj3p6tzae5l8o.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your project file tree should look like:&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%2F7bizz71v1jxkentn62cn.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%2F7bizz71v1jxkentn62cn.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wdio.conf.js&lt;/code&gt; is the file that holds the configuration properties of the entire communication flow diagram in the first section. It is now loaded with the properties needed to allow communications between our webdriver client and Appium server. However, we still need to define our &lt;a href="http://appium.io/docs/en/writing-running-appium/caps/" rel="noopener noreferrer"&gt;capabilities&lt;/a&gt;. Capabilities define which environment we are running Appium  on, which protocol Appium will use, and what AUT it will be automating. Let’s uphold tradition and use the classic calculator example. Below are the capabilities required to connect Appium to the calculator application on Windows Server 2019 along with other properties relevant to this post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// wdio.conf.js snippet&lt;/span&gt;
    &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/wd/hub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4723&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="nx"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;platformName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;windows&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;automationName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;windows&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;deviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WindowsPC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;Windows&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;System32&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;win32calc.exe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;

    &lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;logPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;     &lt;span class="c1"&gt;// logs are friends&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="nx"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mocha&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="nx"&gt;specs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/test/specs/**/*.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note here - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Defining appium in &lt;code&gt;services&lt;/code&gt; instructs wdio to start an Appium server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Runner&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;port&lt;/code&gt; and &lt;code&gt;baseUrl&lt;/code&gt; properties are used to connect wdio client to the host Appium server &lt;/li&gt;
&lt;li&gt;Defining &lt;code&gt;capabilities&lt;/code&gt; tells Appium what application to automate and what automation tool we want it to configure; &lt;code&gt;WindowsPC&lt;/code&gt; maps Appium to &lt;code&gt;WinAppDriver&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We define &lt;code&gt;mocha&lt;/code&gt; as the test &lt;code&gt;framework&lt;/code&gt; and&lt;/li&gt;
&lt;li&gt;We tell said &lt;code&gt;framework&lt;/code&gt; what location the &lt;code&gt;spec&lt;/code&gt; file are in&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;More details on Appium/WinAppDriver can be found &lt;a href="https://github.com/appium/appium-windows-driver#windowsdriver-specific-capabilities" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Side Note:
&lt;/h3&gt;

&lt;p&gt;I highly recommend using &lt;a href="https://github.com/appium/appium-desktop" rel="noopener noreferrer"&gt;Appium-Desktop&lt;/a&gt; as a development tool to troubleshoot any Appium connectivity issues. You can  download it &lt;a href="https://github.com/appium/appium-desktop/releases/tag/v1.19.1" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start Appium Desktop from Start Menu, open File -&amp;gt; New Session Window… and enter the &lt;code&gt;capabilities&lt;/code&gt; above in the 'Desired Capabilities' tab:&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%2F2p6z6nfqugvhk7ip7yhb.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%2F2p6z6nfqugvhk7ip7yhb.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Start Session&lt;/code&gt; to connect Appium to calculator app:&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%2Fj4tc0yix8pfn3foxfr76.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%2Fj4tc0yix8pfn3foxfr76.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you cannot connect to your AUT with capabilities in Appium-Desktop, you will also not be able to connect through wdio, so make sure to get things working here first. Here's a contrived error on Appium-Desktop to give an example of error logs:&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%2F2dlp1hat27m11vjfktx7.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%2F2dlp1hat27m11vjfktx7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now with our capabilities sorted we can start writing test cases for the wdio client.&lt;/p&gt;




&lt;h1&gt;
  
  
  Pain Point 3 - Controlling app elements (pretty painful)
&lt;/h1&gt;

&lt;p&gt;Shifting attention to our test spec file &lt;code&gt;./test/spec/example.e2e.js&lt;/code&gt;, we can define code to send requests to the Appium server. One of the beauties of webdriver is that the client object is accessible anywhere in wdio’s scope so we can directly call its properties. Let’s do some quick math to verify our calculators &lt;code&gt;sum()&lt;/code&gt; functionality =&amp;gt; &lt;code&gt;2 + 2 = 4&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./test/spec/example.e2e.js&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Quick math&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Two plus two is four&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~132&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;              &lt;span class="c1"&gt;// click 2&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~93&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;               &lt;span class="c1"&gt;// click +&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~132&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;              &lt;span class="c1"&gt;// click 2&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~121&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;              &lt;span class="c1"&gt;// click equals&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~150&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// get result&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4 &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// verify it equals 4 &lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test spec files are using &lt;a href="https://mochajs.org/" rel="noopener noreferrer"&gt;mochajs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Webdriver uses &lt;a href="https://webdriver.io/docs/api/expect.html" rel="noopener noreferrer"&gt;expect&lt;/a&gt; for assertions&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://webdriver.io/docs/api/browser/$.html" rel="noopener noreferrer"&gt;selector&lt;/a&gt; method uses &lt;code&gt;~&lt;/code&gt; to identify the &lt;code&gt;AutomationId&lt;/code&gt;. There’s not much clear cut documentation on this but you can work back from &lt;a href="http://appium.io/docs/en/drivers/windows/index.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; to figure it&lt;/li&gt;
&lt;li&gt;Yes, I did append a space on the &lt;code&gt;4&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We are finally ready to run our tests! In the Command Prompt run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx wdio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s the result from &lt;code&gt;npx wdio&lt;/code&gt; without appending a space:&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%2Fkbvzsfd3d0i3ul5frkd3.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%2Fkbvzsfd3d0i3ul5frkd3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here it is with appending a space:&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%2Fxnipfxdjteo6u9to44xc.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%2Fxnipfxdjteo6u9to44xc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OK, I cheated. I should have parsed the data before asserting. But I refuse to apologize.&lt;/p&gt;

&lt;h3&gt;
  
  
  Side Note:
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://docs.microsoft.com/en-us/windows/win32/winauto/inspect-objects" rel="noopener noreferrer"&gt;inspect.exe&lt;/a&gt; to find the AutomationId of your elements. Open inspect.exe and hover mouse over element you wish to inspect. Here’s an example of the ‘2’ button on the calculator app:&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%2F98fy67hjh29e9uvrw9vv.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%2F98fy67hjh29e9uvrw9vv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thus concludes setting up automation of a windows desktop application. &lt;/p&gt;

&lt;p&gt;On a side note, &lt;a href="https://github.com/microsoft/WinAppDriver/tree/master/Samples/JavaScript/packages/webdriverio" rel="noopener noreferrer"&gt;here's&lt;/a&gt; a great example of a scalable, maintainable wdio project using the Page Object Model pattern.&lt;/p&gt;




&lt;h1&gt;
  
  
  Pain point 4 - Running WDIO in a CI/CD pipeline without manual access to the GUI (painful)
&lt;/h1&gt;

&lt;p&gt;What we have achieved so far is great for development and presentation purposes. But to provide real value and execute this in a CI/CD pipeline we will need some extra setup in our test environment. Keeping cost saving in mind, we will not want our test environment running when tests are not being executed. It also becomes clear that Remote Desktop (RDP) is not very useful when it comes to automated pipelines. This now presents an interesting challenge - how do we run our tests with no user interaction or manual login step? One way is to send the test command over ssh. This is what we will explore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Aside:&lt;/em&gt;&lt;/strong&gt; configuring communication to the test environment is out of scope of this post; there are lots of great articles on setting up ssh servers out there.&lt;/p&gt;

&lt;p&gt;This part assumes we are running our test environment in a cloud based CI/CD pipeline, therefore the test environment is not being accessed through the GUI via RDP nor is it sitting beside you on your desk. With all that in mind, let’s remotely run our test cases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;@&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IP_ADDRESS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;'cd my-project &amp;amp; npx wdio'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmmmm, we are met with a pleasant 500 server error.&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%2Fmnxibnnni00gkizhopch.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%2Fmnxibnnni00gkizhopch.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s see what’s going on by checking our processes in parallel while wdio is attempting to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;@&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IP_ADDRESS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;'tasklist /fi "imagename eq node.exe" &amp;amp; tasklist /fi "imagename eq win32calc.exe"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&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%2F1th6t4cszlk0kzeafpdr.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%2F1th6t4cszlk0kzeafpdr.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hmmmm, &lt;code&gt;node.exe&lt;/code&gt; and &lt;code&gt;win32calc.exe&lt;/code&gt; are running as services on &lt;code&gt;Session# 0&lt;/code&gt;. Service processes on &lt;code&gt;Session# 0&lt;/code&gt; are reserved for user agnostic processes and do not run with a GUI. Therefore Appium/WinAppDriver has no GUI output for it to find the running calculator application which also doesn't have a GUI output. This is sad times for people hoping to test the GUI.&lt;/p&gt;

&lt;p&gt;Let’s take a step back to figure out what's going on. Let's open an RDP connection to our test environment. Here, we can directly run our tests, and in parallel check our running processes.&lt;br&gt;
Using the Command Prompt (cmd) on the test environment let's run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-project &amp;amp; npx wdio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And at the same time in a separate terminal run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tasklist /fi &lt;span class="s2"&gt;"imagename eq node.exe"&lt;/span&gt; &amp;amp; tasklist /fi &lt;span class="s2"&gt;"imagename eq win32calc.exe
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We see the tests have ran successfully:&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%2Fnma2qi76croldk399zgs.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%2Fnma2qi76croldk399zgs.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And our processes are showing an RDP Session which does use the GUI. All actual logged in users get assigned &lt;code&gt;Session# &amp;gt; 0&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%2Fi%2Fiba95rog5hw85c3y7z7h.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%2Fiba95rog5hw85c3y7z7h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To solve the issue in our CI/CD pipeline we must configure our test environment to run our code as a logged in user. This will allow &lt;code&gt;node.exe&lt;/code&gt; and &lt;code&gt;win32calc.exe&lt;/code&gt; to run as a GUI Session.&lt;/p&gt;

&lt;p&gt;To achieve this, we must do 2 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable Autologon  &amp;amp;&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;paexec&lt;/code&gt; to run processes as the System account user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This solution is stated by hepivax on &lt;a href="https://github.com/microsoft/WinAppDriver/issues/147#issuecomment-500903983" rel="noopener noreferrer"&gt;this thread&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable AutoLogon:
&lt;/h2&gt;

&lt;p&gt;Please use guide &lt;a href="https://docs.microsoft.com/en-us/troubleshoot/windows-server/user-profiles-and-logon/turn-on-automatic-logon" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  PAExec:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.poweradmin.com/paexec/" rel="noopener noreferrer"&gt;PAExec&lt;/a&gt; is an open-source equivalent of &lt;a href="https://docs.microsoft.com/en-us/sysinternals/downloads/psexec" rel="noopener noreferrer"&gt;PsExec&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PAExec allows you to run interactive command-prompts on local and remote servers as the System user account (it can do a lot more too!).&lt;/p&gt;

&lt;p&gt;Let’s install it on our test server and add it to a System Variable location so we can run it from anywhere with &lt;code&gt;cmd&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.poweradmin.com/paexec/paexec.exe" rel="noopener noreferrer"&gt;Download&lt;/a&gt; &lt;code&gt;paexec.exe&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy and paste to &lt;code&gt;C:\Windows\paexec.exe&lt;/code&gt; (If you’re unsure of this step refer to &lt;a href="https://www.computerhope.com/issues/ch000549.htm" rel="noopener noreferrer"&gt;this&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Test &lt;code&gt;cmd&lt;/code&gt; has access to &lt;code&gt;paexec.exe&lt;/code&gt; by running
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;where paexec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it should return &lt;code&gt;C:\Windows\paexec.exe&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can run &lt;code&gt;paexec&lt;/code&gt; in command prompt for more details on its use cases.&lt;/p&gt;

&lt;p&gt;Now that PAExec is installed and accessible, we can open a Command Prompt with PAExec so that &lt;code&gt;cmd&lt;/code&gt; is running under System user. Let's do this and verify where we are in the file system by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;paexec -s cmd /C "cd"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking this command down:&lt;br&gt;
&lt;code&gt;paexec&lt;/code&gt; -&amp;gt; Executes paexec.exe&lt;br&gt;
&lt;code&gt;-s&lt;/code&gt; -&amp;gt; Flags PAExec to run requested process under System user&lt;br&gt;
&lt;code&gt;cmd&lt;/code&gt; -&amp;gt; Open Command Prompt&lt;br&gt;
&lt;code&gt;/C&lt;/code&gt; -&amp;gt; Tells PAExec to pass a command to Command Prompt&lt;br&gt;
&lt;code&gt;“cd”&lt;/code&gt; -&amp;gt; Once paexec runs Command Prompt as System user it executes &lt;code&gt;cd&lt;/code&gt; which returns the current directory path&lt;/p&gt;

&lt;p&gt;This outputs our System account user path, verifying we are running &lt;code&gt;cmd&lt;/code&gt; as System user:&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%2Fan0y9kg3g9yciz9obaxr.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%2Fan0y9kg3g9yciz9obaxr.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now any command we choose to execute in the Command Prompt with PAExec will run under the System user&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;...piecing it all together&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Over in our test automation server (or whatever you are using to remotely trigger your test suite), run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;@&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IP_ADDRESS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;'paexec -s cmd /C "cd C:\Users\${USER}\my-project &amp;amp; npx wdio"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; Remember the new path is required here because we opened a &lt;code&gt;cmd&lt;/code&gt; window from the System user account with PAExec.&lt;/p&gt;

&lt;p&gt;...And in parallel check that &lt;code&gt;node.exe&lt;/code&gt; and &lt;code&gt;win32calc.exe&lt;/code&gt; are running as a logged on user with GUI access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;@&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IP_ADDRESS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="s1"&gt;'tasklist /fi "imagename eq node.exe" &amp;amp; tasklist /fi "imagename eq win32calc.exe"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at our processes:&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%2Fyvtsawyc94e4ijt5irdm.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%2Fyvtsawyc94e4ijt5irdm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We see they are running under a &lt;code&gt;Session# &amp;gt; 0&lt;/code&gt; and Voila! Test executed successfully.&lt;/p&gt;

</description>
      <category>testautomation</category>
      <category>windows</category>
      <category>appium</category>
      <category>webdriverio</category>
    </item>
    <item>
      <title>PHP For The Impatient</title>
      <dc:creator>Tom Lei</dc:creator>
      <pubDate>Fri, 25 Sep 2020 04:04:43 +0000</pubDate>
      <link>https://forem.com/beanworks/php-for-the-impatient-2j54</link>
      <guid>https://forem.com/beanworks/php-for-the-impatient-2j54</guid>
      <description>&lt;p&gt;We are primarily a PHP shop for about half of the engineering team here at Beanworks. PHP tends to have a bad reputation out there, but with PHP 7 (and potentially PHP 8), it's really becoming a mature and powerful language while keeping its simplicity.&lt;/p&gt;

&lt;p&gt;I always tell new engineers with no prior PHP experience that if you're able to write code in any language, you will be able to learn PHP pretty quickly.&lt;/p&gt;

&lt;p&gt;In this article we will explore some PHP syntax and compare it to Javascript or Typescript.&lt;/p&gt;

&lt;p&gt;I have added 🔁 icon to the ones that have identical syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Syntax
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Variables
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;In PHP land, all variables have to start with a dollar sign &lt;code&gt;$&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;$foo = 'bar';&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  NodeJS
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;let foo = 'bar';&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Template Literals
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;You can use variables in strings as long as the string is wrapped with double quote (&lt;code&gt;"&lt;/code&gt;), strings wrapped with single quotes will not evaluate the variables.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"My name is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  NodeJs
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`My name is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Constants 🔁
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;PHP has real constants, they must be capitalized, but otherwise the syntax is actually the same&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;const FOO = 'bar';&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  NodeJS
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;const FOO = 'bar';&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Arrays
&lt;/h3&gt;

&lt;h4&gt;
  
  
  PHP
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// you can iterate using foreach&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$arr&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mf"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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



&lt;h4&gt;
  
  
  NodeJS
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// you can iterate using for ... of&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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



&lt;h2&gt;
  
  
  Objects
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;In PHP land, objects are just arrays, associative arrays.  Instead of using &lt;code&gt;:&lt;/code&gt;, you need to use a &lt;a href="https://en.wikipedia.org/wiki/Fat_comma"&gt;double arrow&lt;/a&gt; &lt;code&gt;=&amp;gt;&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$myFavouriteVehicles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s1"&gt;'Toyota'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'4Runner'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Tacoma'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="s1"&gt;'Ford'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Bronco'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$fordVehicles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$myFavouriteVehicles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Ford'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// you can iterate key =&amp;gt; value using foreach&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$myFavouriteVehicles&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$brand&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$vehicles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="mf"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Javascript
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;myFavouriteVehicles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Toyota&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4Runner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tacoma&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ford&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bronco&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fordVehicles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myFavouriteVehicles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bronco&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// you can iterate key value with Object.entries&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vehicles&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myFavouriteVehicles&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Callback functions
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;In most of the PHP land, you still have to write call back functions like you had to in ES5 days, arrow function is only recently supported in PHP7.4&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP 🔁
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;$arr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// or in PHP 7.4+ you can use arrow function, no multi line though&lt;/span&gt;
&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  NodeJS
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{...});&lt;/span&gt;

&lt;span class="c1"&gt;// Arrow function since ES6&lt;/span&gt;
&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Classes &amp;amp; Inheritance
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;PHP's uses the same syntax extending classes and interfaces&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP 🔁
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;class Foo extends Bar implements FooBarInterface&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Typescript
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;class Foo extends Bar implements FooBarInterface&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Type system
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Contrary to popular beliefs, PHP does have a type system that works similar to other OOP languages, and with each new version of PHP, the type system is getting better.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;writeLine&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;LineWriter&lt;/span&gt; &lt;span class="nv"&gt;$writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Typescript
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;writeLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lineWriter&lt;/span&gt; &lt;span class="nx"&gt;lineWriter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Invoke Methods
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;To invoke a method, you need to use &lt;code&gt;-&amp;gt;&lt;/code&gt; instead of a dot in PHP.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;$logger-&amp;gt;log('PHP uses -&amp;gt;');&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  NodeJs
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;logger.log('Node uses .');&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generators 🔁
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;There is no special syntax in the function signature if you want to use the &lt;code&gt;yield&lt;/code&gt; keyword&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getLaptopBrands&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Iterator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s1"&gt;'Lenovo'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s1"&gt;'Asus'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s1"&gt;'Dell'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  TypeScript
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;getLaptopBrands&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IterableIterator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lenovo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Asus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Yield from generator
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;In PHP you can use the &lt;code&gt;yield from&lt;/code&gt; syntax to yield from a generator&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PHP
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getComputerBrands&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Iterator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="nf"&gt;getLaptopBrands&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="nf"&gt;getDesktopBrands&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  TypeScript
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;getLaptopBrands&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IterableIterator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;getLaptopBrands&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;getDesktopBrands&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</description>
    </item>
    <item>
      <title>GraphQL Schema Test with GraphQL Inspector</title>
      <dc:creator>Katelyn Truong</dc:creator>
      <pubDate>Thu, 30 Jul 2020 23:52:23 +0000</pubDate>
      <link>https://forem.com/beanworks/graphql-schema-inspector-test-32e7</link>
      <guid>https://forem.com/beanworks/graphql-schema-inspector-test-32e7</guid>
      <description>&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;We were nearing the alpha release of &lt;strong&gt;one of our new modules&lt;/strong&gt; which includes the release of a &lt;strong&gt;new mobile application.&lt;/strong&gt; Our web app, new mobile app and our desktop app, all consume our graphQL API. &lt;br&gt;
To increase the reliability of our platform, we needed to formalize and implement a backwards compatibility and deprecation policy for our GraphQL API.&lt;br&gt;
The rules to add or delete from schema were&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only add new fields. &lt;/li&gt;
&lt;li&gt;If you want to remove a field, deprecate it and remove it when it’s no longer in use. &lt;/li&gt;
&lt;li&gt;If you want to change an existing field, create a new field, deprecate the old field and remove it once it’s no longer in use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, since the mobile app has a much larger footprint as users might be using the app with the older version with old graphQL schema, having a test for it seemed inevitable. Our existing e2e automation on mobile did not cover all graphQL endpoints, and it would have been too expensive and time consuming to cover all endpoints from a user interface perspective. Moreover, we wanted backward compatibility test to be a test that runs before we run e2e tests to detect breaking changes earlier in the cycle.&lt;/p&gt;

&lt;p&gt;Since we needed something light weight and graphQL compatible, we decided to use &lt;a href="https://graphql-inspector.com/" rel="noopener noreferrer"&gt;GraphQL Inspector Tool&lt;/a&gt; to &lt;strong&gt;ensure backward compatibility&lt;/strong&gt; and prevent schema breaking changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bitbucket (Git)&lt;/li&gt;
&lt;li&gt;Codeship Pro for CI/CD workflow&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;GraphQL&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Structure
&lt;/h3&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%2F40iho56itks2rj4ygbor.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%2F40iho56itks2rj4ygbor.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why GraphQL Inspector Tool?
&lt;/h3&gt;

&lt;p&gt;We decide to use GraphQL Inspector Tool for graphQL backward capability test because of following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GraphQL compatibility &lt;/li&gt;
&lt;li&gt;Ease of implementation with custom rules &lt;/li&gt;
&lt;li&gt;Easy CLI command for CI integration &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Steps to Implement
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Step1: Create a dockerfile for &lt;a href="https://graphql-inspector.com/" rel="noopener noreferrer"&gt;GraphQL Inspector&lt;/a&gt;
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Dockerfile template can be found here at &lt;a href="https://hub.docker.com/r/kamilkisiela/graphql-inspector" rel="noopener noreferrer"&gt;graphQL inspector docker hub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dockerfile can be named as &lt;code&gt;Dockerfile.graphqlInspector&lt;/code&gt; in &lt;code&gt;./codeship&lt;/code&gt; folder
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:10-slim&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; LOG_LEVEL "debug"&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; PRIVATE_SSH_KEY&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt update 
&lt;span class="k"&gt;RUN &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ssh
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn global add @graphql-inspector/cli@2.1.0 graphql
&lt;span class="k"&gt;RUN &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git-all

&lt;span class="c"&gt;# Copy the decrypt ssh private key for Bitbucket Readonly User to allow codeship access to &lt;/span&gt;
&lt;span class="c"&gt;# Our repository from codeship container  &lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /root/.ssh
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PRIVATE_SSH_KEY&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /root/.ssh/id_rsa
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /root/.ssh/id_rsa
&lt;span class="k"&gt;RUN &lt;/span&gt;ssh-keyscan &lt;span class="nt"&gt;-H&lt;/span&gt; bitbucket.org &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /root/.ssh/known_hosts

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /app
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Step2: Create an entrypoint file
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;This file will be triggered after docker image is built &lt;/li&gt;
&lt;li&gt;Name file &lt;code&gt;entrypoint-graphql-inspector.sh&lt;/code&gt;  in &lt;code&gt;./codeship&lt;/code&gt; folder

&lt;ul&gt;
&lt;li&gt;To add git remote for our repository

&lt;ul&gt;
&lt;li&gt;Note: it uses remote name as &lt;code&gt;api&lt;/code&gt; because &lt;code&gt;origin&lt;/code&gt; already taken and it doesn't work in codeship&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;To fetch the master branch which will use for &lt;code&gt;graphql inspector command&lt;/code&gt; in codeship
&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
git remote add api git@bitbucket.org:&amp;lt;owner_name&amp;gt;/&amp;lt;repo_name&amp;gt;.git
git fetch api master
&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Step3: Create graphQL inspector test service
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;graphql-inspector-test&lt;/code&gt; service will be placed in &lt;code&gt;./codeship-services.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build from &lt;code&gt;Dockerfile.graphqlInspector&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Include &lt;em&gt;entrypoint&lt;/em&gt; link with &lt;code&gt;entrypoint-graphql-inspector.sh&lt;/code&gt; to trigger the command line after finishing pulling and building the docker image from &lt;em&gt;dockerfile&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;volumes&lt;/code&gt;, it can be different depending on your code structure or same as created above in &lt;em&gt;dockerfile&lt;/em&gt;.

&lt;ul&gt;
&lt;li&gt;How to find which volume to mount&lt;/li&gt;
&lt;li&gt;Install jet if you have not  (for locally running commands for &lt;em&gt;codeship&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd &amp;lt;repo&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jet run SERVICE_NAME pwd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;jet run graphql-inspector-test pwd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;pwd&lt;/code&gt;, it will show the path of the volume where it should be mounted
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;graphql-inspector-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./codeship/Dockerfile.graphqlInspector&lt;/span&gt;
      &lt;span class="na"&gt;encrypted_args_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;path_to_encrypted_git_ssh_key&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;encrypted_env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;path_to_encrypted_git_ssh_key&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./codeship/entrypoint-graphql-inspector.sh"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;cached&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Step4: Create graphql-inspector-test step
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;GraphQL Schema Inspector Test&lt;/em&gt; test step under &lt;em&gt;Jest Tests&lt;/em&gt; in &lt;code&gt;./codeship-steps.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;graphql-inspector-test&lt;/code&gt; service&lt;/li&gt;
&lt;li&gt;To trigger command &lt;code&gt;graphql-inspector diff git:api/master:./src/generated/graphql/schema.graphql ./src/generated/graphql/schema.graphql --rule ./src/generated/graphql/custom-rule.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serial&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Continuous Integration&lt;/span&gt;
  &lt;span class="na"&gt;encrypted_dockercfg_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dockercfg.encrypted&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tests&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;parallel&lt;/span&gt;
    &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^(production)&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Jest Tests&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serial&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Jest Tests for E2E Tests&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;e2e-runner&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn test&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ESLint for E2E Tests&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;e2e-runner&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn run lint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Graph QL Test&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;graphql-test&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn run graphql-test&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Graph QL Schema Inspector Test&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;graphql-inspector-test&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;graphql-inspector diff&lt;/span&gt;
                 &lt;span class="s"&gt;git:api/master:./src/generated/graphql/schema.graphql&lt;/span&gt;             
                 &lt;span class="s"&gt;./src/generated/graphql/schema.graphql --rule&lt;/span&gt; 
                 &lt;span class="s"&gt;./src/generated/graphql/custom-rule.js&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Dev and Production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Step5: How to create custom rules for graphQL inspector test
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;For details, you can see &lt;a href="https://graphql-inspector.com/docs/essentials/diff#custom-rules" rel="noopener noreferrer"&gt;graphql inspector custom rules&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We use &lt;em&gt;custom-rules&lt;/em&gt; to skip certain attributes that we don’t want the test to run 

&lt;ul&gt;
&lt;li&gt;Example: the field may not be available to mobile apps yet and still under development, so there may be many changes to the field.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Create file name &lt;code&gt;custom-rules.js&lt;/code&gt; in the folder where you have your &lt;code&gt;schema.graphql&lt;/code&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// custom-rules.js&lt;/span&gt;
&lt;span class="cm"&gt;/**
 * @summary exceptionSet is an array of schema attributes that can be skipped during graphql schema diff test
 * for breaking changes. 
 * !important: by adding to exceptionSet, there is a risk of graphql schema test will skip these attributes
 * example: new Set(['Form.user]);
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exceptionSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @summary changes is an object array of all breaking changes [{error1},{error2}]
 * Each error object contains these key value pairs as follow 
 * critical: { level: 'BREAKING', reason: "some reason"}
 * type: 'FIELD_TYPE_CHANGED' or 'FIELD_REMOVED' or 'ENUM_VALUE_REMOVED', etc... 
 * message: "Field 'XYZ' was removed from interface 'ABC'"
 * path: graphql schema attribute, eg: 'Comment.description' 
 */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;included&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;excluded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exceptionSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;included&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;warnColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;x1b[33m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The following changes have been excluded from breaking changes test:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;excluded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// print something sensible - should show that the field was excluded&lt;/span&gt;
            &lt;span class="c1"&gt;// and what potential problem we're ignoring ;-)&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`!!! &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;warnColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;included&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  How to execute test locally
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Prerequisite
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; @graphql-inspector/cli graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Steps
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &amp;lt;repo_folder&amp;gt; 
graphql-inspector diff git:origin/master:./src/generated/graphql/schema.graphql ./src/generated/graphql/schema.graphql &lt;span class="nt"&gt;--rule&lt;/span&gt; ./src/generated/graphql/custom-rule.js

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

&lt;/div&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%2Fmt5t0d9giz9eldcygkfn.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%2Fmt5t0d9giz9eldcygkfn.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;We were successfully able to add &lt;em&gt;graphQL backward capabilities test&lt;/em&gt; that is easy to maintain, and is platform agnostic by using &lt;a href="https://graphql-inspector.com/" rel="noopener noreferrer"&gt;GraphQL Inspector Tool&lt;/a&gt;. We are able to gain a better understanding of how API should be handled for projects, where the same API is used across multiple platforms (i.e. web, desktop and mobile).&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>testing</category>
      <category>apitesting</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
