[Workshop for beginners] Let's write an E2E test with Cloud9 + Docker Compose + Cypress!

Introduction

[Workshop for beginners] Let's do everything from app publishing to CI / CD + E2E test automation with Cloud9 + React + TypeScript + Amplify + Cypress! I wrote an article to experience the whole process from application release to CI / CD and E2E testing. This time, I made a tutorial to quickly experience the process of gradually growing E2E tests while developing an application. The content is designed so that you can try it in about an hour!

Construction of development environment

We use AWS Cloud9, a cloud-based integrated development environment (IDE). We will build an environment where Docker Compose can run on Cloud9. Cypress is installed when Docker is built, so you don't need to install npm commands.

If you haven't acquired an AWS account yet, please refer to the official AWS account creation flow. Please have an account.

AWS Cloud9 setup

Extend the EBS volume area used by AWS Cloud9

Since Docker is built in order to proceed with the workshop, there will be insufficient space in the process of executing the build. We recommend that you expand the volume in advance by referring to this article. I increased it to 10GB-> 20GB with a margin. Extend the EBS volume area used by AWS Cloud9

Installation of required packages

Docker Compose Docker is included as standard, but Docker Compose is not included and must be installed. (As of 09/09/2020)

ec2-user:~/environment $ docker -v
Docker version 19.03.6-ce, build 369ce74
ec2-user:~/environment $ docker-compose -v
bash: docker-compose: command not found

Install by referring to the procedure in Official Document.

Download the current stable release version of Docker Compose

The following is the latest as of 09/09/2020.

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.27.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose -v
docker-compose version 1.27.0, build 980ec85b

Get sample code to try E2E testing with Cypress

Sample code that can perform E2E test with Cypress for local application using Docker is published on Github, so I will use this this time. https://github.com/cypress-io/cypress-example-docker-compose

Get sample code

This time, I will not involve committing to Github and CI / CD triggered by it, so I will clone the original repository without Forking the Github repository.

For https


$ git clone https://github.com/cypress-io/cypress-example-docker-compose.git
$ cd cypress-example-docker-compose

For ssh


$ git clone [email protected]:cypress-io/cypress-example-docker-compose.git
$ cd cypress-example-docker-compose

Build container

Run the build of the Docker container. $ docker-compose build will be executed.

$ npm run build

> [email protected] build /home/ec2-user/environment/cypress-example-docker-compose
> docker-compose build

Building web
Step 1/4 : FROM httpd:2.4
 ---> a6ea92c35c43
Step 2/4 : RUN echo "ServerName localhost" >> /usr/local/apache2/conf/httpd.conf
 ---> Using cache
 ---> e132eaf0b6d6
Step 3/4 : COPY index.html /usr/local/apache2/htdocs/
 ---> Using cache
 ---> a79afef5fb17
Step 4/4 : EXPOSE 80
 ---> Using cache
 ---> 0ba9c6797d6d

Successfully built 0ba9c6797d6d
Successfully tagged apache:latest
Building e2e
Step 1/7 : FROM cypress/base:10
10: Pulling from cypress/base
d6ff36c9ec48: Pull complete
c958d65b3090: Pull complete
edaf0a6b092f: Pull complete
80931cf68816: Pull complete
bc1b8aca3825: Pull complete
ad9790d89c32: Pull complete
6085b6a0249c: Pull complete
6af9e71c78d2: Pull complete
d85bae49b22d: Pull complete
f6c8ce594b00: Pull complete
d67d7860a80a: Pull complete
b3a1dfd049d1: Pull complete
6fb47a9e5454: Pull complete
Digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Status: Downloaded newer image for cypress/base:10
 ---> 071155d6ed07
Step 2/7 : WORKDIR /app
 ---> Running in 468f37420452
Removing intermediate container 468f37420452
 ---> 2f1c6d19e291
Step 3/7 : COPY package.json .
 ---> 90ec115cb213
Step 4/7 : COPY package-lock.json .
 ---> 54e720d60a87
Step 5/7 : ENV CI=1
 ---> Running in 5e56a602e37c
Removing intermediate container 5e56a602e37c
 ---> 670f913b90e6
Step 6/7 : RUN npm ci
 ---> Running in d511a99685c5

> [email protected] postinstall /app/node_modules/cypress
> node index.js --exec install

