<?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: Alex Sanzhanov</title>
    <description>The latest articles on DEV Community by Alex Sanzhanov (@sanzhanov).</description>
    <link>https://dev.to/sanzhanov</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%2F931511%2F3856e520-6219-495f-a7be-a50c4fc1fa5e.jpg</url>
      <title>DEV Community: Alex Sanzhanov</title>
      <link>https://dev.to/sanzhanov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sanzhanov"/>
    <language>en</language>
    <item>
      <title>Cross Browser Testing with Cypress in CI/CD using Docker</title>
      <dc:creator>Alex Sanzhanov</dc:creator>
      <pubDate>Thu, 08 Jun 2023 14:18:10 +0000</pubDate>
      <link>https://dev.to/sanzhanov/cross-browser-testing-with-cypress-in-cicd-using-docker-48p3</link>
      <guid>https://dev.to/sanzhanov/cross-browser-testing-with-cypress-in-cicd-using-docker-48p3</guid>
      <description>&lt;p&gt;&lt;em&gt;Greetings to all Cypress enthusiasts!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore one interesting way of running Cypress tests across multiple browsers in CI/CD using Docker. The cool thing here is that with the help of Docker Compose we will set up the simultaneous launch of several Docker containers in which Cypress tests will be executed in parallel in different browsers. The entire workflow will run automatically on the GitHub Actions platform, and as a result, we will get artifacts from running tests in each of the browsers. Get ready to read, it’s going to be very exciting!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cross Browser Testing?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cross Browser compatibility&lt;/strong&gt; is one of the most important characteristics of a web application, which implies its equally correct display and functionality in various browsers and their versions.&lt;/p&gt;

&lt;p&gt;The modern variety of browsers is determined, among other things, by &lt;strong&gt;&lt;em&gt;differences in&lt;/em&gt;&lt;/strong&gt; the ways of &lt;strong&gt;&lt;em&gt;rendering&lt;/em&gt;&lt;/strong&gt; web application content, when different browser engines (&lt;em&gt;Blink, WebKit, Gecko, EdgeHTML&lt;/em&gt;) perceive and process HTML tags and CSS styles differently, which naturally affects the appearance and application behavior.&lt;/p&gt;

&lt;p&gt;In this regard, the importance of &lt;strong&gt;cross-browser testing&lt;/strong&gt; is obvious and extremely clear. Its purpose is to ensure that when a user opens a web application in various browsers and their versions, the correct display of its content is ensured, the integrity of its structure is preserved, there are no functional errors and inconsistencies in performance, layout collapse, overlapping elements on top of each other, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cypress Features in Cross Browser Testing
&lt;/h2&gt;

&lt;p&gt;If you are not yet familiar with &lt;strong&gt;&lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cypress&lt;/strong&gt; &lt;em&gt;is a JavaScript-based end-to-end testing tool designed for modern web test automation. Cypress allows conducting both full-fledged end-to-end testing with passing user scenarios on a real product, as well as integration testing of individual front-end components. Cypress has emerged as a popular end-to-end testing tool for web applications due to a bunch of its powerful features, user-friendly interface, fast test execution time, easy installation and debugging, etc.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To my mind, Cypress is a real game changer in end-to-end and component testing and it grows at a rapid pace. Among the many benefits of Cypress, I would like to emphasize the high quality of its &lt;a href="https://docs.cypress.io/guides/overview/why-cypress" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, as well as the master classes that the Cypress development team conducts and publishes in the public domain, and also a very friendly and responsive &lt;a href="https://discord.gg/cypress" rel="noopener noreferrer"&gt;community&lt;/a&gt;. Honestly, and as you may have noted from my previous articles I’m a big fan of that wonderful tool!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Cypress has the capability to run tests across multiple browsers&lt;/em&gt;&lt;/strong&gt;. According to official &lt;a href="https://docs.cypress.io/guides/guides/cross-browser-testing" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, Cypress has currently support for &lt;a href="https://docs.cypress.io/guides/guides/launching-browsers#Chrome-Browsers" rel="noopener noreferrer"&gt;Chrome-family browsers&lt;/a&gt; (including &lt;em&gt;Electron&lt;/em&gt; and Chromium-based &lt;em&gt;Microsoft Edge&lt;/em&gt;), &lt;a href="https://docs.cypress.io/guides/guides/launching-browsers#WebKit-Experimental" rel="noopener noreferrer"&gt;WebKit&lt;/a&gt; (Safari’s browser engine), and &lt;em&gt;Firefox&lt;/em&gt;. Excluding &lt;em&gt;&lt;a href="https://docs.cypress.io/guides/guides/launching-browsers#Electron-Browser" rel="noopener noreferrer"&gt;Electron&lt;/a&gt;&lt;/em&gt;, any browser you want to run Cypress tests in needs to be installed on your local system or CI environment.&lt;/p&gt;

&lt;p&gt;Obviously, it is often not necessary to run all available test suites in different browsers given the increase in test execution time and the associated cost of the required CI infrastructure. Therefore, &lt;strong&gt;&lt;em&gt;Cypress provides different continuous integration strategies&lt;/em&gt;&lt;/strong&gt; for deploying cross-browser testing in CI pipelines depending on the needs of a particular project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Using Cypress to Optimize Cross Browser Testing in CI
&lt;/h2&gt;

&lt;p&gt;In order to balance costs and available CI resources, as well as the optimal level of confidence in testing, &lt;strong&gt;&lt;em&gt;Cypress provides the following options for effectively organizing cross-browser testing&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Selecting a specific test suite for a given browser&lt;/em&gt;&lt;/strong&gt;. For example, sometimes it makes sense to run all available tests within &lt;em&gt;Chrome&lt;/em&gt;, but within &lt;em&gt;Firefox _execute only the happy or critical path related test files, or a directory of specific _“smoke”&lt;/em&gt; test files using &lt;code&gt;--spec&lt;/code&gt; flag. In some cases, the priority areas for implementing cross-browser testing may be critical application features or workflows, as well as the most likely user scenarios.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Flexible setting of the periodic frequency of launching individual browsers&lt;/em&gt;&lt;/strong&gt;. For example, running tests within &lt;em&gt;Chrome&lt;/em&gt; can be triggered by events in the repository, while within &lt;em&gt;Firefox&lt;/em&gt;, tests will run on a set schedule based on release periodic frequency. Modern CI pipelines allow you to set the required time and periodic frequency for running workflows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Parallel execution of test files for each group&lt;/em&gt;&lt;/strong&gt;, where the groups are based on the browsers being tested. For example, using &lt;strong&gt;&lt;a href="https://cloud.cypress.io/login" rel="noopener noreferrer"&gt;Cypress Cloud&lt;/a&gt;&lt;/strong&gt; allows you to run each browser at different levels of &lt;em&gt;parallelization&lt;/em&gt;, differentiating the amount of allocated CI resources between browsers depending on the importance of each browser in the testing strategy. For example, tests within &lt;em&gt;Chrome&lt;/em&gt; can be run conditionally in parallel on up to four machines, while within &lt;em&gt;Firefox&lt;/em&gt; on two, minimizing CI costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Possibility to configure the launching or exclusion of browsers for a specific test/test suite&lt;/em&gt;&lt;/strong&gt;. Sometimes it makes sense to run or ignore one or more tests in certain browsers to shorten the duration of a test running. To do this, Cypress allows to specify directly in the configuration of a test or test suite a specific browser to run or exclude, for example: &lt;code&gt;{ browser: 'firefox' }&lt;/code&gt; or &lt;code&gt;{ browser: '!chrome' }&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Determination of the software deployment environment&lt;/em&gt;&lt;/strong&gt;. In cases where the project shows consistently stable behavior in different browsers, it is advisable to set up cross-browser testing only before deploying changes to the &lt;em&gt;production&lt;/em&gt; environment.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A thoughtful combination of these Cypress advantages will help you build an optimal cross-browser testing strategy based on the needs of a particular project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Docker in Cross Browser Testing?
&lt;/h2&gt;

&lt;p&gt;There are various approaches to implementing cross-browser testing with Cypress, one of which is to set up the automatic running of Cypress tests in chosen CI platform using &lt;strong&gt;&lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Docker makes it much easier to set up and maintain a cross-browser test environment. By encapsulating the entire test stack, including different browsers in isolated Docker containers, it is possible to reproduce a stable and consistent environment for running tests in different browsers, regardless of the servers running the containers.&lt;/p&gt;

&lt;p&gt;Using &lt;strong&gt;&lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;&lt;/strong&gt; allows us to effortlessly scale our testing infrastructure by distributing our workload across multiple containers. So, it is possible to define several containers for different browsers and run them simultaneously using one command, or rather one configuration file. Parallel execution of tests in different browsers obviously significantly speeds up the overall testing process.&lt;/p&gt;

&lt;p&gt;Using the &lt;strong&gt;official Cypress Docker &lt;a href="https://github.com/cypress-io/cypress-docker-images" rel="noopener noreferrer"&gt;images&lt;/a&gt;&lt;/strong&gt; with pre-installed browsers as a base layer eliminates the need to install browsers on servers running Docker containers. You can read more about the benefits of using Docker in testing in my previous &lt;a href="https://medium.com/testing-with-cypress/running-cypress-tests-in-docker-containers-using-different-docker-images-2dee3450881e" rel="noopener noreferrer"&gt;article&lt;/a&gt; about running Cypress tests in Docker containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving from theory to practice…
&lt;/h2&gt;

&lt;p&gt;In this article, I use &lt;strong&gt;&lt;a href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;&lt;/strong&gt; as my continuous integration platform. It should be noted that &lt;em&gt;&lt;strong&gt;Cypress provides many useful &lt;a href="https://github.com/cypress-io/github-action" rel="noopener noreferrer"&gt;examples&lt;/a&gt; of configuration files for setting up GitHub Actions workflows&lt;/strong&gt;&lt;/em&gt;, based on which it is quite simple to organize various options for running tests in several browsers. For example, you can create a workflow for each browser separately and activate the created workflows depending on the specified trigger events in the repository. Obviously, this is very convenient.&lt;/p&gt;

&lt;p&gt;This article provides a simplified example of creating multiple containers to run tests within different browsers simultaneously using Docker Compose.&lt;/p&gt;

&lt;p&gt;The idea is pretty simple — let’s say we need to run a specific Cypress test suite within four browsers — &lt;em&gt;Google Chrome, Firefox, Microsoft Edge&lt;/em&gt;, and &lt;em&gt;Electron&lt;/em&gt;. Trigger events for automaticall ystarting a workflow in the repository should be a &lt;em&gt;push&lt;/em&gt; event to the &lt;em&gt;main&lt;/em&gt; branch, an &lt;em&gt;open&lt;/em&gt; or &lt;em&gt;reopened pull request&lt;/em&gt;, and a &lt;em&gt;scheduled&lt;/em&gt; start, for example, every Friday at 2 am.&lt;/p&gt;

&lt;p&gt;Also, during the execution of the workflow, it is necessary to obtain &lt;strong&gt;artifacts&lt;/strong&gt; with the results of running tests in each of the browsers — videos and screenshots in case any tests fail.&lt;/p&gt;

&lt;p&gt;One possible solution to this task is to create a custom Docker image based on one of the &lt;strong&gt;official Cypress Docker &lt;a href="https://github.com/cypress-io/cypress-docker-images" rel="noopener noreferrer"&gt;images&lt;/a&gt;&lt;/strong&gt;, build and simultaneously run four containers from the created image, in each of which our Cypress tests will be executed in a specific browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Briefly about the test project
&lt;/h2&gt;

&lt;p&gt;To demonstrate how to run Cypress tests across multiple browsers, I use the very simple &lt;em&gt;Cypress-Docker&lt;/em&gt; project to test my blog &lt;strong&gt;&lt;em&gt;&lt;a href="https://medium.com/testing-with-cypress" rel="noopener noreferrer"&gt;“Testing with Cypress”&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; on Medium. The project already has some dependencies installed — &lt;strong&gt;&lt;em&gt;&lt;a href="https://www.npmjs.com/package/cypress/v/12.13.0" rel="noopener noreferrer"&gt;Cypress 12.13.0&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;&lt;a href="https://www.npmjs.com/package/typescript/v/5.0.4" rel="noopener noreferrer"&gt;Typescript 5.0.4&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;, and also has &lt;em&gt;spec.cy.ts&lt;/em&gt; file with a set of three trivial tests for the blog’s homepage:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsjojx5f7mz52gf5sar7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsjojx5f7mz52gf5sar7b.png" alt=" " width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By running it locally within &lt;em&gt;Chrome&lt;/em&gt; browser, we make sure that all tests pass successfully:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Foao2338a0f34ttb4kllp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Foao2338a0f34ttb4kllp.png" alt=" " width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously, these tests are only demonstration examples and do not indicate the correct display and functionality of the website in different browsers.&lt;/p&gt;

&lt;p&gt;To run tests on the GitHub Actions platform, this project is hosted on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Docker image
&lt;/h2&gt;

&lt;p&gt;As a base layer for building the image, the official Docker image &lt;strong&gt;&lt;a href="https://hub.docker.com/r/cypress/browsers" rel="noopener noreferrer"&gt;Cypress/Browsers&lt;/a&gt;&lt;/strong&gt; was taken, which includes all operating system dependencies and some browsers.&lt;/p&gt;

&lt;p&gt;The most current version of the image at the moment — &lt;strong&gt;&lt;a href="https://hub.docker.com/layers/cypress/browsers/node-18.16.0-chrome-113.0.5672.92-1-ff-113.0-edge-113.0.1774.35-1/images/sha256-478b68c50a2544d0b8f00e3b79a09e084e16a8a1637096404c27c319d185b41d?context=explore" rel="noopener noreferrer"&gt;node-18.16.0-chrome-113.0.5672.92–1-ff-113.0-edge-113.0.1774.35–1&lt;/a&gt;&lt;/strong&gt; includes pre-installed &lt;em&gt;Node.js 18.16.0&lt;/em&gt;, as well as three browsers — &lt;em&gt;Google Chrome, Firefox&lt;/em&gt; and &lt;em&gt;Microsoft Edge&lt;/em&gt;. Given that the &lt;em&gt;Electron&lt;/em&gt; browser is pre-installed in Cypress, we will have all the necessary browsers at our disposal to carry out cross-browser test execution in accordance with the initial task.&lt;/p&gt;

&lt;p&gt;The final &lt;strong&gt;Dockerfile&lt;/strong&gt; for building the required image will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fplta3yq6xht4nngdz7ne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fplta3yq6xht4nngdz7ne.png" alt=" " width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Firstly, &lt;code&gt;FROM&lt;/code&gt; instruction defines a base image, all of whose dependencies and configurations will be included in the generated image.&lt;/p&gt;

&lt;p&gt;The next &lt;code&gt;WORKDIR&lt;/code&gt; step creates the &lt;code&gt;/e2e&lt;/code&gt; working directory, in which all subsequent commands will be executed.&lt;/p&gt;

&lt;p&gt;Next, in &lt;code&gt;COPY&lt;/code&gt;instruction the &lt;em&gt;package.json&lt;/em&gt; and &lt;em&gt;cypress.config.ts&lt;/em&gt; files, as well as the &lt;em&gt;cypress&lt;/em&gt; folder, including the &lt;em&gt;spec&lt;/em&gt; file, will be copied from the repository to the working directory inside the image.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;RUN&lt;/code&gt; instruction specifies two commands to run, in particular, &lt;code&gt;npm i&lt;/code&gt; — to install the necessary dependencies in the working directory of the image and &lt;code&gt;npx cypress info&lt;/code&gt; — to display information about Cypress, current browsers detected by Cypress, and so on.&lt;/p&gt;