[14:40:14]  Downloading Cypress     [started]
[14:40:17]  Downloading Cypress     [completed]
[14:40:17]  Unzipping Cypress       [started]
[14:40:33]  Unzipping Cypress       [completed]
[14:40:33]  Finishing Installation  [started]
[14:40:33]  Finishing Installation  [completed]
added 216 packages in 25.818s
Removing intermediate container d511a99685c5
 ---> 8dc49d0f8503
Step 7/7 : RUN npx cypress verify
 ---> Running in aa9a7fb8a0d9
[14:41:10]  Verifying Cypress can run /root/.cache/Cypress/5.1.0/Cypress [started]
[14:41:14]  Verifying Cypress can run /root/.cache/Cypress/5.1.0/Cypress [completed]
Removing intermediate container aa9a7fb8a0d9
 ---> 0a5f502ade2e

Successfully built 0a5f502ade2e
Successfully tagged cypress:latest

Launching a web application and running E2E tests with Cypress

First, let's make sure that the test works untouched. And let's see what makes Cypress so happy. $ docker-compose build will be executed.

$ npm run up

> [email protected] up /home/ec2-user/environment/cypress-example-docker-compose
> docker-compose up --abort-on-container-exit --exit-code-from e2e

Creating network "cypress-example-docker-compose_default" with the default driver
Creating apache ... done
Creating cypress ... done
Attaching to apache, cypress
apache | [Wed Sep 09 14:44:05.645451 2020] [mpm_event:notice] [pid 1:tid 140649514787968] AH00489: Apache/2.4.46 (Unix) configured -- resuming normal operations
apache | [Wed Sep 09 14:44:05.664458 2020] [core:notice] [pid 1:tid 140649514787968] AH00094: Command line: 'httpd -D FOREGROUND'
cypress | 
cypress | ====================================================================================================
cypress | 
cypress |   (Run Starting)
cypress | 
cypress |   ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
cypress |   │ Cypress:    5.1.0                                                                              │
cypress |   │ Browser:    Electron 83 (headless)                                                             │
cypress |   │ Specs:      1 found (spec.js)                                                                  │
cypress |   └────────────────────────────────────────────────────────────────────────────────────────────────┘
cypress | 
cypress | 
cypress | ────────────────────────────────────────────────────────────────────────────────────────────────────
cypress |                                                                                                     
cypress |   Running:  spec.js                                                                         (1 of 1)
apache | 172.18.0.3 - - [09/Sep/2020:14:44:15 +0000] "GET / HTTP/1.1" 200 27
cypress | 
cypress |   (Results)
cypress | 
cypress |   ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
cypress |   │ Tests:        1                                                                                │
cypress |   │ Passing:      1                                                                                │
cypress |   │ Failing:      0                                                                                │
cypress |   │ Pending:      0                                                                                │
cypress |   │ Skipped:      0                                                                                │
cypress |   │ Screenshots:  0                                                                                │
cypress |   │ Video:        true                                                                             │
cypress |   │ Duration:     0 seconds                                                                        │
cypress |   │ Spec Ran:     spec.js                                                                          │
cypress |   └────────────────────────────────────────────────────────────────────────────────────────────────┘
cypress | 
cypress | 
cypress |   (Video)
cypress | 
cypress |   -  Started processing:  Compressing to 32 CRF                                                     
cypress |   -  Finished processing: /app/cypress/videos/spec.js.mp4                                 (1 second)
cypress | 
cypress | 
cypress | ====================================================================================================
cypress | 
cypress |   (Run Finished)
cypress | 
cypress | 
cypress |        Spec                                              Tests  Passing  Failing  Pending  Skipped  
cypress |   ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
cypress |   │ ✔  spec.js                                  342ms        1        1        -        -        - │
cypress |   └────────────────────────────────────────────────────────────────────────────────────────────────┘
cypress |     ✔  All specs passed!                        342ms        1        1        -        -        -  
cypress | 
cypress exited with code 0
Aborting on container exit...
Stopping apache  ... done

Happy thing part 1

It will take a video without permission and leave it.

Video storage location


$ ls -l e2e/cypress/videos/spec.js.mp4
-rw-r--r-- 1 root root 29169 Sep  9 14:44 e2e/cypress/videos/spec.js.mp4

When viewing videos and images on AWS Cloud9, you can view them by focusing the mouse cursor on the file as shown below, right-clicking and selecting "Preview". スクリーンショット 2020-09-10 22.30.15.png スクリーンショット 2020-09-10 22.35.23.png

[E2e / cypress / videos / spec.js.mp4 converted to gif]

spec.js.gif

Happy thing part 2

With Selenium, you may insert code like page.save_screenshot'failed.png' where it might fail and leave a screenshot. By default, Cypress will leave a still image when the test fails without any special work. The video will remain, so you can check it from there, but it's here! Still images are easier to understand. The following is an example of failing to change the verification that the expected result "Hi there" is displayed to "Hey there".

$ git diff
diff --git a/e2e/cypress/integration/spec.js b/e2e/cypress/integration/spec.js
index 5e90f2d..809de14 100644
--- a/e2e/cypress/integration/spec.js
+++ b/e2e/cypress/integration/spec.js
@@ -1,4 +1,4 @@
 it('loads page', () => {
   cy.visit('/')
-  cy.contains('Hi there')
+  cy.contains('Hey there')
 })
$ npm run up

> [email protected] up /home/ec2-user/environment/cypress-example-docker-compose
> docker-compose up --abort-on-container-exit --exit-code-from e2e

Starting apache ... done
Starting cypress ... done
Attaching to apache, cypress
apache | [Wed Sep 09 14:55:49.753051 2020] [mpm_event:notice] [pid 1:tid 140399861245056] AH00489: Apache/2.4.46 (Unix) configured -- resuming normal operations
apache | [Wed Sep 09 14:55:49.753761 2020] [core:notice] [pid 1:tid 140399861245056] AH00094: Command line: 'httpd -D FOREGROUND'
cypress | 
cypress | ====================================================================================================
cypress | 
cypress |   (Run Starting)
cypress | 
cypress |   ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
cypress |   │ Cypress:    5.1.0                                                                              │
cypress |   │ Browser:    Electron 83 (headless)                                                             │
cypress |   │ Specs:      1 found (spec.js)                                                                  │
cypress |   └────────────────────────────────────────────────────────────────────────────────────────────────┘
cypress | 
cypress | 
cypress | ────────────────────────────────────────────────────────────────────────────────────────────────────
cypress |                                                                                                     
cypress |   Running:  spec.js                                                                         (1 of 1)
apache | 172.18.0.3 - - [09/Sep/2020:14:55:59 +0000] "GET / HTTP/1.1" 200 27
cypress | 
cypress |   (Results)
cypress | 
cypress |   ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
cypress |   │ Tests:        1                                                                                │
cypress |   │ Passing:      0                                                                                │
cypress |   │ Failing:      1                                                                                │
cypress |   │ Pending:      0                                                                                │
cypress |   │ Skipped:      0                                                                                │
cypress |   │ Screenshots:  1                                                                                │
cypress |   │ Video:        true                                                                             │
cypress |   │ Duration:     4 seconds                                                                        │
cypress |   │ Spec Ran:     spec.js                                                                          │
cypress |   └────────────────────────────────────────────────────────────────────────────────────────────────┘
cypress | 
cypress | 
cypress |   (Screenshots)
cypress | 
cypress |   -  /app/cypress/screenshots/spec.js/loads page (failed).png                             (1280x720)
cypress | 
cypress | 
cypress |   (Video)
cypress | 
cypress |   -  Started processing:  Compressing to 32 CRF                                                     
cypress |   -  Finished processing: /app/cypress/videos/spec.js.mp4                                (2 seconds)
cypress | 
cypress | 
cypress | ====================================================================================================
cypress | 
cypress |   (Run Finished)
cypress | 
cypress | 
cypress |        Spec                                              Tests  Passing  Failing  Pending  Skipped  
cypress |   ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
cypress |   │ ✖  spec.js                                  00:04        1        -        1        -        - │
cypress |   └────────────────────────────────────────────────────────────────────────────────────────────────┘
cypress |     ✖  1 of 1 failed (100%)                     00:04        1        -        1        -        -  
cypress | 
cypress exited with code 1

Storage location for still images taken when the test fails


$ ls -l e2e/cypress/screenshots/spec.js/loads\ page\ \(failed\).png 
-rw-r--r-- 1 root root 29790 Sep  9 14:56 e2e/cypress/screenshots/spec.js/loads page (failed).png