&lt;p&gt;The last step in &lt;code&gt;ENTRYPOINT&lt;/code&gt; instruction the command in &lt;em&gt;exec&lt;/em&gt; form is defined to run Cypress in headless mode in containers generated from this image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up running containers with Docker Compose
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, after building the Docker image, &lt;em&gt;four&lt;/em&gt; containers will be created based on it. In each container, Cypress tests will be run in a specific browser. To simultaneously run containers with a single command, it is advisable to use a tool such as &lt;strong&gt;&lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;&lt;/strong&gt;. To describe the process of loading and configuring containers, the YAML configuration file should include the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5pndq7wm5cpyhw88eyu2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5pndq7wm5cpyhw88eyu2.png" alt=" " width="800" height="831"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see, in the configuration file we define &lt;em&gt;four&lt;/em&gt; &lt;code&gt;services&lt;/code&gt; (containers) — &lt;code&gt;e2e-chrome&lt;/code&gt;, &lt;code&gt;e2e-firefox&lt;/code&gt;, &lt;code&gt;e2e-edge&lt;/code&gt; and &lt;code&gt;e2e-electron&lt;/code&gt;, each of which uses the Docker image created based on the &lt;em&gt;Dockerfile&lt;/em&gt; located in the same directory (&lt;code&gt;build&lt;/code&gt; keys).&lt;/p&gt;

&lt;p&gt;Next, the values ​​of the &lt;code&gt;command&lt;/code&gt; keys set the commands to launch Cypress in browsers with the given names. It is worth noting here that in the &lt;code&gt;e2e-firefox&lt;/code&gt; service, the command was supplemented by a configuration change due to an open &lt;a href="https://github.com/cypress-io/cypress/issues/18415" rel="noopener noreferrer"&gt;issue&lt;/a&gt; with recording video when using the &lt;em&gt;Firefox&lt;/em&gt; browser in Cypress.&lt;/p&gt;

&lt;p&gt;The next step is to mount volumes (&lt;code&gt;volumes&lt;/code&gt; keys) for each service and set the appropriate mappings to gain access to artifacts outside of containers. In essence, this means that the videos and screenshots generated during the launch of Cypress and written into containers will actually be stored in the GitHub Actions virtual environment at the specified paths relative to the workspace of the launched workflow. This will allow, in case any tests fail, to extract artifacts from the &lt;code&gt;./artifacts&lt;/code&gt; directory during the execution of the GitHub Actions workflow, which is required according to the initial task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the Workflow in GitHub Actions
&lt;/h2&gt;

&lt;p&gt;To run the workflow, we’re going to use following workflow &lt;em&gt;e2e.yml&lt;/em&gt; file which is located in &lt;em&gt;.github/workflows&lt;/em&gt; directory of the project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fecpf6xjwnj14dtvzm6m5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fecpf6xjwnj14dtvzm6m5.png" alt=" " width="800" height="833"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Facvi4k71hjy2k7jw69jp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Facvi4k71hjy2k7jw69jp.png" alt=" " width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s analyze it in more detail. By using &lt;code&gt;on&lt;/code&gt; key we define trigger events that automatically activate the workflow according to the initial task conditions:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;push&lt;/code&gt; — when pushing changes to the &lt;em&gt;main&lt;/em&gt; branch&lt;br&gt;
&lt;code&gt;pull_request&lt;/code&gt; — when &lt;em&gt;opening&lt;/em&gt; or &lt;em&gt;reopening a pull request&lt;/em&gt;&lt;br&gt;
&lt;code&gt;schedule&lt;/code&gt; — run a &lt;em&gt;scheduled&lt;/em&gt; workflow every Friday at 2am&lt;/p&gt;

&lt;p&gt;Next, we define one job in the workflow, &lt;code&gt;cypress-run&lt;/code&gt;. The job will run in a virtual machine with the latest version of the &lt;em&gt;Ubuntu Linux&lt;/em&gt; operating system within the configured &lt;code&gt;timeout&lt;/code&gt; of &lt;em&gt;5 minutes&lt;/em&gt; to ensure that an accidental hang does not use up extra CI minutes.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;steps&lt;/code&gt; key combines all the necessary steps to complete the task. First, custom application &lt;code&gt;actions/checkout@v3&lt;/code&gt; is launched, which extracts the repository into the virtual machine, sequentially performing the necessary actions, including checking the &lt;em&gt;git&lt;/em&gt; version, creating the necessary folders, authorizing, etc.&lt;/p&gt;

&lt;p&gt;Next, we see the main step of the job — &lt;code&gt;Run docker-compose&lt;/code&gt;, which executes &lt;code&gt;docker-compose up&lt;/code&gt; command to build and run four Docker containers with the specified names (&lt;code&gt;e2e-chrome&lt;/code&gt;, &lt;code&gt;e2e-firefox&lt;/code&gt;, &lt;code&gt;e2e-edge&lt;/code&gt; and &lt;code&gt;e2e-electron&lt;/code&gt;) defined in Docker Compose configurations. In this case &lt;code&gt;-d&lt;/code&gt; flag means running containers in the background to allow the workflow to continue executing subsequent steps.&lt;/p&gt;

&lt;p&gt;The purpose of the next step is to provide visibility into the logs of running Docker containers in order to monitor them and control the process of starting services before moving on to subsequent workflow steps. The &lt;code&gt;docker-compose logs -f&lt;/code&gt; command displays logs of all running containers in real-time. At this step, it is possible to track the execution of Cypress tests step by step in each of the &lt;em&gt;four&lt;/em&gt; browsers.&lt;/p&gt;

&lt;p&gt;The next four steps are identical in nature and ensure that the workflow artifacts from each container are downloaded for access after the workflow completes. The custom application &lt;code&gt;actions/upload-artifact@v3&lt;/code&gt; takes the paths provided as input and uploads folders with generated by Cypress videos and screenshots if the test fails. This will make the artifacts from each container available in the workflow summary. In addition, the behavior of the action in case no artifact files are found is also configured here, as well as the storage period for artifacts (&lt;em&gt;5 days&lt;/em&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the Workflow
&lt;/h2&gt;

&lt;p&gt;To activate the workflow, let’s create a &lt;em&gt;trigger event&lt;/em&gt;. For this let’s make a small change to one of the existing tests, for example, add “.” at the end of the expected header text (highlighted) for the test to fail:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4auxkkqiwehw267lb8dn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4auxkkqiwehw267lb8dn.png" alt=" " width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let’s commit and push the change to the &lt;em&gt;main&lt;/em&gt; branch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F21s01o52sogtsxrl2bs2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F21s01o52sogtsxrl2bs2.png" alt=" " width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voila, the workflow is started! Let’s go to the project repository on GitHub and open the summary of the last workflow in the &lt;em&gt;Actions&lt;/em&gt; tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8zqtyhnr8kanq6hfi1wk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8zqtyhnr8kanq6hfi1wk.png" alt=" " width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the log of the completed &lt;code&gt;cypress-run&lt;/code&gt; job, we make sure that all steps were completed successfully:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fuywwcn04360wxo9rhln9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fuywwcn04360wxo9rhln9.png" alt=" " width="800" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In particular, at the &lt;code&gt;Run docker-compose&lt;/code&gt; step, a Docker image was built based on the previously described &lt;em&gt;Dockerfile&lt;/em&gt;, from which four containers were generated. During the build of the image, &lt;code&gt;npx cypress info&lt;/code&gt; command was executed and information about detected browsers was displayed in the log, as well as other characteristics of the test environment — the operating system, versions of Node.js, Cypress, etc.:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flk8usizsreljgfva8c7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flk8usizsreljgfva8c7k.png" alt=" " width="800" height="1026"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the next step, processes were simultaneously launched in the created containers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ffa7fvuy5p7b9s0rt7ddh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffa7fvuy5p7b9s0rt7ddh.png" alt=" " width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;to be more precise, parallel execution of Cypress tests was launched in four browsers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvhp0fv71974vg3tpwr1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvhp0fv71974vg3tpwr1n.png" alt=" " width="800" height="736"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see logs from each container about the progress of the test execution:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbxessj8icvov5ofm004w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbxessj8icvov5ofm004w.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a result, the first test failed as expected, while the next two passed successfully in each of the &lt;em&gt;four&lt;/em&gt; browsers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff447gxdjsgq8kpyltfgz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff447gxdjsgq8kpyltfgz.png" alt=" " width="800" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Feg56smkkn3ybogo4zpmb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Feg56smkkn3ybogo4zpmb.png" alt=" " width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F87ad8h2cheynmiomfdy2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F87ad8h2cheynmiomfdy2.png" alt=" " width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftbwg1fwxux96mp4yhxhp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftbwg1fwxux96mp4yhxhp.png" alt=" " width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate Workflow Artifacts
&lt;/h2&gt;

&lt;p&gt;As you may have noticed earlier, the workflow summary contains information about the downloaded artifacts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdmbtfm7c0c9c8uzylky8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdmbtfm7c0c9c8uzylky8.png" alt=" " width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After downloading the artifacts from the workflow summary in ZIP format, we verify that we have videos and screenshots of the failing test in each browser (except the video in &lt;em&gt;Firefox&lt;/em&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fmwf1lxpvfqpcyxj5pj03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fmwf1lxpvfqpcyxj5pj03.png" alt=" " width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s check that in case of successful completion of all tests, the workflow artifacts will not be loaded. To do this, let’s fix the first test, make a commit, and push the change to the &lt;em&gt;main&lt;/em&gt; branch again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8vm3nylk5e9di4de3oka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8vm3nylk5e9di4de3oka.png" alt=" " width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the workflow is completed, we make sure that artifacts were not loaded from any container due to their absence:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fol3mocdcz1flhha96soz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fol3mocdcz1flhha96soz.png" alt=" " width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;In conclusion, it should be noted that the optimization of cross-browser testing with Cypress based on the approach described in this article has a bunch of obvious advantages.&lt;/p&gt;

&lt;p&gt;In particular, the &lt;strong&gt;&lt;em&gt;simultaneous launch and parallel execution of tests in several browsers&lt;/em&gt;&lt;/strong&gt; are guaranteed. Containerization ensures that the &lt;strong&gt;&lt;em&gt;test environment is consistent and reproducible&lt;/em&gt;&lt;/strong&gt; when running Cypress tests on different browsers.&lt;/p&gt;

&lt;p&gt;Moreover, it is &lt;strong&gt;&lt;em&gt;easier to set up and maintain a test environment&lt;/em&gt;&lt;/strong&gt; based on a single configuration file. The &lt;strong&gt;&lt;em&gt;test infrastructure scales easily&lt;/em&gt;&lt;/strong&gt; depending on the desired level of parallelism for each run or the number of browsers being tested, etc.&lt;/p&gt;

&lt;p&gt;In general, all this allows you to &lt;strong&gt;&lt;em&gt;increase the efficiency of using available CI resources, reduce the total time for executing tests, expand test coverage in several browsers, providing the optimal level of reliability, taking into account the specifics of a particular project&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s about it. If you found this useful, share it with a friend or community. Maybe there’s someone who will benefit from it as well. To continue your journey with me and get more information about testing with the awesome &lt;strong&gt;&lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt;&lt;/strong&gt; tool, you might be interested in subscribing to my blog &lt;strong&gt;&lt;a href="https://medium.com/testing-with-cypress" rel="noopener noreferrer"&gt;“Testing with Cypress”&lt;/a&gt;&lt;/strong&gt; and get notified when there’s a new useful article.&lt;/p&gt;

&lt;p&gt;The source code of all examples presented in this article can be found in the &lt;a href="https://github.com/Sanzhanov/Blog-Testing-with-Cypress" rel="noopener noreferrer"&gt;repository&lt;/a&gt; of the blog on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for your attention! Happy testing!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>docker</category>
      <category>cicd</category>
      <category>testing</category>
    </item>
    <item>
      <title>Running Cypress tests in Docker containers using different Docker images</title>
      <dc:creator>Alex Sanzhanov</dc:creator>
      <pubDate>Wed, 07 Jun 2023 01:55:41 +0000</pubDate>
      <link>https://dev.to/sanzhanov/running-cypress-tests-in-docker-containers-using-different-docker-images-4lp0</link>
      <guid>https://dev.to/sanzhanov/running-cypress-tests-in-docker-containers-using-different-docker-images-4lp0</guid>
      <description>&lt;p&gt;&lt;em&gt;Greetings to all Cypress enthusiasts!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article describes the benefits of using Docker in Cypress testing, discusses in detail the current official Cypress images, and outlines the mechanism for building custom Docker images to run Cypress tests in Docker containers deployed based on these images.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firstly, a few words about Cypress
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cypress&lt;/strong&gt;&lt;/a&gt; is a JavaScript-based end-to-end testing tool designed for modern web test automation. Cypress has become a popular end-to-end web application testing tool due to its powerful features, user-friendly interface, and fast test execution.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are many pros and cons regarding the strength and weaknesses of the most popular web application testing frameworks today (Cypress, Playwright, Selenium, Puppeteer, etc.), but my own preference is Cypress, which I have been using on a daily basis for several years now.&lt;/p&gt;

&lt;p&gt;To my mind, Cypress is a real game changer in end-to-end and component testing and it grows at a rapid pace. Among the many benefits of Cypress, I would like to emphasize the high quality of its &lt;strong&gt;&lt;em&gt;&lt;a href="https://docs.cypress.io/guides/overview/why-cypress" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;, as well as the master classes that the Cypress development team conducts and publishes in the public domain, and also a friendly and responsive &lt;strong&gt;&lt;em&gt;&lt;a href="https://discord.gg/cypress" rel="noopener noreferrer"&gt;community&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;. Honestly, and as you may have noted from my previous articles I’m a big fan of that wonderful tool!&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of using Docker in Cypress testing
&lt;/h2&gt;

&lt;p&gt;In modern automated testing, setting up and maintaining a test environment can often become a tedious task, especially when dealing with multiple dependencies and their configurations, different operating systems, libraries, tools, and their versions. Often in practice, we can encounter dependency conflicts, inconsistencies in environments, limitations in scalability and reproduction of errors that occur, etc., which ultimately leads to unpredictable and unreliable test results. Using Docker goes a long way in preventing most of these problems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/strong&gt; is an open-source service for packaging, deploying, and running applications in an isolated containerized environment.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today it is difficult to imagine the development of any web application without the participation of Docker. The usage of Docker containerization technologies brings a bunch of serious advantages for testing web applications, such as ensuring a &lt;strong&gt;&lt;em&gt;consistent and isolated environment&lt;/em&gt;&lt;/strong&gt; for running tests in each new run; &lt;strong&gt;&lt;em&gt;independence from the parameters and settings of the system&lt;/em&gt;&lt;/strong&gt; on which the tests are run; &lt;strong&gt;&lt;em&gt;reliability, reproducibility,&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;predictability&lt;/em&gt;&lt;/strong&gt; of test results when they are run in different environments, etc.&lt;/p&gt;

&lt;p&gt;In particular, &lt;em&gt;&lt;strong&gt;using Docker in Cypress testing can be very useful for several reasons:&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;It ensures that Cypress tests run in an &lt;strong&gt;&lt;em&gt;isolated test environment&lt;/em&gt;&lt;/strong&gt; because the container in which the tests are run is self-contained from other containers and from the external environment. In this case, the tests are essentially independent of what is outside the container, which ensures the &lt;strong&gt;&lt;em&gt;reliability and continuity of the tests in each new run&lt;/em&gt;&lt;/strong&gt;. For example, in the case of deploying containers with tests locally, this means that the absence of Node.js, Cypress, any browser, or certain versions of them on the host computer will not become an obstacle to running Cypress tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allows applications and Cypress tests to be run locally on different host machines, as well as deployed to CI/CD pipelines and cloud infrastructure by providing a stable and consistent test environment. When moving a Docker image from one server to another, containers with the application itself and tests will work the same regardless of the operating system used, the presence of Node.js, Cypress, browsers, etc. This ensures &lt;strong&gt;&lt;em&gt;reproducibility and predictability when running Cypress tests across different systems&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Frees us from having to install any dependencies outside of containers&lt;/em&gt;&lt;/strong&gt; — on the servers that run the containers. Docker allows us to quickly deploy the containerized environment to run Cypress tests, so we do not need to install operating system dependencies, necessary browsers, and test frameworks every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Significantly simplifies the integration of Cypress tests into the web application development process&lt;/em&gt;&lt;/strong&gt;, embedding and deploying tests in various CI/CD pipelines, cloud services, etc., eliminating the need to troubleshoot compatibility issues during installation, reconfiguration, etc. In addition, Docker allows us to &lt;strong&gt;&lt;em&gt;safely move tests between software deployment environments&lt;/em&gt;&lt;/strong&gt;, for example, from a tester’s laptop to a stage or production environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Speeds up the testing process&lt;/em&gt;&lt;/strong&gt; by reducing the total time for test runs. This is achieved through scaling, i.e. increasing the number of containers, running Cypress tests in different containers in parallel, parallel cross-browser testing capabilities using Docker Compose, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Briefly about official Cypress images
&lt;/h2&gt;

&lt;p&gt;As of today, there are 4 &lt;strong&gt;official Cypress Docker images&lt;/strong&gt; hosted in the public image repository — &lt;strong&gt;&lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt;&lt;/strong&gt;, as well as in the corresponding &lt;a href="https://github.com/cypress-io/cypress-docker-images" rel="noopener noreferrer"&gt;&lt;strong&gt;“cypress-docker-images”&lt;/strong&gt;&lt;/a&gt; repository of Cypress.io on GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://hub.docker.com/r/cypress/factory/" rel="noopener noreferrer"&gt;cypress/factory&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://hub.docker.com/r/cypress/base/" rel="noopener noreferrer"&gt;cypress/base&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://hub.docker.com/r/cypress/browsers/" rel="noopener noreferrer"&gt;cypress/browsers&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://hub.docker.com/r/cypress/included/" rel="noopener noreferrer"&gt;cypress/included&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Docker repositories of these images include their various versions and tags. The number of downloads of each image (except &lt;em&gt;cypress/factory&lt;/em&gt;) at the time of writing was more than &lt;em&gt;50 million&lt;/em&gt;, which is quite impressive.&lt;/p&gt;

&lt;p&gt;Now let’s see how each of these images can be interacted with in order to run Cypress tests in containers created from them. It should be noted that often these containers are not generated directly from the four specified images, but based on &lt;strong&gt;&lt;em&gt;custom images&lt;/em&gt;&lt;/strong&gt;, which in turn are built &lt;strong&gt;&lt;em&gt;from official images&lt;/em&gt;&lt;/strong&gt;. This is possible because Docker allows us to reuse official images as a &lt;strong&gt;&lt;em&gt;base layer&lt;/em&gt;&lt;/strong&gt; and build custom images based on them by adding the necessary additional layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short dive into a test project
&lt;/h2&gt;

&lt;p&gt;To demonstrate running tests in Docker containers, I’ll use “Cypress-Docker” — the simplest project with Cypress tests that I have to test my blog &lt;em&gt;&lt;a href="https://medium.com/testing-with-cypress" rel="noopener noreferrer"&gt;“Testing with Cypress”&lt;/a&gt;&lt;/em&gt; on Medium.&lt;/p&gt;

&lt;p&gt;The project already has some dependencies installed — &lt;em&gt;Cypress 12.12.0&lt;/em&gt; and &lt;em&gt;Typescript 5.0.4&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fykuskgev9vwqlg429vwm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fykuskgev9vwqlg429vwm.png" alt=" " width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and there is also one &lt;em&gt;spec.cy.ts&lt;/em&gt; file with a test suite of three trivial tests for the blog’s homepage:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fd4g1mekofhc8leq55cne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd4g1mekofhc8leq55cne.png" alt=" " width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By running these tests in &lt;em&gt;headless&lt;/em&gt; mode, we are convinced of the successful execution of the tests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fp6zrc79srmnvs08yluuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fp6zrc79srmnvs08yluuk.png" alt=" " width="800" height="651"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we should pay attention to the environment settings when running tests locally on the computer — &lt;em&gt;Node.js 19.8.1, Cypress 12.12.0&lt;/em&gt;, and browser &lt;em&gt;Electron 106&lt;/em&gt;. Next, we will make sure that when running tests inside Docker containers, these settings will change depending on the content of the Docker images used. I also note that &lt;strong&gt;&lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/strong&gt; is already installed locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running tests in Docker containers
&lt;/h2&gt;

&lt;p&gt;Now let’s look at how we can run our Cypress tests inside Docker containers using each of the official Cypress Docker images.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://hub.docker.com/r/cypress/included/" rel="noopener noreferrer"&gt;CYPRESS/INCLUDED&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;I would like to start with this image because, unlike the others, it includes &lt;strong&gt;&lt;em&gt;all the dependencies of the operating system, pre-installed Cypress, and some browsers&lt;/em&gt;&lt;/strong&gt;. This makes &lt;em&gt;cypress/included&lt;/em&gt; available to use out of the box without the need to add additional layers. The image allows us to create containers and run Cypress tests in them using just single command.&lt;/p&gt;

&lt;p&gt;To download an image from the Docker Hub, just run the following command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;pull&lt;/span&gt; &lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;included&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;12.12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s important to note here that it’s a good idea to always add a specific image &lt;em&gt;tag&lt;/em&gt; rather than relying on the default (&lt;em&gt;latest&lt;/em&gt;) tag name because the latest version changes as new versions of the image are released. In the case of &lt;em&gt;cypress/included&lt;/em&gt;, the &lt;strong&gt;&lt;em&gt;tag name corresponds to the version of Cypress&lt;/em&gt;&lt;/strong&gt; preinstalled inside it, for example, in my case, it is &lt;em&gt;12.12.0 (stable)&lt;/em&gt;. Thus, if we do not specify the image version, then there is a chance that the tests will be run in different versions of Cypress since the containers for running the tests will be generated from images with different tags.&lt;/p&gt;

&lt;p&gt;The presence of the specified image after it has finished downloading can be checked with a simple command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downloaded &lt;em&gt;cypress/included&lt;/em&gt; image is shown first in the list of downloaded images:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F95roz0webtlb9851wehi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F95roz0webtlb9851wehi.png" alt=" " width="800" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To find out what this image contains, just execute the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;entrypoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;cypress&lt;/span&gt; &lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;included&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;12.12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, we get information about the version of Node.js preinstalled in the image, Cypress, three browsers, as well as other characteristics:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2c2vakxqto2h6yfdm8ob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2c2vakxqto2h6yfdm8ob.png" alt=" " width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxxgrepbxa3na6fd862ak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxxgrepbxa3na6fd862ak.png" alt=" " width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, to run our Cypress tests in a container, we need to initiate the creation of a new container from the downloaded image with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;$PWD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt; &lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;included&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;12.12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this command, we have created a Docker container, which essentially means a &lt;strong&gt;&lt;em&gt;writable layer on top of existing image layers&lt;/em&gt;&lt;/strong&gt;. Let’s take a look at the structure of used command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run&lt;/code&gt; means to create and run a container based on the specified image&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-it&lt;/code&gt; — interactive terminal&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-v $PWD:/e2e&lt;/code&gt; — map the contents of the directory inside the container to the contents of the current project directory, where &lt;code&gt;$PWD&lt;/code&gt; is the absolute path to the current directory (&lt;em&gt;root&lt;/em&gt; of the project), &lt;code&gt;/e2e&lt;/code&gt; is the path to the directory inside the container&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-w /e2e&lt;/code&gt; — set the working directory inside the container, where &lt;code&gt;/e2e&lt;/code&gt; is the &lt;em&gt;path&lt;/em&gt; to the specified directory&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cypress/included:12.12.0&lt;/code&gt; — an indication of the image on the basis of which the container will be created.&lt;/p&gt;

&lt;p&gt;This command will create a Docker container in which Cypress will run tests on the built-in Electron browser. It is important to note that the &lt;em&gt;cypress/included&lt;/em&gt; image includes &lt;code&gt;ENTRYPOINT ["cypress" "run"]&lt;/code&gt; instruction, which automatically starts Cypress inside the container created from this image.&lt;/p&gt;

&lt;p&gt;If desired, it is possible to supplement the command with &lt;code&gt;--name&lt;/code&gt; flag to specify a specific name for the created container, as well as &lt;code&gt;--rm&lt;/code&gt; flag to automatically delete the container after it is stopped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;$PWD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt;  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;tests&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;rm&lt;/span&gt; &lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;included&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;12.12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the same time, any additional arguments can be passed after the image name &lt;strong&gt;&lt;em&gt;to control the behavior of Cypress inside Docker container&lt;/em&gt;&lt;/strong&gt;, similar to how we run Cypress in headless mode without using Docker. So, we can specify a specific browser or spec file to run, configure recording parameters, run tests in parallel, etc. For example, to run tests in any of the browsers preinstalled in the image (&lt;em&gt;Chrome, Edge, Firefox&lt;/em&gt;), it is enough to add to the command &lt;code&gt;-b&lt;/code&gt; flag specifying the name of the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;$PWD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt; &lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;included&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;12.12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or if we assume that initially, the project has several &lt;em&gt;spec&lt;/em&gt; files, then to run a specific file/files, we should pass to the command &lt;code&gt;-s&lt;/code&gt; flag specifying the absolute path to the required file/files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;$PWD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt; &lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;included&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;12.12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we know all those commands, let’s create a container and run our tests inside it using Chrome browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fskjgur2w5l5go3xov9og.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fskjgur2w5l5go3xov9og.png" alt=" " width="800" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And voila — we can now see that the specified test environment settings (&lt;em&gt;Node.js 18.16.0, Cypress 12.12.0, Chrome 113&lt;/em&gt;) match the characteristics of the &lt;em&gt;cypress/included&lt;/em&gt; image content while being different from the settings when we initially ran the tests locally. Thus, it took &lt;strong&gt;&lt;em&gt;only single command to run the tests inside created Docker container&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running tests multiple times in the same container
&lt;/h3&gt;

&lt;p&gt;It is important to note that each time we run Cypress tests based on the &lt;code&gt;docker run …&lt;/code&gt; command, a new container will be created and run. To avoid this and be able &lt;strong&gt;&lt;em&gt;to run tests multiple times inside the same container&lt;/em&gt;&lt;/strong&gt;, we need to connect to a process inside the container to control the running of tests from the container. To do this, it is enough to supplement the used command with &lt;code&gt;--entrypoint&lt;/code&gt; flag with &lt;code&gt;/bin/bash&lt;/code&gt; argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;$PWD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;e2e&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;entrypoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sr"&gt;/bin/&lt;/span&gt;&lt;span class="nx"&gt;bash&lt;/span&gt; &lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;included&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;12.12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, we have the ability to control the re-running of tests from the container using &lt;code&gt;cypress run&lt;/code&gt; command, or to execute any other commands, for example, view files and folders inside the container using the standard &lt;code&gt;ls -la&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;In addition to the above way of running tests using &lt;em&gt;cypress/included&lt;/em&gt; image, we can build our own custom image based on it using &lt;em&gt;Dockerfile&lt;/em&gt;. However, in the case of &lt;em&gt;cypress/included&lt;/em&gt; it often doesn’t make much practical sense (which can’t be said about other images), since this image already includes all the necessary dependencies of the test environment. Moreover, as mentioned earlier, &lt;em&gt;cypress/included&lt;/em&gt; already contains &lt;code&gt;ENTRYPOINT ["cypress" "run"]&lt;/code&gt; instruction, so there is no need to explicitly define the command to run Cypress tests in &lt;em&gt;Dockerfile&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Next, let’s look at another interesting official Cypress image — &lt;em&gt;cypress/browsers&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://hub.docker.com/r/cypress/browsers" rel="noopener noreferrer"&gt;CYPRESS/BROWSERS&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This image includes &lt;strong&gt;&lt;em&gt;all operating system dependencies and some browsers&lt;/em&gt;&lt;/strong&gt;. However, it does not contain preinstalled Cypress, which is similar to &lt;em&gt;cypress/factory&lt;/em&gt;, the main difference is that in the latter we can &lt;strong&gt;&lt;em&gt;optionally configure certain versions of browsers and operating system dependencies, as well as add Cypress of the desired version&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each &lt;em&gt;cypress/browsers&lt;/em&gt; image is named according to the following &lt;strong&gt;&lt;em&gt;general pattern&lt;/em&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;code&gt;cypress/browsers:node-&amp;lt;full Node version&amp;gt;-chrome&amp;lt;Chrome version&amp;gt;-ff-&amp;lt;Firefox version&amp;gt;-edge-&amp;lt;Edge version&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is a complete template, while in some versions of the image, there may be only one or two browsers from Chrome, Firefox, and Edge in various combinations.&lt;/p&gt;

&lt;p&gt;Let’s create a custom image based on &lt;em&gt;cypress/browsers&lt;/em&gt;. In doing so, I will deliberately use the current penultimate version of the base image to demonstrate the difference in the version of the browser used.&lt;/p&gt;

&lt;p&gt;As you know, Docker can build images automatically by reading the instructions given in &lt;em&gt;Dockerfile&lt;/em&gt;. In this case, &lt;em&gt;Dockerfile&lt;/em&gt; for building the custom image will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqeemmv97qrdrczx9y7bl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqeemmv97qrdrczx9y7bl.png" alt=" " width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;FROM&lt;/code&gt; instruction defines a base image, all of whose dependencies and configurations will be included in the generated image. In particular, these are &lt;em&gt;Node.js 18.16.0&lt;/em&gt;, as well as pre-installed browsers — &lt;em&gt;Chrome 112.0.5615.121–1, Firefox 112.0.1&lt;/em&gt;, and &lt;em&gt;Edge 112.0.1722.48–1&lt;/em&gt;. The base image will be picked up from Docker Hub.&lt;/p&gt;

&lt;p&gt;At the next step, &lt;code&gt;WORKDIR&lt;/code&gt;, a working directory &lt;code&gt;/tests&lt;/code&gt; is created, in which all subsequent commands will be executed. If such a directory does not exist, this instruction will create it.&lt;/p&gt;

&lt;p&gt;Next, the files and folders specified in &lt;code&gt;COPY&lt;/code&gt; command will be copied from the current project directory (in which &lt;em&gt;Dockerfile&lt;/em&gt; is located) to the working directory inside the resulting image. It’s worth noting that I’m not copying the entire contents of the project, as it’s obviously not necessary. In particular, there is no point in copying the &lt;em&gt;node-modules&lt;/em&gt; directory with the dependencies already installed, as they depend on the test environment settings. Also, to avoid copying, we can create a &lt;em&gt;.dockerignore&lt;/em&gt; file by explicitly specifying &lt;em&gt;node-modules&lt;/em&gt; inside it.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;RUN&lt;/code&gt; instruction executes commands during the image build process. In our case, it’s a command that installs all the necessary dependencies in the working directory, and in particular Cypress will be installed at this stage.&lt;/p&gt;

&lt;p&gt;At the last step, the command to run Cypress is specified in &lt;code&gt;ENTRYPOINT&lt;/code&gt;, which will be executed inside the containers generated from this image. It is noteworthy that the instruction is written in exec form.&lt;/p&gt;

&lt;p&gt;Let’s initiate an image build based on the instructions above by running the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;docker build&lt;/code&gt; is the custom image build command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-t my-cypress-browsers:1.0.0&lt;/code&gt; — an addition of arbitrary name and tag for the created image&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.&lt;/code&gt; means that we are referring to the Dockerfile location as the Docker build context.&lt;/p&gt;

&lt;p&gt;As we can see, the successful building of the resulting image ended with &lt;code&gt;FINISHED&lt;/code&gt; message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fijno1eluc0haqvaac6te.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fijno1eluc0haqvaac6te.png" alt=" " width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s check the existence of the created image. We can see that the built image is listed first in the list of available images:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftwigmot34up98ddqif24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftwigmot34up98ddqif24.png" alt=" " width="773" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, from the created image, we will generate a container and invoke Cypress inside it to execute tests in the Chrome browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, our tests are automatically run in an environment with the Node.js and Chrome versions specified in the base image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fzrrg3z4hp7elsxtuqid2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzrrg3z4hp7elsxtuqid2.png" alt=" " width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let’s go over one more image — &lt;em&gt;cypress/base&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://hub.docker.com/r/cypress/base" rel="noopener noreferrer"&gt;CYPRESS/BASE&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This image contains &lt;strong&gt;&lt;em&gt;all the operating system dependencies necessary to run Cypress but does not contain Cypress itself and pre-installed browsers&lt;/em&gt;&lt;/strong&gt;. Due to this, &lt;em&gt;cypress/base&lt;/em&gt; is significantly (almost 2 times) smaller than the &lt;em&gt;cypress/included&lt;/em&gt; and &lt;em&gt;cypress/browsers&lt;/em&gt; images, which looks very attractive if there is no need to use some of the browsers. As stated in the image description, a &lt;strong&gt;&lt;em&gt;specific image tag corresponds to the version of Node.js or the operating system&lt;/em&gt;&lt;/strong&gt; it is built on.&lt;/p&gt;

&lt;p&gt;To build our own image based on &lt;em&gt;cypress/base&lt;/em&gt; let’s create a new &lt;em&gt;Dockerfile&lt;/em&gt; with the following instructions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ffc3f9x2lhmq1k9aymsku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffc3f9x2lhmq1k9aymsku.png" alt=" " width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here &lt;code&gt;FROM&lt;/code&gt; instruction defines &lt;em&gt;cypress/base:18.15.0&lt;/em&gt; as the base layer, where the &lt;em&gt;18.15.0&lt;/em&gt; tag name corresponds to the version preinstalled in the Node.js image. In this case, as in the previous case, I specify the penultimate version of the base image to demonstrate the difference in the versions of Node.js used.&lt;/p&gt;

&lt;p&gt;At the next step, as before, a working directory is created to run all subsequent commands inside it. The &lt;code&gt;COPY&lt;/code&gt; instruction specifies the files and folders to be copied to the working directory within the resulting image. The &lt;code&gt;RUN&lt;/code&gt; command installs the necessary dependencies to the working directory. At the final stage, a command is specified to run tests inside containers generated from this image.&lt;/p&gt;

&lt;p&gt;Let’s set the name and tag for the resulting image — &lt;code&gt;my-cypress-base:1.0.0&lt;/code&gt; and build it with the already-known command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, successful build of the image ended with &lt;code&gt;FINISHED&lt;/code&gt; message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftvzop9ddn3podxswom2r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftvzop9ddn3podxswom2r.png" alt=" " width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As before, let’s check if the built image exists. We can see that the &lt;em&gt;my-cypress-base&lt;/em&gt; image is listed first among the available images:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fg5xw5ksdjwrwmon5apeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fg5xw5ksdjwrwmon5apeu.png" alt=" " width="791" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned earlier, due to the lack of browsers, the created image has a significantly (more than 2 times) smaller size than our previous images.&lt;/p&gt;

&lt;p&gt;Next, we will generate a container based on the created image and run Cypress tests inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have verified that the tests run in the pre-installed &lt;em&gt;Electron 106&lt;/em&gt; browser. And as expected the specified version of &lt;em&gt;Node.js — 18.15.0&lt;/em&gt; corresponds to the tag name of the base image and differs from the previously used version (&lt;em&gt;18.16.0&lt;/em&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0060r03pfg8t4uv6msbg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0060r03pfg8t4uv6msbg.png" alt=" " width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is important to note that if we try to run the tests in another browser, for example, by adding &lt;code&gt;-b chrome&lt;/code&gt; flag to the initial command, then despite the fact that the corresponding container will be generated, the tests will obviously not run due to the absence of the Chrome browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvypp90oz9ycyegleviwl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvypp90oz9ycyegleviwl.png" alt=" " width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As stated in the text of the error, the only available browser is Electron, which is reasonable since the base image &lt;em&gt;cypress/base&lt;/em&gt; &lt;strong&gt;&lt;em&gt;does not contain any pre-installed browsers&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Well, now let’s dive into the last of the official images — &lt;em&gt;cypress/factory&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://hub.docker.com/r/cypress/factory/" rel="noopener noreferrer"&gt;CYPRESS/FACTORY&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This image appeared relatively recently, the first version of the image with the &lt;em&gt;1.0.0&lt;/em&gt; tag name was uploaded to Docker Hub about 4 months ago. &lt;em&gt;Cypress/factory&lt;/em&gt; allows building custom images with specific versions of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;node&lt;/li&gt;
&lt;li&gt;yarn&lt;/li&gt;
&lt;li&gt;chrome&lt;/li&gt;
&lt;li&gt;firefox&lt;/li&gt;
&lt;li&gt;edge&lt;/li&gt;
&lt;li&gt;cypress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As stated in the instructions for this image, using &lt;strong&gt;&lt;em&gt;cypress/factory provides the following benefits&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Freedom to choose which versions to test against.&lt;/li&gt;
&lt;li&gt;No need to wait on an official release to test the latest version of a browser.&lt;/li&gt;
&lt;li&gt;Smaller docker sizes especially when not including unused browsers.&lt;/li&gt;
&lt;li&gt;Easily test multiple browser versions.&lt;/li&gt;
&lt;li&gt;Reduced maintenance and pull requests for the cypress-docker repo.&lt;/li&gt;
&lt;li&gt;Ability to offer more variations of docker containers at low cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Indeed, one of the main advantages of &lt;em&gt;cypress/factory&lt;/em&gt; is the ability to customize the contents of the image, which allows, if necessary, to &lt;strong&gt;&lt;em&gt;significantly reduce its size&lt;/em&gt;&lt;/strong&gt;. Required versions of Node.js, Cypress, and browsers can be passed as &lt;strong&gt;&lt;em&gt;arguments&lt;/em&gt;&lt;/strong&gt; when building the image.&lt;/p&gt;

&lt;p&gt;Suppose we plan to build an image, including the most up-to-date versions of Node.js, as well as Chrome, Edge, and Firefox browsers. At the moment it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 20.1.0&lt;/li&gt;
&lt;li&gt;Chrome 113.0.5672.92–1&lt;/li&gt;
&lt;li&gt;Edge 113.0.1774.42–1&lt;/li&gt;
&lt;li&gt;Firefox 113.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, we should pass the specified parameters as &lt;em&gt;arguments&lt;/em&gt; to build the custom image. There are &lt;strong&gt;&lt;em&gt;different ways&lt;/em&gt;&lt;/strong&gt; to declare them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;as instructions in &lt;em&gt;Dockerfile&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;inside the image build command with &lt;code&gt;--build-arg&lt;/code&gt; flag&lt;/li&gt;
&lt;li&gt;using &lt;em&gt;Docker Compose&lt;/em&gt; and specifying them in the &lt;em&gt;docker-compose.yml&lt;/em&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the way, if we don’t pass any arguments at all when creating the image, then by default the generated image will contain only Node.js.&lt;/p&gt;

&lt;p&gt;Let’s first look at the option of specifying arguments in &lt;em&gt;Dockerfile&lt;/em&gt;. To do this, let’s create &lt;em&gt;Dockerfile&lt;/em&gt; at the root of our project with the following content:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fmxj7efgem2fxn80ir4ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fmxj7efgem2fxn80ir4ho.png" alt=" " width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see, before &lt;code&gt;FROM&lt;/code&gt; instruction, the &lt;code&gt;ARG&lt;/code&gt; arguments for building the image are declared. &lt;code&gt;ARG&lt;/code&gt; is used to set build-time variables with key and value. In our case, it defines the required versions of Node.js and browsers. It’s important to note that the exact version must be used, no wildcards or shorthands are supported.&lt;/p&gt;

&lt;p&gt;Further &lt;code&gt;FROM&lt;/code&gt; instruction states that the base image for creating a custom image is &lt;em&gt;cypress/factory&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;In the next &lt;code&gt;WORKDIR&lt;/code&gt; step, the working directory &lt;code&gt;/tests&lt;/code&gt; is created inside the resulting image for executing all subsequent commands. &lt;/p&gt;

&lt;p&gt;Then as in previous cases, &lt;code&gt;COPY&lt;/code&gt; instruction specifies the files and folders from the current project folder (which contains &lt;em&gt;Dockerfile&lt;/em&gt;) to be copied to &lt;code&gt;WORKDIR&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Next, &lt;code&gt;RUN&lt;/code&gt; instruction will install the necessary dependencies specified in &lt;em&gt;package.json&lt;/em&gt; file. &lt;/p&gt;

&lt;p&gt;At the last stage, &lt;code&gt;ENTRYPOINT&lt;/code&gt; instruction, as already noted, specifies the command to run Cypress inside the containers that will be generated based on this image.&lt;/p&gt;

&lt;p&gt;Now let’s create an image following the instructions above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the image is built, we make sure that it is available:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fs3vvrkzpy2l2l8k3kxaa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fs3vvrkzpy2l2l8k3kxaa.png" alt=" " width="777" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s initiate container creation from our resulting image and run Cypress tests inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, in the created container, the tests are automatically run in an environment with the versions of Node.js and Chrome browser specified in &lt;em&gt;Dockerfile&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Feto9e449hfhemellrk3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Feto9e449hfhemellrk3q.png" alt=" " width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously, &lt;strong&gt;&lt;em&gt;to control the behavior of Cypress&lt;/em&gt;&lt;/strong&gt; during test execution, we can also pass additional parameters to the container run command using &lt;em&gt;CLI flags&lt;/em&gt;. So, in the above command, the tests are run in the Chrome browser using the &lt;code&gt;-b&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;To build a custom image without adding arguments to &lt;em&gt;Dockerfile&lt;/em&gt;, we can run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt; &lt;span class="nx"&gt;NODE_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;20.1.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt; &lt;span class="nx"&gt;CHROME_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;113.0.5672.92-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt; &lt;span class="nx"&gt;EDGE_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;113.0.1774.42-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt; &lt;span class="nx"&gt;FIREFOX_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;113.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cypress&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the way, this action overrides the &lt;code&gt;ARG&lt;/code&gt; values ​​if they were also set in &lt;em&gt;Dockerfile&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Despite the obvious flexibility in building custom images based on &lt;em&gt;cypress/factory&lt;/em&gt;, it is possible to combine versions that are &lt;strong&gt;&lt;em&gt;incompatible&lt;/em&gt;&lt;/strong&gt; with each other within an image. For example, certain versions of Cypress may not support certain versions of Node.js. Obviously, this will allow us to build the image, but the containers generated from it will not work correctly. According to the developers, both the image itself and containers generated from it are intended for test use only and are not intended for use hosting services in a production environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The official Cypress Docker images discussed in this article, as well as custom images created on their basis, &lt;strong&gt;&lt;em&gt;greatly simplify the integration of Cypress tests into the development&lt;/em&gt;&lt;/strong&gt; of modern web applications and &lt;strong&gt;&lt;em&gt;increase the reliability and efficiency of the testing process&lt;/em&gt;&lt;/strong&gt; in general. Providing a &lt;strong&gt;&lt;em&gt;stable and consistent test environment&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;reproducibility across different software deployment environments&lt;/em&gt;&lt;/strong&gt; greatly simplifies the setup and maintenance of automated tests, and makes it easier to detect and troubleshoot issues that arise.&lt;/p&gt;

&lt;p&gt;I hope this article was helpful for you to understand the basics of using containerization in Cypress testing. Additionally, it is worth noting that outside the article there were cases of running Cypress tests as part of several containers, parallel and cross-browser testing, which certainly deserves a separate article.&lt;/p&gt;

&lt;p&gt;That’s about it. If you found this useful, share it with a friend or community. Maybe there’s someone who will benefit from it as well. To continue your journey with me and get more information about testing with the awesome &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt; tool, you might be interested in subscribing to my blog &lt;em&gt;&lt;a href="https://medium.com/testing-with-cypress" rel="noopener noreferrer"&gt;“Testing with Cypress”&lt;/a&gt;&lt;/em&gt; and get notified when there’s a new useful article.&lt;/p&gt;

&lt;p&gt;Dockerfiles used for this article, as well as the test project itself, can be found in the blog’s &lt;a href="https://github.com/Sanzhanov/Blog-Testing-with-Cypress" rel="noopener noreferrer"&gt;repository&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for your attention! Happy testing!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>docker</category>
      <category>testing</category>
      <category>test</category>
    </item>
    <item>
      <title>Testing Excel Data with Cypress</title>
      <dc:creator>Alex Sanzhanov</dc:creator>
      <pubDate>Fri, 21 Apr 2023 03:02:20 +0000</pubDate>
      <link>https://dev.to/sanzhanov/testing-excel-data-with-cypress-1f1n</link>
      <guid>https://dev.to/sanzhanov/testing-excel-data-with-cypress-1f1n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; this article was included in the &lt;a href="https://www.lambdatest.com/newsletter/editions/issue136" rel="noopener noreferrer"&gt;136th Coding Jag&lt;/a&gt; by LambdaTest and also in &lt;a href="https://softwaretestingnotes.substack.com/p/issue-82-software-testing-notes" rel="noopener noreferrer"&gt;Issue #82&lt;/a&gt; of Software Testing Notes.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Greetings to all Cypress enthusiasts!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A couple of days ago I was approached by a member of our &lt;strong&gt;&lt;a href="https://discord.gg/cypress" rel="noopener noreferrer"&gt;Cypress-community&lt;/a&gt;&lt;/strong&gt; on Discord with a question about how to validate data from an &lt;em&gt;Excel&lt;/em&gt; file using Cypress. This prompted me to write this article.&lt;/p&gt;

&lt;p&gt;Looking ahead, I’ll just note that this is a rather simple matter, and is mainly based on setting up the corresponding &lt;em&gt;Node&lt;/em&gt; event inside the Cypress configuration file to convert the original &lt;em&gt;Excel&lt;/em&gt; file to &lt;em&gt;JSON&lt;/em&gt; format and further work with the resulting file. But first things first.&lt;/p&gt;

&lt;p&gt;If you are not yet familiar with &lt;strong&gt;&lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Cypress is an open-source JavaScript-based testing tool designed for modern web test automation. Cypress has emerged as a popular end-to-end testing tool for web applications due to a bunch of its powerful features, user-friendly interface, fast test execution time, easy installation and debugging, etc.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cypress is a real game changer in e2e and component testing and it grows at a rapid pace. And as you may have noted from my previous articles I’m a big fan of that wonderful tool!&lt;/p&gt;

&lt;p&gt;However, it is obvious that Cypress is not intended for testing &lt;em&gt;Excel&lt;/em&gt; data, which in some test scenarios can create some inconvenience if you need to validate data in &lt;em&gt;Excel&lt;/em&gt; files. Despite this, Cypress provides a way to do this, as I noted earlier, by converting the &lt;em&gt;Excel&lt;/em&gt; file to Cypress-compatible &lt;em&gt;JSON&lt;/em&gt; format. This allows you to test your source data using the same test suite that you use to test your web applications.&lt;/p&gt;

&lt;p&gt;Well, let’s imagine that in the course of executing some test scenario, we get a certain &lt;em&gt;Excel&lt;/em&gt; file in the &lt;em&gt;cypress/downloads&lt;/em&gt; directory of our project. In essence, the scenario can be anything, as well as the initial location of the initial file. And now we need to test the data in this file.&lt;/p&gt;

&lt;p&gt;For example, I have a &lt;em&gt;companies.xlsx&lt;/em&gt; file in the &lt;em&gt;downloads&lt;/em&gt; directory&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff617nnqs8iubcf140ko4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff617nnqs8iubcf140ko4.png" alt=" " width="400" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;with the data of some &lt;em&gt;10&lt;/em&gt; non-existing companies:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fytgeb57xkbssdohjmg8n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fytgeb57xkbssdohjmg8n.png" alt=" " width="596" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, let’s convert the original &lt;em&gt;companies.xlsx&lt;/em&gt; file to &lt;em&gt;JSON&lt;/em&gt; format. Actually, there are several suitable &lt;em&gt;npm packages&lt;/em&gt; for this, the most popular of which today (judging by the number of weekly downloads) is &lt;strong&gt;&lt;a href="https://github.com/SheetJS/sheetjs" rel="noopener noreferrer"&gt;SheetJS&lt;/a&gt;&lt;/strong&gt;. Let’s install it into the project by typing the following command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; npm i xlsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, using the syntax of this package, let’s add a &lt;code&gt;task&lt;/code&gt; event to the &lt;code&gt;setupNodeEvents&lt;/code&gt; function in the &lt;em&gt;cypress.config.ts&lt;/em&gt; file. This will allow us to move from the browser environment to the &lt;em&gt;Node&lt;/em&gt; environment and execute some &lt;em&gt;JavaScript&lt;/em&gt; function in this environment. For example, let’s call our function &lt;code&gt;convertXlsxToJson&lt;/code&gt;, and with its help, we will convert the original &lt;em&gt;Excel&lt;/em&gt; file into &lt;em&gt;JSON&lt;/em&gt; format:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbyvfyh8khkra95s65fyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbyvfyh8khkra95s65fyj.png" alt=" " width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, our &lt;code&gt;convertXlsxToJson&lt;/code&gt; function takes as an argument the &lt;em&gt;path&lt;/em&gt; to the initial &lt;em&gt;Excel&lt;/em&gt; file — &lt;code&gt;filePath&lt;/code&gt; and uses &lt;code&gt;XLSX.readFile()&lt;/code&gt; method to read the file, as a result of which we get a certain &lt;code&gt;workbook&lt;/code&gt; object with a rather complex structure containing the data of the original &lt;em&gt;Excel&lt;/em&gt; workbook. Let’s analyze it in more detail.&lt;/p&gt;

&lt;p&gt;The main element of &lt;code&gt;workbook&lt;/code&gt; object is &lt;code&gt;Sheets&lt;/code&gt; object, whose properties are also objects containing the individual worksheets in the original &lt;em&gt;Excel&lt;/em&gt; workbook, where the &lt;em&gt;keys&lt;/em&gt; are the names of the worksheets and the &lt;em&gt;values&lt;/em&gt; ​​are the objects that represent each worksheet. In turn, each worksheet object has its own set of properties, whose &lt;em&gt;keys&lt;/em&gt; are references to cells in the worksheet (for example, &lt;em&gt;A1, B2&lt;/em&gt;, etc.), and the &lt;em&gt;values&lt;/em&gt; ​​are objects containing information about the type of data in a particular cell, and also the value of the cell itself. Also, &lt;code&gt;workbook&lt;/code&gt; object contains &lt;code&gt;SheetNames&lt;/code&gt; array with the &lt;em&gt;names&lt;/em&gt; of the worksheets of the initial &lt;em&gt;Excel&lt;/em&gt; book, while the order of the names in the array corresponds to the order of the worksheets in the workbook. In addition, &lt;code&gt;workbook&lt;/code&gt; object also includes a bunch of other properties that contain data about the original &lt;em&gt;Excel&lt;/em&gt; workbook.&lt;/p&gt;

&lt;p&gt;To retrieve the &lt;em&gt;name&lt;/em&gt; of the &lt;em&gt;first&lt;/em&gt; worksheet that includes the companies data table (obviously, it could have been any other worksheet), we access the value of the &lt;em&gt;null&lt;/em&gt; element of &lt;code&gt;SheetNames&lt;/code&gt; array. Next, using the obtained sheet name as the &lt;em&gt;key&lt;/em&gt; of the corresponding property on &lt;code&gt;Sheets&lt;/code&gt; object, we get the worksheet object as the value of this property and put it in &lt;code&gt;worksheet&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;On the specified variable, we apply &lt;code&gt;XLSX.utils.sheet_to_json()&lt;/code&gt; method to convert the worksheet data to &lt;em&gt;JSON&lt;/em&gt; format. As a result, we have an array of objects &lt;code&gt;jsonData&lt;/code&gt;, in which each object corresponds to a specific row of the initial &lt;em&gt;Excel&lt;/em&gt; table. The properties in each object include the column headings &lt;em&gt;(keys)&lt;/em&gt; and the corresponding data in the cells &lt;em&gt;(values)&lt;/em&gt;. Ultimately, our &lt;code&gt;convertXlsxToJson&lt;/code&gt; function returns &lt;code&gt;jsonData&lt;/code&gt; array as the result of the &lt;code&gt;task&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;In general, at this stage, the task can be considered completed, since the &lt;code&gt;jsonData&lt;/code&gt; variable contains data in &lt;em&gt;JSON&lt;/em&gt; format suitable for use in tests. However, let’s imagine that we want to write the received data as a &lt;em&gt;companies.json&lt;/em&gt; file and place it, for example, in the &lt;em&gt;cypress/fixtures&lt;/em&gt; directory. To do this, we supplement our &lt;code&gt;convertXlsxToJson&lt;/code&gt; function with the following lines of code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fps9jl45oehhpidhozkjn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fps9jl45oehhpidhozkjn.png" alt=" " width="800" height="666"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Firstly, we define &lt;code&gt;fileName&lt;/code&gt; variable, in which we place the name of the original &lt;em&gt;Excel&lt;/em&gt; file (without extension), obtained based on the value of &lt;code&gt;filePath&lt;/code&gt; variable using &lt;code&gt;path.basename()&lt;/code&gt; method. Using the specified file name, we set &lt;code&gt;jsonFilePath&lt;/code&gt; variable to the path to the resulting &lt;em&gt;JSON&lt;/em&gt; file in &lt;em&gt;fixtures&lt;/em&gt; directory. Finally, we use &lt;code&gt;writeFileSync()&lt;/code&gt; method from Node.js built-in &lt;code&gt;fs&lt;/code&gt; module, which synchronously writes data to the resulting file as a &lt;em&gt;JSON&lt;/em&gt;-formatted string with 2 spaces for each indentation level using &lt;code&gt;JSON.stringify()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Thus, we set up our &lt;code&gt;task&lt;/code&gt; event &lt;code&gt;convertXlsxToJson&lt;/code&gt; in such a way that it accepts any &lt;em&gt;Excel&lt;/em&gt; file located at the specified &lt;em&gt;path&lt;/em&gt;, reads it and converts it to &lt;em&gt;JSON&lt;/em&gt; format, and also writes the resulting &lt;em&gt;JSON&lt;/em&gt; file to &lt;em&gt;fixtures&lt;/em&gt; directory.&lt;/p&gt;

&lt;p&gt;Now, to test the original data of ten companies, let’s create a &lt;em&gt;spec&lt;/em&gt; file &lt;em&gt;testExcel.cy.ts&lt;/em&gt; in &lt;em&gt;e2e&lt;/em&gt; directory. First, let’s add &lt;code&gt;before&lt;/code&gt; hook to our test suite, in which we will call our &lt;code&gt;task&lt;/code&gt; event, passing it the &lt;em&gt;path&lt;/em&gt; to the initial &lt;em&gt;Excel&lt;/em&gt; file as an argument:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxx0v5a37fql9a522osam.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxx0v5a37fql9a522osam.png" alt=" " width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After calling our &lt;code&gt;task&lt;/code&gt; event, the resulting &lt;em&gt;companies.json&lt;/em&gt; file will be written in the &lt;em&gt;fixtures&lt;/em&gt; directory:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8logrk3oo0b8e92z5w1r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8logrk3oo0b8e92z5w1r.png" alt=" " width="385" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;with the following structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ffh94lnqq2yj33v9ipvxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffh94lnqq2yj33v9ipvxh.png" alt=" " width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After adding the tests, finally, our &lt;em&gt;spec&lt;/em&gt; file will have the following content:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft1vw6bgicq2vdxdagz82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft1vw6bgicq2vdxdagz82.png" alt=" " width="800" height="1038"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, first we define two variables: &lt;code&gt;xlsxPath&lt;/code&gt; — the path to the original &lt;em&gt;Excel&lt;/em&gt; file and &lt;code&gt;jsonName&lt;/code&gt; — the name of the final &lt;em&gt;JSON&lt;/em&gt; file, obtained based on the value of the &lt;code&gt;xlsxPath&lt;/code&gt; variable using the &lt;code&gt;path.basename()&lt;/code&gt; and &lt;code&gt;replace()&lt;/code&gt; methods. Next, inside the &lt;code&gt;beforeEach&lt;/code&gt; hook, we use the &lt;code&gt;cy.fixture()&lt;/code&gt; command to load the contents of the resulting &lt;em&gt;JSON&lt;/em&gt; file as a &lt;em&gt;fixture&lt;/em&gt;, which we &lt;em&gt;alias&lt;/em&gt; &lt;code&gt;companiesData&lt;/code&gt; to access the specified content in each test.&lt;/p&gt;

&lt;p&gt;As possible examples of tests, I have given &lt;em&gt;three&lt;/em&gt; rather primitive demo tests. So, the &lt;em&gt;first&lt;/em&gt; one checks if our test file contains data for &lt;em&gt;10&lt;/em&gt; companies. The &lt;em&gt;second&lt;/em&gt; verifies that each company’s data contains &lt;em&gt;non-empty&lt;/em&gt; values ​​for the four keys — “Company Name”, “Product”, “City”, and “Email”. And the &lt;em&gt;last&lt;/em&gt; test checks if each company’s data contains a &lt;em&gt;unique&lt;/em&gt; email address.&lt;/p&gt;

&lt;p&gt;Let’s run our tests in the Cypress test runner with the standard command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;We click on the name of the &lt;em&gt;spec&lt;/em&gt; file in the test runner window and voila — our tests completed successfully in less than &lt;em&gt;0.1&lt;/em&gt; seconds:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fslvmp0bwyk8p08cxioz2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fslvmp0bwyk8p08cxioz2.gif" alt=" " width="800" height="978"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Although Cypress can be used to validate &lt;em&gt;Excel&lt;/em&gt; data, this is obviously not the case for its core functionality. Cypress does not test &lt;em&gt;Excel&lt;/em&gt; files “under the hood”. Therefore, you should be aware of possible errors that may occur at the stage of converting an &lt;em&gt;Excel&lt;/em&gt; file to &lt;em&gt;JSON&lt;/em&gt; format, such as formatting errors, data loss, etc., which can subsequently lead to undesirable results. Despite the relative rarity of the described case and the comparative simplicity of the approach to solving it, I really hope that this article will be useful for improving your testing skills with Cypress.&lt;/p&gt;

&lt;p&gt;That’s about it. If you found this useful, share it with a friend or community. Maybe there’s someone who will benefit from it as well. To continue your journey with me and get more information about testing with the awesome &lt;strong&gt;&lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt;&lt;/strong&gt; tool, you might be interested in subscribing to my blog &lt;strong&gt;&lt;a href="https://medium.com/testing-with-cypress" rel="noopener noreferrer"&gt;“Testing with Cypress”&lt;/a&gt;&lt;/strong&gt; and get notified when there’s a new useful article.&lt;/p&gt;

&lt;p&gt;The source code of all examples as well as the files presented in this article can be found in the &lt;a href="https://github.com/Sanzhanov/Blog-Testing-with-Cypress" rel="noopener noreferrer"&gt;repository&lt;/a&gt; of the blog on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for your attention! Happy testing!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>testing</category>
      <category>javascript</category>
      <category>test</category>
    </item>
    <item>
      <title>How to optimize Cypress tests using JavaScript abilities? (Part 2. Recursion)</title>
      <dc:creator>Alex Sanzhanov</dc:creator>
      <pubDate>Thu, 20 Apr 2023 20:24:54 +0000</pubDate>
      <link>https://dev.to/sanzhanov/how-to-optimize-cypress-tests-using-javascript-abilities-part-2-recursion-3lb0</link>
      <guid>https://dev.to/sanzhanov/how-to-optimize-cypress-tests-using-javascript-abilities-part-2-recursion-3lb0</guid>
      <description>&lt;p&gt;&lt;em&gt;Greetings to all Cypress enthusiasts!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the last &lt;a href="https://medium.com/testing-with-cypress/how-to-optimize-cypress-tests-using-javascript-abilities-part-1-loops-52f0ec422ab6" rel="noopener noreferrer"&gt;article&lt;/a&gt;, we looked at various cases of optimizing &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cypress&lt;/strong&gt;&lt;/a&gt; tests based on the use of loops. I would like to delve a bit into the problem of optimizing repetitive tasks and consider another programming construct used to repeatedly repeat a particular piece of code or set of instructions. This construction is called a &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Recursion" rel="noopener noreferrer"&gt;&lt;strong&gt;recursion&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What exactly does recursion mean?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recursion is a programming technique that involves a function calling itself within its own code. When a function is called recursively, it solves a problem by breaking it down into smaller and simpler subproblems until it reaches the base case, which is the smallest subproblem that can be solved without further recursion.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;How can recursion be used in Cypress tests?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Cypress automated tests, recursion can be used to iterate through complex nested structures or perform actions on dynamic elements that may not be present when the test is first executed. By breaking down the problem into smaller subproblems, recursion allows for more efficient and modular code that can handle a variety of scenarios. One of the most common cases of using recursion in Cypress tests is testing of &lt;strong&gt;pagination&lt;/strong&gt; (see below).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Why use recursion in Cypress tests when we can use loops instead?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using recursion may be more suitable than using loops in Cypress tests when the problem or task requires repeated execution of a block of code on a &lt;em&gt;nested structure&lt;/em&gt;, or when the problem can be divided into smaller, similar sub-problems. Recursion is also useful when the exact number of iterations needed is not known in advance, or when the nesting level of the elements being tested is unknown or variable. Additionally, recursion can make the code more readable and easier to maintain in cases where loops would require complex conditional statements or multiple nested loops.&lt;/p&gt;

&lt;p&gt;Now that we have an understanding of the advantages of recursion for optimizing testing processes, let’s consider several common cases of its use in Cypress tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Iterating through nested elements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Cypress tests, it is common to iterate through nested elements to perform various actions like searching for elements, clicking or validating their properties, etc. Recursion can be used in this scenario when the elements are nested in an unknown or dynamic depth, meaning that the number of nested elements can vary.&lt;/p&gt;

&lt;p&gt;Let’s consider an example where we want to validate the text of all nested elements inside a parent element. We can define a recursive function that traverses through all the nested elements, checks if it has any children, and then recursively calls the same function on those children:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fc9vhiejx916pps13fkt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fc9vhiejx916pps13fkt7.png" alt=" " width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above example, the &lt;code&gt;validateNestedElements()&lt;/code&gt; function takes a parent element, checks if it has any children, and recursively calls itself on each child element until it reaches the leaf elements without any children. Then &lt;code&gt;expect&lt;/code&gt; statement is used to validate the text of each leaf element.&lt;/p&gt;

&lt;p&gt;Recursion can also be used to iterate through nested elements in a page and perform actions on them. Therefore, this scenario is very suitable for testing &lt;strong&gt;pagination&lt;/strong&gt; when we are going to test moving to each &lt;em&gt;next/previous&lt;/em&gt; page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fspt3nwq0s6jg3brzpjtj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fspt3nwq0s6jg3brzpjtj.png" alt=" " width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;visitTextPageIfPossible()&lt;/code&gt; function this code repeatedly clicks on the &lt;em&gt;“next”&lt;/em&gt; button, waiting for the page to update, until the button becomes &lt;em&gt;disabled&lt;/em&gt;, which indicates that there are no more pages to display.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Handling asynchronous operations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recursion can be useful in handling &lt;em&gt;asynchronous&lt;/em&gt; operations in Cypress tests when dealing with situations where we need to retry an action until a certain condition is met. For example, suppose we have a test case where we need to wait for an element to appear on the page, but due to network latency or other factors, the element may not appear immediately. In such cases, we can use recursion to &lt;em&gt;retry&lt;/em&gt; the action until the element is found or until a certain timeout is reached:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxgoivrj15pl92wo6t6xa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxgoivrj15pl92wo6t6xa.png" alt=" " width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, the &lt;code&gt;clickElement()&lt;/code&gt; function uses recursion to handle the case where the element to be clicked is not immediately available on the page. The function first attempts to locate the element using &lt;code&gt;cy.get()&lt;/code&gt; command, with a timeout of &lt;em&gt;5 seconds&lt;/em&gt;. If the element is found, the function clicks on it and returns. If the element is not found, the function recursively calls itself with the same selector and one less retry remaining. This continues until either the element is successfully clicked or the maximum number of retries is reached, at which point an error is thrown.&lt;/p&gt;

&lt;p&gt;Using recursion with &lt;em&gt;retries&lt;/em&gt; is better than multiple increases in the &lt;em&gt;timeout&lt;/em&gt; because it allows us to wait for the element to become visible without blocking the execution of the test. Increasing the timeout multiple times would add unnecessary delays to the test and could make it more prone to flakiness if the element takes longer to appear than expected. Recursion with retries allows us to wait for the element without adding unnecessary delays, and if the element never appears, the test fails quickly rather than waiting for an excessive amount of time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Testing complex user interactions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recursion can be used in Cypress tests to test complex user interactions that involve multiple steps and dynamic elements. In such scenarios, using recursion can be more suitable than loops because it allows for a more elegant and efficient way of navigating through dynamic UI elements.&lt;/p&gt;

&lt;p&gt;For example, let’s say we have a form with a dynamic number of fields, and we want to test that all required fields are filled before submitting the form. We can use recursion to navigate through each field, check if it’s required, and fill it if it’s empty:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9t4ruwkvrwtsy4ucnkyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9t4ruwkvrwtsy4ucnkyj.png" alt=" " width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, the &lt;code&gt;fillForm()&lt;/code&gt; function takes an array of &lt;code&gt;FormField()&lt;/code&gt; objects and an optional &lt;code&gt;index&lt;/code&gt; parameter. It checks if the current field at the given index is required and empty, and fills it if needed using Cypress &lt;code&gt;cy.get()&lt;/code&gt; and &lt;code&gt;cy.type()&lt;/code&gt; commands. It then calls itself recursively with the next index until all fields are processed. Finally, when all fields are processed, the function submits the form using Cypress &lt;code&gt;submit()&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Building complex test flows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recursion can be used to build complex test flows that require multiple steps and conditions. For example, if you have a test that requires the user to navigate through a series of pages and perform different actions on each page, you can use recursion to build a flow that navigates through the pages and performs the necessary actions.&lt;/p&gt;

&lt;p&gt;Let’s consider an example where we want to test a form that has multiple pages, and we need to fill in various fields on each page before we can submit the form. We can use recursion to navigate through the pages, fill in the fields, and move to the next page until we reach the last page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fseadm67rz5i9jbd4akbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fseadm67rz5i9jbd4akbk.png" alt=" " width="800" height="1353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this code, we define an array of &lt;code&gt;FormPage&lt;/code&gt; objects, where each object represents a page in the form. Each page has an array of &lt;code&gt;FormField&lt;/code&gt; objects, which represent the fields on that page. The next property is a function that is called when the current page is filled out and submitted, which navigates to the next page.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;fillFormPage()&lt;/code&gt; function takes an index of the current page and fills out the fields on that page using &lt;code&gt;cy.get()&lt;/code&gt; and &lt;code&gt;cy.type()&lt;/code&gt; commands. If the current page has a &lt;code&gt;next&lt;/code&gt; function defined, it calls that function to move to the next page. The &lt;code&gt;submitForm()&lt;/code&gt; function submits the form by clicking the submit button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Dynamic data handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recursion can be used to handle dynamic data that changes over time. For example, if you have a list of elements that is updated dynamically, you can use recursion to continuously check for updates and perform actions on the updated elements.&lt;/p&gt;

&lt;p&gt;Suppose we have a web page that displays a list of items, where each item has a unique ID and some data associated with it. The data displayed on the page can change dynamically based on various user interactions or other factors. Our Cypress test needs to verify that all the items are displayed correctly on the page, but we don’t know how many items there are or what their IDs are in advance. In this case, we can use recursion to traverse the list of items on the page and check their data one by one until we have checked them all:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fx88wa12rh7i1wjwbuksl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fx88wa12rh7i1wjwbuksl.png" alt=" " width="800" height="739"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this code, the &lt;code&gt;checkAllItems()&lt;/code&gt; function calls itself recursively with the next index until all items have been checked. The function extracts the data for each item from the corresponding DOM element and performs some assertions on the data.&lt;/p&gt;

&lt;p&gt;The reason why recursion is a good fit for this scenario is that we don’t know how many items there are in advance, so we can’t use a traditional loop with a fixed number of iterations. Additionally, the Cypress commands used in the function are asynchronous and return promises, so we need to use recursion to ensure that each item is checked in the correct order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Implementing retries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Cypress tests, sometimes there may be &lt;em&gt;flaky&lt;/em&gt; tests, where the test may fail intermittently due to network latency, server issues, or other reasons. In such scenarios, it’s helpful to retry the test multiple times to increase the likelihood of it passing.&lt;/p&gt;

&lt;p&gt;To implement &lt;em&gt;retries&lt;/em&gt; in Cypress tests, we can use recursion. The basic idea is that we will recursively call the test function until it passes or until a maximum number of attempts have been made.&lt;/p&gt;

&lt;p&gt;Let’s say we have a test that checks whether a login form is working properly. We want to retry this test up to three times if it fails due to some intermittent issue. Here’s an example of how we can implement this using recursion:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ffssgdg78io6kh6gtasqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffssgdg78io6kh6gtasqv.png" alt=" " width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, we define a &lt;code&gt;testLogin()&lt;/code&gt; function that takes two arguments: &lt;code&gt;maxAttempts&lt;/code&gt;, which is the maximum number of times the test should be retried, and &lt;code&gt;currentAttempt&lt;/code&gt;, which is the current attempt number (defaulted to 1).&lt;/p&gt;

&lt;p&gt;Inside the function, we first visit the login page and fill in the form. Then we use &lt;code&gt;cy.url().should()&lt;/code&gt; to check whether we have been redirected to the dashboard page. If the assertion passes, we do nothing and the test is considered to have passed. If the assertion fails, we check whether we have exceeded the maximum number of attempts. If we have not exceeded the maximum, we recursively call &lt;code&gt;testLogin()&lt;/code&gt; with an incremented attempt number. If we have exceeded the maximum, we use an assertion to fail the test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In conclusion, recursion is a powerful tool that can greatly enhance the flexibility and functionality of Cypress tests. As we’ve seen in this article, it can be used in a variety of scenarios, from iterating through nested elements to building complex test flows. However, it’s important to note that while recursion can be a useful approach, it may not always be the most efficient option due to potential performance issues.&lt;/p&gt;

&lt;p&gt;It’s crucial to evaluate each use case carefully and consider alternative solutions if necessary. Ultimately, the goal is to create Cypress tests that are reliable, maintainable, and efficient. By understanding the strengths and limitations of recursion in testing, we can make informed decisions and optimize our test suites for success.&lt;/p&gt;

&lt;p&gt;The source code of all presented examples can be found in the corresponding &lt;a href="https://github.com/Sanzhanov/Cypress-tests-optimization" rel="noopener noreferrer"&gt;repository&lt;/a&gt; on GitHub. To continue your journey with me and get even more information about testing with the awesome &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cypress&lt;/strong&gt;&lt;/a&gt; tool, I invite you to subscribe to my blog &lt;a href="https://medium.com/testing-with-cypress" rel="noopener noreferrer"&gt;&lt;strong&gt;“Testing with Cypress”&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for your attention! Happy testing!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>javascript</category>
      <category>testing</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Selecting elements in Cypress tests: basic + advanced patterns (2 useful Cheatsheets)</title>
      <dc:creator>Alex Sanzhanov</dc:creator>
      <pubDate>Thu, 23 Mar 2023 18:52:07 +0000</pubDate>
      <link>https://dev.to/sanzhanov/selecting-elements-in-cypress-tests-useful-cheatsheet-1bko</link>
      <guid>https://dev.to/sanzhanov/selecting-elements-in-cypress-tests-useful-cheatsheet-1bko</guid>
      <description>&lt;p&gt;&lt;em&gt;Greetings to all Cypress enthusiasts!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this very short note, I would like to share with you one very useful thing for selecting elements in &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt; tests. To be honest, I had plans for a long time to summarize the most common CSS and jQuery selector patterns, which in fact was the reason for creating this extensive table. You can of course use the great Cypress built-in commands like &lt;em&gt;contains()&lt;/em&gt;, &lt;em&gt;find()&lt;/em&gt;, &lt;em&gt;eq()&lt;/em&gt;, etc. to select test elements. However, skillfully combining element attributes, binding to pseudo-classes and the current state of elements, using a hierarchy and combinators, and so on can give you additional advantages and ensure reliability in choosing DOM elements.&lt;/p&gt;

&lt;p&gt;Well, I present to you my cheatsheets with basic and advanced selector patterns and examples of their use with the Cypress &lt;em&gt;cy.get()&lt;/em&gt; command:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cheatsheet #1: Basic patterns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4n2huovwwocn38gj9to1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4n2huovwwocn38gj9to1.png" alt=" " width="800" height="1120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cheatsheet #2: Advanced patterns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fr3ur68eenajs87d3ho2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fr3ur68eenajs87d3ho2a.png" alt=" " width="800" height="1292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I really hope that this will be useful for you and help you improve your skills in testing. To continue your journey with me and get even more information about testing with the awesome Cypress tool, I invite you to subscribe to my blog &lt;a href="https://medium.com/testing-with-cypress" rel="noopener noreferrer"&gt;“Testing with Cypress”&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for your attention! Happy testing!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>testing</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to optimize Cypress tests using JavaScript abilities? (Part 1. Loops)</title>
      <dc:creator>Alex Sanzhanov</dc:creator>
      <pubDate>Tue, 28 Feb 2023 07:31:37 +0000</pubDate>
      <link>https://dev.to/sanzhanov/how-to-optimize-cypress-tests-using-javascript-abilities-part-1-loops-12mn</link>
      <guid>https://dev.to/sanzhanov/how-to-optimize-cypress-tests-using-javascript-abilities-part-1-loops-12mn</guid>
      <description>&lt;p&gt;&lt;em&gt;Greetings to all Cypress enthusiasts!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Those who have had experience working with &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cypress.io&lt;/strong&gt;&lt;/a&gt; know that it is truly a wonderful tool that provides great opportunities for developing and implementing high-quality and reliable tests in your projects. Therefore, all that remains for us is to be able to make the best use of these opportunities.&lt;/p&gt;

&lt;p&gt;The fact is that over the past few years, I have encountered various approaches to writing tests, sometimes with fundamentally different models and templates for building test scenarios and test cases. And it often happened that already written tests needed serious improvement or even complete restructuring, mainly to ensure their reliability and reduce the time it takes to run them. It’s obvious that as a QA engineer, it is crucial to continuously evaluate and optimize testing processes to ensure the highest possible level of quality assurance.&lt;/p&gt;

&lt;p&gt;And in my observations, problems were often caused not only by a lack of deep understanding of the built-in commands of the testing framework, but also very often by an inability to apply &lt;em&gt;basic programming language capabilities&lt;/em&gt; in tests. This has prompted me to write a series of articles in which I share some of my techniques for optimizing Cypress tests, drawing on simple and mostly familiar JavaScript capabilities.&lt;/p&gt;

&lt;p&gt;Among the most common errors, a separate group can be identified for cases where QA engineers repeatedly perform exactly the same actions on different elements, be it form fields, buttons, links, or any other UI elements. Often, depending on the tested elements or actions performed, they are often divided into separate tests. This leads to a sharp increase in the number of tests, which is further compounded by the presence of multiple prerequisites placed in &lt;em&gt;beforeEach&lt;/em&gt; and &lt;em&gt;afterEach&lt;/em&gt; hooks, which are repeated over and over again as test suites are executed. In some cases, such unjustified omissions ultimately lead to a significant increase in the overall time it takes to run tests.&lt;/p&gt;

&lt;p&gt;In general, it is important to note that such erroneous actions do not correspond to the &lt;em&gt;DRY&lt;/em&gt; principle, which can make the tests harder to maintain, slower to run, and more prone to errors and inconsistencies.&lt;/p&gt;

&lt;p&gt;Therefore, in the first article, I would like to discuss when and how the best way to optimize repeatedly performed actions, events, or assertions in Cypress tests using basic JavaScript &lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration" rel="noopener noreferrer"&gt;loops&lt;/a&gt;&lt;/strong&gt; to avoid the above-mentioned ungrateful situations.&lt;/p&gt;

&lt;p&gt;After analyzing the most frequently occurring cases of iterative actions during testing, I identified &lt;em&gt;8 common scenarios&lt;/em&gt; where the use of loops is most effective. This is certainly not an exhaustive list, as other cases may arise depending on the specified test scenarios. Next, I plan to examine each of these scenarios in detail and demonstrate with specific examples how loops can significantly help optimize your Cypress tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Iterating through a set of items or elements
&lt;/h2&gt;

&lt;p&gt;When iterating through a set of items or elements, loops come in handy to perform repetitive tasks. For instance, if there are multiple checkboxes on a web page, a loop can be used to select or deselect each one of them. The loop can be controlled by setting the start and end values, making the code cleaner and efficient. By using loops in this scenario, you can ensure that all items or elements are verified or interacted with as needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F23790enscf2g08ua5v5b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F23790enscf2g08ua5v5b.png" alt=" " width="800" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, we use the &lt;code&gt;Array.from()&lt;/code&gt; method to convert an array-like object returned by Cypress &lt;code&gt;cy.get()&lt;/code&gt; command to a standard array. We then use a &lt;code&gt;forEach()&lt;/code&gt; loop to iterate over each element and perform some action.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Repeating a test with different inputs or expected outputs
&lt;/h2&gt;

&lt;p&gt;For example, if there are multiple forms on a page, each requiring different inputs, a loop can be used to run the same test with each form. The loop can be controlled using conditional statements that verify the expected outputs for each input. This approach saves time and reduces the amount of code needed to test multiple scenarios.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fx1fiwurvwzdd7jpdci88.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fx1fiwurvwzdd7jpdci88.png" alt=" " width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, we use a &lt;em&gt;&lt;strong&gt;for…of&lt;/strong&gt;&lt;/em&gt; loop with &lt;em&gt;destructuring&lt;/em&gt; to repeat a test with different inputs and expected outputs. We define an array of objects, each with an input and expected output property, and use the loop to iterate over each object and perform the test.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Navigating through multiple pages
&lt;/h2&gt;

&lt;p&gt;Cypress test can navigate through multiple pages of a web application by implementing a loop to check the contents of each page. The loop’s value can be regulated with conditional statements that confirm the existence of a next or previous page. Such a technique not only enhances the code’s organization but also improves its readability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9knxxb926o2xsbwhsqfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9knxxb926o2xsbwhsqfv.png" alt=" " width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This example demonstrates how to use &lt;code&gt;forEach()&lt;/code&gt; loop to navigate through multiple pages. The code creates an array of page URLs, and then uses a &lt;code&gt;forEach()&lt;/code&gt; loop to visit each page URL and assert that the page has loaded correctly using Cypress’s &lt;code&gt;should()&lt;/code&gt; method.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Testing multiple user accounts or roles
&lt;/h2&gt;

&lt;p&gt;If there are multiple roles or user accounts in a web application, a loop can be used to test each role or account by logging in with each set of credentials. The loop can be managed through conditional statements that confirm the anticipated results for every user role or account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fse0nggyq0kawg8mlcsh9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fse0nggyq0kawg8mlcsh9.png" alt=" " width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The value of using the &lt;strong&gt;&lt;em&gt;for…of&lt;/em&gt;&lt;/strong&gt; loop in this Cypress test is to execute the same test scenario multiple times with different sets of data. In this case, the test scenario is logging in with a specific user's credentials, performing some actions specific to their role, and then logging out. The loop iterates over the users array and performs the test scenario for each user in the array. This saves time and effort in writing individual tests for each user account or role, and allows for more efficient and comprehensive testing of the system under different user scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Running the same test with different configurations
&lt;/h2&gt;

&lt;p&gt;If a web application has multiple configurations, a loop can be used to test the application with each one. The loop can be determined by implementing conditional statements that define the anticipated results for every configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F08cmo6k8w36kwwhkd9zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F08cmo6k8w36kwwhkd9zm.png" alt=" " width="800" height="896"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The value of using the &lt;strong&gt;&lt;em&gt;for…of&lt;/em&gt;&lt;/strong&gt; loop in this test is to optimize the test execution by reducing code duplication. By iterating over the &lt;code&gt;configs&lt;/code&gt; array and using the configuration data to set up the testing environment and execute the tests, we can avoid repeating the same setup and test code for each configuration.&lt;/p&gt;

&lt;p&gt;This not only makes the test code more concise and easier to read, but it also reduces the risk of errors and omissions that can occur when copying and pasting test code. Additionally, by separating the configuration data from the test code, we can easily add or remove configurations without having to modify the test code.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Dynamically generating test data
&lt;/h2&gt;

&lt;p&gt;Loops can be used to generate test data dynamically. For instance, if there is a need to create multiple user accounts, a loop can be used to generate a set of user data with each iteration. The loop can be controlled by setting the start and end values and by specifying the number of users to create.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F94s98d8hphj4lw0zeeev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F94s98d8hphj4lw0zeeev.png" alt=" " width="800" height="812"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;forEach()&lt;/code&gt; loop is used here to iterate over each key in the &lt;code&gt;testData&lt;/code&gt;object and perform a test for each input. This loop enables the code to avoid duplicating the same test case for each input, and instead, it performs the same test for each input dynamically, thus saving time and effort.&lt;/p&gt;

&lt;p&gt;By using a loop to iterate over the &lt;code&gt;testData&lt;/code&gt;object, it becomes easy to add more test cases without duplicating code. This makes the code more efficient and reduces the likelihood of errors when adding new tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Iterating through a set of test steps
&lt;/h2&gt;

&lt;p&gt;Also, loops can be used to iterate through a set of test steps when testing a web application. For example, if there are multiple steps involved in testing an application, a loop can be used to repeat the steps with each iteration. The loop can be managed using conditional statements that specify the expected outputs for each step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fx6sr5qakx536kxqylh4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fx6sr5qakx536kxqylh4h.png" alt=" " width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this Cypress test each step is an object that contains information about an action that needs to be performed during the test, such as clicking on a button or typing into an input field. By using a loop &lt;code&gt;forEach()&lt;/code&gt; to iterate through the steps array, the test can perform each action sequentially and with less duplicated code.&lt;/p&gt;

&lt;p&gt;Inside the loop, the test checks the action property of each step object to determine what action should be performed. Depending on the value of action, the test will use a different Cypress command to perform the action. For example, if the action is click, the test will use the &lt;code&gt;cy.get().click()&lt;/code&gt; command to simulate a button click.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Testing the same functionality or behavior across different environments
&lt;/h2&gt;

&lt;p&gt;Loops can be used to test the same functionality or behavior across different environments, such as multiple screen resolutions or browser types. For example, if a web application needs to be tested on different browsers or screen resolutions, a loop can be used to test the application on each configuration. The loop can be controlled using conditional statements that verify the expected outputs for each environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fj6ibamf74tgwbq995yvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fj6ibamf74tgwbq995yvc.png" alt=" " width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the loop, the test sets the viewport size using the &lt;code&gt;cy.viewport()&lt;/code&gt; command to match the width and height properties of the current environment. Using a loop in this way allows the test to be run multiple times with different configurations, without having to duplicate the same test code. This can make the test more efficient and easier to maintain, especially when there are many different environments to test against.&lt;/p&gt;

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

&lt;p&gt;Thus, we have confirmed that there are numerous useful applications of loops in Cypress tests, significantly improving testing efficiency, reducing the time spent on test runs, and enhancing test coverage. Overall, loops are an important tool for automating testing in Cypress, improving the process and saving time and effort. In our next articles, we will explore other JavaScript features that can help optimize your Cypress tests.&lt;/p&gt;

&lt;p&gt;The source code of all presented examples can be found in the corresponding &lt;a href="https://github.com/Sanzhanov/Cypress-tests-optimization" rel="noopener noreferrer"&gt;&lt;em&gt;repository&lt;/em&gt;&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for your attention! Happy testing!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gaminghardware</category>
      <category>gamedeals</category>
      <category>offers</category>
    </item>
    <item>
      <title>Node Version Manager (NVM): how to install and use (step-by-step guide)</title>
      <dc:creator>Alex Sanzhanov</dc:creator>
      <pubDate>Wed, 16 Nov 2022 19:17:39 +0000</pubDate>
      <link>https://dev.to/sanzhanov/node-version-manager-nvm-how-to-install-and-use-step-by-step-guide-k4a</link>
      <guid>https://dev.to/sanzhanov/node-version-manager-nvm-how-to-install-and-use-step-by-step-guide-k4a</guid>
      <description>&lt;p&gt;&lt;em&gt;As it turns out, the correct installation and usage of NVM on Windows OS often raises some questions due to the fact that there are some inaccuracies in the &lt;a href="https://github.com/coreybutler/nvm-windows" rel="noopener noreferrer"&gt;official repository&lt;/a&gt; of this tool. In this regard, in this article, I will tell you how to install NVM on your computer step by step in a more optimal way (MacOS users do not need to read this).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  NVM: What's the point? 🤨
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Node Version Manager (NVM)&lt;/strong&gt; is a tool that allows you to have multiple versions of Node.js on your device, switch them quickly, and it is managed from the command line interface (CLI).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why is this needed?&lt;/strong&gt; The fact is that different applications you work with can be implemented on different versions of Node.js. In practice, this is usually indicated by the &lt;em&gt;.nvmrc&lt;/em&gt; file which is committed with your project and contains a single line with the version of Node.js to use for this project. However, running the application in another environment may not give the expected result or lead to errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I do without it?&lt;/strong&gt; The cases when NVM is just an indispensable thing occur very often in the workplace. Let me give you a small example from my past experience. Having come to work for a new company some time ago, I had to quickly get to the bottom of one of their past projects. At that time, we seemed to be using Node.js version &lt;em&gt;14.16.0&lt;/em&gt;, but the project repository required version &lt;em&gt;10.x&lt;/em&gt;. Of course, if I did not know about NVM, I would only have to reinstall from one version to another. And so for each new project! Definitely, it would be a big headache and a waste of working time.&lt;br&gt;
Moreover, when you work on a large team, it's rare that everyone is using the same version of Node.js.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to install NVM correctly? 🤔
&lt;/h2&gt;

&lt;p&gt;Well, let's get started with NVM installation. Along the way, I will point out the inaccuracies in the official repository that I mentioned at the very beginning of this article. Here I'm only emphasizing that the further explanation is based on the example of the &lt;em&gt;11th&lt;/em&gt; version of Windows OS.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;To begin, open the terminal application on your computer. For Windows, I recommend using &lt;em&gt;Git Bash&lt;/em&gt; due to some commands may not be available in &lt;em&gt;Windows PowerShell&lt;/em&gt;. Ideally, use the built-in &lt;em&gt;Windows Terminal&lt;/em&gt;, in which you need to add &lt;em&gt;Git Bash&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;Let's make sure NVM is not already installed on your computer. Just enter the command:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;nvm&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;If this is correct, then you will see the following response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F7wiq6yqs6hvvzqbsm5bq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F7wiq6yqs6hvvzqbsm5bq.png" alt=" " width="382" height="165"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Go to the NVM for Windows &lt;a href="https://github.com/coreybutler/nvm-windows/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt; and select the latest version (the top one in the list), for example, it is currently &lt;em&gt;1.1.10&lt;/em&gt;. Then download the &lt;em&gt;nvm-setup.exe&lt;/em&gt; file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0qjqitpmbcr4tpuhr3t8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0qjqitpmbcr4tpuhr3t8.png" alt=" " width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;I assume that most of you already have Node.js installed at this point (if not yet, then NVM will do it for you a little later). And if we turn to the &lt;a href="https://github.com/coreybutler/nvm-windows" rel="noopener noreferrer"&gt;official manual&lt;/a&gt;, it states that before installing NVM we need to uninstall any existing versions of Node.js:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3ccm1iopaituu7cfwgvz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3ccm1iopaituu7cfwgvz.png" alt=" " width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, we will not follow this advice. Instead, run the downloaded setup file. You'll be prompted to agree with the project's terms of use (сhoose &lt;em&gt;'I accept the agreement'&lt;/em&gt; option), then the installer will ask where to install NVM (click on the &lt;em&gt;'Next'&lt;/em&gt; button). &lt;br&gt;
&lt;strong&gt;And now attention!&lt;/strong&gt; &lt;br&gt;
You'll be prompted to indicate where to set the Node.js &lt;em&gt;Symlink&lt;/em&gt;. This is the next inaccuracy of the official manual because the default path &lt;code&gt;C:\Program Files\nodejs&lt;/code&gt; is not correct:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkeundrric4e9heh8jawu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkeundrric4e9heh8jawu.png" alt=" " width="573" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I strongly recommend to &lt;strong&gt;change this path&lt;/strong&gt; to any other, that does not contain spaces. For example, I specified the following folder in the user's home directory: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fg61xrqyiqirw1ebds2t9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fg61xrqyiqirw1ebds2t9.png" alt=" " width="572" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next window, click the &lt;em&gt;'Install'&lt;/em&gt; button. Immediately after installation, NVM recognizes that you already have Node.js installed and asks the following question:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbnn4pdpb0dhg1gce95oi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbnn4pdpb0dhg1gce95oi.png" alt=" " width="541" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the &lt;em&gt;'Yes'&lt;/em&gt; option and the installation process will complete.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now restart the terminal application and make sure NVM is installed:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;nvm&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;If everything was successful in the previous step, you will receive the following response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fa5li2w96lfdbu06ohj96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fa5li2w96lfdbu06ohj96.png" alt=" " width="332" height="88"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;h2&gt;
  
  
  Usage 😃
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, make sure NVM is &lt;em&gt;enabled&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;nvm&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Expected response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F410y33uhx97kp2jacdd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F410y33uhx97kp2jacdd3.png" alt=" " width="401" height="112"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the &lt;em&gt;active version&lt;/em&gt; of Node.js:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;nvm&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Expected response (in your case it may be another version):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwea6kyk8w73wiunisu22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwea6kyk8w73wiunisu22.png" alt=" " width="325" height="88"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Download the &lt;em&gt;latest version&lt;/em&gt; of Node.js:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;nvm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Expected response (in your case it may be another version):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flmxu1e2r608vsjyr3hyt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flmxu1e2r608vsjyr3hyt.png" alt=" " width="760" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, I can download a specific version of Node.js, for example, 14.18.0:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;nvm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="mf"&gt;14.18&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Expected response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2xaeviwfeqahbca7va6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2xaeviwfeqahbca7va6g.png" alt=" " width="762" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to &lt;em&gt;remove&lt;/em&gt; a specific version use the command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;nvm&lt;/span&gt; &lt;span class="nx"&gt;uninstall&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open a &lt;em&gt;list of all versions&lt;/em&gt; installed on your computer:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;nvm&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Expected response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgx1291bz1qfpa7xbf2m9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgx1291bz1qfpa7xbf2m9.png" alt=" " width="605" height="160"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Switch&lt;/em&gt; to use the specified version:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;nvm&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="mf"&gt;14.18&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Expected response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fg4eil5cp65mkpdc3ckuu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fg4eil5cp65mkpdc3ckuu.png" alt=" " width="422" height="93"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A complete list of commands can also be seen in the &lt;a href="https://github.com/coreybutler/nvm-windows" rel="noopener noreferrer"&gt;official repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Well, that's where I end. I really hope that this article will help you properly install and use this wonderful and very useful tool. If you have any questions, write them in the comments below. 👋&lt;/p&gt;

</description>
      <category>nvm</category>
      <category>node</category>
      <category>testing</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Run API tests with Newman and send reports to Telegram</title>
      <dc:creator>Alex Sanzhanov</dc:creator>
      <pubDate>Sun, 06 Nov 2022 20:54:34 +0000</pubDate>
      <link>https://dev.to/sanzhanov/run-api-tests-with-newman-and-send-reports-to-telegram-340e</link>
      <guid>https://dev.to/sanzhanov/run-api-tests-with-newman-and-send-reports-to-telegram-340e</guid>
      <description>&lt;p&gt;How about running your Postman collections of API requests and automatically getting a meaningful report right in Telegram? In this article, I will show you how to implement this trick in one small script! 🔥&lt;/p&gt;

&lt;p&gt;First of all, it would be appropriate to note that this article is only a training example for creating a small part of a &lt;em&gt;continuous integration&lt;/em&gt; pipeline, which can be easily done locally using &lt;em&gt;Node.js&lt;/em&gt;. That will help you to practice running collections with (or without) executing tests and sending API requests using &lt;em&gt;node-modules&lt;/em&gt; and without Postman. But for practical use, there are many great built-in solutions for the most widely known CI tools that you can easily find in the public domain.     ♾️&lt;/p&gt;

&lt;h2&gt;
  
  
  Export of initial collection and environment from Postman 🚀
&lt;/h2&gt;

&lt;p&gt;In order to run any collections of API requests, we first need to have them available as a separate file that can be pulled from Postman, for example. For this case, in my Postman, I had a collection for the &lt;em&gt;CRM Project &lt;a href="https://clientbase.us/v5" rel="noopener noreferrer"&gt;"ClientBase v5"&lt;/a&gt;&lt;/em&gt; which contains several dozen of the most likely API requests and more than 400 built-in tests and in some cases also pre-request-scripts. &lt;/p&gt;

&lt;p&gt;In all tests, I deliberately used &lt;a href="https://www.chaijs.com/api/bdd/" rel="noopener noreferrer"&gt;Chai&lt;/a&gt; Assertion Library BDD syntax to make it easier to develop automated tests based on them. Some tests contain the same assertions but use a slightly different syntax. The point is that the Postman and Chai Library syntax allows us to implement a specific assertion in different ways, which is certainly great. 🍵&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcq706tza3q4izlib7zn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcq706tza3q4izlib7zn2.png" alt=" " width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All I needed to do here was to export the collection as a &lt;em&gt;json&lt;/em&gt; file and save it locally. Further, it will be important for me to know only the path to this file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fds5oeowacee7w1funcgr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fds5oeowacee7w1funcgr.png" alt=" " width="780" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also exported as a json the set of environment variables. Most of these variables were created as &lt;em&gt;dynamic&lt;/em&gt;, in other words, they are generated using the &lt;a href="https://www.npmjs.com/package/@faker-js/faker" rel="noopener noreferrer"&gt;faker library&lt;/a&gt;. The values ​​of variables, as you can see in the picture, are not initially set and are filled automatically during the running of requests: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff6ii16khiuei0q3hgjxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff6ii16khiuei0q3hgjxf.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are interested, this Postman collection is available &lt;a href="https://github.com/Sanzhanov/Postman-full-collection-for-CRM-ClientBase-v5" rel="noopener noreferrer"&gt;here&lt;/a&gt; (still growing).&lt;/p&gt;

&lt;h2&gt;
  
  
  Telegram Bot API 🤖
&lt;/h2&gt;

&lt;p&gt;As I mentioned, аfter running the collection and generating a report, I will send a notification and that report to Telegram. Moreover, it’s important to note here that the receiver, in this case, may not necessarily be Telegram. For example, it can be &lt;em&gt;Slack&lt;/em&gt; or any other resource which is available for interaction by API. &lt;/p&gt;

&lt;p&gt;Well, for the training example, I chose Telegram because I just wanted to learn more about the capabilities of the &lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;Telegram Bot API&lt;/a&gt;. To get started, I needed to select an existing or create a new Telegram group to receive notifications and reports there. It could be any Telegram group (public or private). So I created a new group called &lt;em&gt;"API alerts"&lt;/em&gt;. The most important step was getting my group's &lt;em&gt;chat ID&lt;/em&gt;. And I got it very easily with the help of a special bot inside Telegram - the "Get My ID" bot. I just forwarded one message from my newly created group to this bot and already had the chat ID. There were other ways to do this, but this one seemed faster.&lt;/p&gt;

&lt;p&gt;Then it was just a matter of creating a bot with which I was going to interact using the request API. Well, I did it even faster and &lt;a href="https://core.telegram.org/bots/features#botfather" rel="noopener noreferrer"&gt;here&lt;/a&gt; is a detailed guide on how to create a Telegram bot using &lt;a href="https://t.me/botfather" rel="noopener noreferrer"&gt;@Botfather&lt;/a&gt;. The main thing here is to get from Botfather a unique bot authentication &lt;em&gt;token&lt;/em&gt; that looks something like  &lt;code&gt;123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11&lt;/code&gt;.  Further, I added my new bot to the created "API alerts" group and gave the admin permission to send messages to the group. &lt;/p&gt;

&lt;p&gt;It seems there is no point in explaining this step in more detail because everything here is extremely simple and there are a lot of instructions on the Internet on how to do it. &lt;/p&gt;

&lt;p&gt;Well, it's time to create a project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the project and installing the required dependencies 🗂️
&lt;/h2&gt;

&lt;p&gt;Having completed the preparatory steps in Postman and Telegram, I finally created a new project in the IDE, initialized it, and proceeded to install the necessary dependencies. So for running the collection I used &lt;a href="https://github.com/postmanlabs/newman" rel="noopener noreferrer"&gt;Newman&lt;/a&gt; which is a command-line Collection Runner for Postman. In order to install Newman locally in the project as a library, used the following command:&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="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="nx"&gt;newman&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As for the report of collection running, in general, we can integrate it with our CI pipeline using the Postman CLI or Newman as a pure command-line Collection Runner and generate some reports for example with &lt;em&gt;Allure&lt;/em&gt; or at least &lt;em&gt;JUnit&lt;/em&gt;. However, we are also able to use Newman reporters and get at least (and sometimes much more) informative and user-friendly reports with a dashboard-style summary landing page and a set of different tabs which contain the detailed request information. &lt;/p&gt;

&lt;p&gt;To be more precise, I often prefer to use Danny Dainton’s &lt;a href="https://github.com/DannyDainton/newman-reporter-htmlextra" rel="noopener noreferrer"&gt;html-extra reporter&lt;/a&gt; which is an updated version of the standard &lt;em&gt;Newman HTML reporter&lt;/em&gt; containing a more in-depth data output and a few helpful extras. 📑 &lt;/p&gt;

&lt;p&gt;So this reporter was installed in the project using the command:&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="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="nx"&gt;newman&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;reporter&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;htmlextra&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to using environment variables in the project, I also needed another module &lt;a href="https://github.com/motdotla/dotenv" rel="noopener noreferrer"&gt;dotenv&lt;/a&gt; that loads environment variables from a &lt;em&gt;.env&lt;/em&gt; file into &lt;em&gt;process.env&lt;/em&gt;. After installing this module with the command:&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="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created a &lt;em&gt;.env&lt;/em&gt; file in the root of the project, in which I put the necessary environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;COLLECTION_PATH&lt;/em&gt; -  the path to a locally hosted json file with my Postman collection;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;ENVIRONMENT_PATH&lt;/em&gt; - the path to locally hosted json with environment variables which also was exported from Postman;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;TOKEN&lt;/em&gt; - the token of my Telegram bot;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;CHAT_ID&lt;/em&gt; - my Telegram group's chat ID. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my case, it looked like this (the values ​​of sensitive variables have of course been changed):&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="nx"&gt;COLLECTION_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;user1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;Newman&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Run&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Reporter&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;CRM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Project&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;CRM_Project_ClientBase_v5_collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="nx"&gt;ENVIRONMENT_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;user1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;Newman&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Run&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Reporter&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;CRM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Project&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ClientBase_v5_environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="nx"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;270485614&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;AAHfiqksKZ8WmR2zSjiQ7_v4TMAKdiHm9T0&lt;/span&gt;
&lt;span class="nx"&gt;CHAT_ID&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1002956968&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, the basic dependencies are installed, it's time for coding.   👨‍💻 In order to do this, I created a &lt;em&gt;runner.js&lt;/em&gt; file at the root of the project in which I included the first block of code:&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="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&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;newman&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newman&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//Running collection with Newman, report generation:&lt;/span&gt;

&lt;span class="nx"&gt;newman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COLLECTION_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENVIRONMENT_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;reporters&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;htmlextra&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;iterationCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;htmlextra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;export&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;report&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/report.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// template: './template.hbs'&lt;/span&gt;
            &lt;span class="c1"&gt;// logs: true,&lt;/span&gt;
            &lt;span class="c1"&gt;// showOnlyFails: true,&lt;/span&gt;
            &lt;span class="c1"&gt;// noSyntaxHighlighting: true,&lt;/span&gt;
            &lt;span class="c1"&gt;// testPaging: true,&lt;/span&gt;
            &lt;span class="na"&gt;browserTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Newman report&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Newman Report&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;titleSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// omitHeaders: true,&lt;/span&gt;
            &lt;span class="c1"&gt;// skipHeaders: "Authorization",&lt;/span&gt;
            &lt;span class="c1"&gt;// omitRequestBodies: true,&lt;/span&gt;
            &lt;span class="c1"&gt;// omitResponseBodies: true,&lt;/span&gt;
            &lt;span class="c1"&gt;// hideRequestBody: ["Login"],&lt;/span&gt;
            &lt;span class="c1"&gt;// hideResponseBody: ["Auth Request"],&lt;/span&gt;
            &lt;span class="na"&gt;showEnvironmentData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// skipEnvironmentVars: ["API_KEY"],&lt;/span&gt;
            &lt;span class="c1"&gt;// showGlobalData: true,&lt;/span&gt;
            &lt;span class="c1"&gt;// skipGlobalVars: ["API_TOKEN"],&lt;/span&gt;
            &lt;span class="c1"&gt;// skipSensitiveData: true,&lt;/span&gt;
            &lt;span class="c1"&gt;// showMarkdownLinks: true,&lt;/span&gt;
            &lt;span class="na"&gt;showFolderDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// timezone: "US/Boston",&lt;/span&gt;
            &lt;span class="c1"&gt;// skipFolders: "folder name with space,folderWithoutSpace",&lt;/span&gt;
            &lt;span class="c1"&gt;// skipRequests: "request name with space,requestNameWithoutSpace",&lt;/span&gt;
            &lt;span class="na"&gt;displayProgressBar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;p&gt;As you can see, in the &lt;code&gt;collection&lt;/code&gt; and &lt;code&gt;environment&lt;/code&gt; fields, I put the &lt;code&gt;COLLECTION_PATH&lt;/code&gt; and &lt;code&gt;ENVIRONMENT_PATH&lt;/code&gt; as environment variables using the &lt;em&gt;process.env&lt;/em&gt; property. &lt;/p&gt;

&lt;p&gt;In reporter settings in the &lt;code&gt;export&lt;/code&gt; field, I specified the directory where Newman will save my report file. In other words, after Newman finishes its work, a new &lt;em&gt;report&lt;/em&gt; directory will appear in the root of the project in which the &lt;em&gt;report.html&lt;/em&gt; will be placed.&lt;/p&gt;

&lt;p&gt;Also as users, we are able to customize report templates just for our needs. Html-extra reporter allowed us to do this by uncommenting the lines of code inside the &lt;code&gt;htmlextra&lt;/code&gt; field. &lt;/p&gt;

&lt;h2&gt;
  
  
  The problem of asynchronous and its solution 💡
&lt;/h2&gt;

&lt;p&gt;At this stage, we may be in for some problem related to the &lt;em&gt;asynchronous&lt;/em&gt; execution of the collection pass and sending the API call to Telegram. It is clear that Newman will take some time to run through all the requests and nested tests, as well as generate a report. Of course, this is only a couple of seconds, but the execution of the code will hasten to send a call to Telegram without waiting for the Newman.&lt;/p&gt;

&lt;p&gt;To avoid the problem of asynchrony and ensure sequential execution of script blocks, I had to turn to the help of the module &lt;a href="https://github.com/abbr/deasync" rel="noopener noreferrer"&gt;DeAsync&lt;/a&gt; which turns the async function into sync. It was installed with the command:&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="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="nx"&gt;deasync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This package allowed me to use &lt;em&gt;loopWhile(predicateFunc)&lt;/em&gt; where &lt;em&gt;predicateFunc&lt;/em&gt; is a function that returns a boolean loop condition. So I added the following block to my code:&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;
&lt;span class="nx"&gt;newman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start&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="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="s2"&gt;`Running a collection. Please wait a few seconds...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&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="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deasync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;loopWhile&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="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;done&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;Well, it remains only to set up API calls to Telegram.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending files with Fetch API 📨
&lt;/h2&gt;

&lt;p&gt;At this stage, I needed to set up sending two API calls to Telegram: the first one with a notification of the successful completion of the collection running and the second one containing an HTML report. These calls were to go immediately after Newman completed his work. To implement this task, it is certainly possible to use any HTTP client libraries for Node.js which are available as npm packages (Axios, Got, SuperAgent, etc.), but I turned to the &lt;a href="https://github.com/node-fetch/node-fetch" rel="noopener noreferrer"&gt;Fetch API&lt;/a&gt;. The following command was used to install it:&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="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code, I used the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch" rel="noopener noreferrer"&gt;fetch()&lt;/a&gt; method with the usual promise syntax:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;//Sending an information notification to Telegram:&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.telegram.org/bot&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sendMessage?chat_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CHAT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;text=
Your collection has been successfully run. The results are contained in the attached report below.`&lt;/span&gt;

&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&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="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;res&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;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="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;error&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;As you can see, the constant &lt;code&gt;url1&lt;/code&gt; contains a string with the URL to which the request is sent. This is a standard template that is set by the Telegram API. According to this template, I included the &lt;em&gt;token&lt;/em&gt; and &lt;em&gt;chat ID&lt;/em&gt; inside the URL as environment variables, which I pulled from the &lt;em&gt;.env&lt;/em&gt; file using the &lt;em&gt;process.env&lt;/em&gt; property. &lt;/p&gt;

&lt;p&gt;The next request turned out to be a little more difficult to implement because it had to send a &lt;em&gt;report.html&lt;/em&gt; file inside it, which first needed to be converted using the encoding type &lt;em&gt;multipart form-data&lt;/em&gt;. Therefore to create readable "multipart/form-data" streams, I needed to use the &lt;a href="https://github.com/form-data/form-data" rel="noopener noreferrer"&gt;Form-Data&lt;/a&gt; library. In order to install it locally, used the following command:&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="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the syntax of this library, I created a form with one field containing the &lt;em&gt;file stream&lt;/em&gt; of the &lt;em&gt;report.html&lt;/em&gt; file and passed it to the &lt;em&gt;fetch()&lt;/em&gt; method to submit as the request body. The result was the following block of code:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form-data&lt;/span&gt;&lt;span class="dl"&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//Sending html-report to Telegram:&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.telegram.org/bot&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sendDocument?chat_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CHAT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;readStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;report/report.html&lt;/span&gt;&lt;span class="dl"&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;form&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;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;readStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;form&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;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&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="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;res&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 report was successfully sent to 
        Telegram&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="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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="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;error&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;Well, it's finally time to execute the written script. However, in the &lt;em&gt;package.json&lt;/em&gt; file, I previously changed the command to run scripts by writing:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&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="s2"&gt;node runner.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I ran the script using the command:&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="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;immediately in the terminal appeared a special green bar that indicated the progress of running the collection:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjbr7qzllftmwmxy9l9cc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjbr7qzllftmwmxy9l9cc.png" alt=" " width="800" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the running was over I received two messages with notification and &lt;em&gt;report.html&lt;/em&gt; file directly in my Telegram group "API alerts": ✉️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fl14xntos65y7wzgcoovd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fl14xntos65y7wzgcoovd.png" alt=" " width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great, let's open the resulting report and see what it is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fp1g3mg082ra584b1tfva.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fp1g3mg082ra584b1tfva.gif" alt=" " width="800" height="837"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, in the terminal, I received response bodies from the Telegram API containing detailed information about what was sent and where: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F7hdwe4qi4eampz5q05mv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F7hdwe4qi4eampz5q05mv.png" alt=" " width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Instead of a conclusion 👋
&lt;/h2&gt;

&lt;p&gt;In this article, I tried to describe the entire process in as much detail as possible, from exporting the Postman collection to receiving a report on its running in Telegram. I hope that repeating the steps I did will help you charge your API skills. 💪&lt;/p&gt;

&lt;p&gt;This project with detailed installation and usage instructions is completely available &lt;a href="https://github.com/Sanzhanov/Newman-Telegram-API" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>testing</category>
      <category>api</category>
      <category>newman</category>
      <category>telegram</category>
    </item>
  </channel>
</rss>