[e2e/cypress/screenshots/spec.js/loads\ page\ (failed).png ] You can see that it is "Hey there" even though you are expecting "Hi there"! スクリーンショット 2020-09-10 0.05.33.png

Workshop-Let's develop while writing tests-

Test-Driven Development (TDD)

Test-Driven Development (TDD) is a test-first program development method. In other words, it refers to a method of writing test code before implementing a program and proceeding with implementation and refactoring to match the test code. Test-driven development proceeds in the order of "Red", "Green", and "Refactoring". Test-driven development has the following advantages:

Red

Green

Refactoring

Since the test code is written before the program of the function you want to implement, the test fails at first, but the program is implemented and modified many times in a short cycle to eliminate bugs, and refactoring when you can write code that works correctly to hold.

From here, feel free to think about the functional requirements and proceed with test implementation and application implementation. Here are some examples of requirements. Please refer to it and decide on the original specifications before proceeding. Use HTML or CSS to implement the web page. When writing HTML, please note that if you forget to write <meta charset =" utf-8 "/>, Japanese characters will be garbled. For CSS, see [Note] CSS Reintroduction-Selector-.

[Requirement example]

Create a self-introduction page. The self-introduction includes:

[Image of the page to be implemented]

スクリーンショット 2020-09-12 23.16.10.png

1. Write a test to ensure that it meets the requirements of the implemented functionality

For those who ask, "How should I write a test for this?", I also made a command collection, so please refer to it. Cypress command collection that I want to keep this in mind

e2e/cypress/integration/spec.js


it('load page', () => {
  cy.visit('/')
  cy.get('.title').should('have.text', 'Rusty Nail room')
  cy.get('.summary').should('have.text', 'This is Rusty Nail's self-introduction page.')
  cy.get('.content__name').should('have.text', 'Rusty Nail')
  cy.get('.content__age').should('have.text', '34 years old')
  cy.get('.content__hobby').should('have.text', 'Table tennis')
})

2. Run the test (Red: fails)

Run $ npm run up to run the test. Of course it fails because I haven't written any tests yet.

3. Implement the application

webapp/index.html


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Rusty Nail room</title>
</head>
<body>
  <h1 class='title'>Rusty Nail room</h1>
  <h2 class='summary'>This is Rusty Nail's self-introduction page.</h2>

  <ul class='content'>
    <li class='content__name'>Rusty Nail</li>
    <li class='content__age'>34 years old</li>
    <li class='content__hobby'>Table tennis</li>
  </ul>
</body>
</html>

Run $ npm run build to update the changes in webapp / index.html to the Docker image.

4. Run the test (Green: succeed)

Run $ npm run up again to run the test. The test is successful because the application that meets the requirements has also been implemented.

With this kind of feeling, if you want to add "special skills", "favorite food", etc. next time, Write one test and repeat the implementation one.

bonus

In the workshop, we used the default browser, but you can specify it if various browsers are installed.

used(Default)browser


cypress |   ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
cypress |   │ Cypress:    5.1.0                                                                              │
cypress |   │ Browser:    Electron 83 (headless)                                                             │
cypress |   │ Specs:      1 found (spec.js)                                                                  │
cypress |   └────────────────────────────────────────────────────────────────────────────────────────────────┘

In the workshop, browser installation etc. will be omitted because it will prepare the Docker environment.

Specify the browser to execute

You can specify the browser as follows. For details on supported browsers, see the official Launching Browsers.

Example of specifying chrome


$ cypress run --browser chrome

Designation of headless

If you want to run headless, specify the --headless option as follows:

$ cypress run --browser chrome --headless

Recommended Posts

[Workshop for beginners] Let's write an E2E test with Cloud9 + Docker Compose + Cypress!
Create an E2E test environment with Docker x Cypress
Environment construction with Docker for beginners
[Now] Let's Redmine with Docker Compose with Let's Encrypt
Building an environment for WordPress, MySQL and phpMyAdmin with Docker Compose on EC2
[Swift / For beginners] Write smartly with type inference
Build an E2E test environment with Selenium (Java)
Build an Android image for Orange Pi 4 with Docker
Let's write how to make API with SpringBoot + Docker from 0