diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000..d15cc11 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,49 @@ +name: e2e-tests + +on: + push: + branches: [master] + pull_request: + +env: + NODE_VERSION: 16.15.1 + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + cache: yarn + + - name: install dependencies + run: yarn install --frozen-lockfile + + - name: run app in Docker + run: | + docker run \ + --interactive --tty --rm --detach \ + --name ultimate-react-hook-form-form \ + --publish 3000:3000 \ + --publish 4000:4000 \ + --volume "$PWD":/app \ + --workdir /app \ + node:${{ env.NODE_VERSION }}-alpine3.15 \ + yarn dev + + - name: run Cypress tests + uses: cypress-io/github-action@v4 + with: + install: false + wait-on: 'http://localhost:3000' + browser: chrome + + - name: upload video captures + uses: actions/upload-artifact@v3 + if: always() + with: + name: cypress-videos + path: cypress/videos diff --git a/.gitignore b/.gitignore index c1df923..9a841da 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +# cypress +cypress/downloads +cypress/e2e/examples +cypress/videos diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..dac255d --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v16.15.1 diff --git a/cypress.config.js b/cypress.config.js new file mode 100644 index 0000000..8de012b --- /dev/null +++ b/cypress.config.js @@ -0,0 +1,11 @@ +const { defineConfig } = require("cypress"); + +module.exports = defineConfig({ + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + }, + excludeSpecPattern: 'cypress/e2e/examples/**', + baseUrl: 'http://localhost:3000/', + }, +}); diff --git a/cypress/e2e/default.cy.js b/cypress/e2e/default.cy.js new file mode 100644 index 0000000..79cfa23 --- /dev/null +++ b/cypress/e2e/default.cy.js @@ -0,0 +1,93 @@ +describe('ultimate form', () => { + it('submits successfully with phone number', () => { + cy.visit('/') + + // STEP 1 + cy.get('h2').should('contain', 'Step 1') + + cy.get('#firstName-helper-text').should('not.exist') + cy.get('#firstName').focus().blur() + cy.get('#firstName-helper-text').should('exist') + + cy.get('#lastName-helper-text').should('not.exist') + cy.get('#lastName').focus().blur() + cy.get('#lastName-helper-text').should('exist') + + // Submit shouldn't work. + cy.get('button').click() + cy.get('h2').should('contain', 'Step 1') + + cy.get('#firstName').type('Maria').blur() + cy.get('#firstName-helper-text').should('not.exist') + + // Submit shouldn't work. + cy.get('button').click() + cy.get('h2').should('contain', 'Step 1') + + cy.get('#lastName').type('Sanchez').blur() + cy.get('#lastName-helper-text').should('not.exist') + cy.get('button').click() + + // STEP 2 + cy.url().should('contain', '/step2') + cy.get('h2').should('contain', 'Step 2') + + cy.get('#email-helper-text').should('not.exist') + cy.get('#email').focus().blur() + cy.get('#email-helper-text').should('exist') + + cy.get('#email').type('maria@example.com').blur() + cy.get('#email-helper-text').should('not.exist') + + cy.get('#phoneNumber').should('not.exist') + cy.get('input[name=hasPhone]').check() + cy.get('#phoneNumber').should('exist') + cy.get('#phoneNumber').type('555-555-5555') + cy.get('button').click() + + // STEP 3 + cy.url().should('contain', '/step3') + cy.get('h2').should('contain', 'Step 3') + // Skip upload for now. + cy.get('button').click() + + // RESULTS + cy.url().should('contain', '/result') + cy.get('h2').should('contain', 'Form Values') + cy.get('table').within(() => { + cy.contains('td', 'Maria').should('exist') + cy.contains('td', 'Sanchez').should('exist') + cy.contains('td', 'maria@example.com').should('exist') + cy.contains('td', '555-555-5555').should('exist') + }) + cy.get('button').click() + + cy.get('[role=dialog]').within(() => { + cy.get('h2').should('contain', 'Great job!') + cy.contains('button', 'OK').click() + }) + }) + + it('submits successfully without phone number', () => { + // STEP 1 + cy.visit('/') + cy.get('#firstName').type('Maria') + cy.get('#lastName').type('Sanchez') + cy.get('button').click() + + // STEP 2 + cy.get('#email').type('maria@example.com') + cy.get('input[name=hasPhone]').uncheck() + cy.get('button').click() + + // STEP 3 (skip) + cy.get('button').click() + + // RESULTS + cy.get('table').within(() => { + cy.contains('td', 'Maria').should('exist') + cy.contains('td', 'Sanchez').should('exist') + cy.contains('td', 'maria@example.com').should('exist') + }) + }) +}) diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000..66ea16e --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) \ No newline at end of file diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js new file mode 100644 index 0000000..0e7290a --- /dev/null +++ b/cypress/support/e2e.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') \ No newline at end of file diff --git a/package.json b/package.json index 42a4ab1..f3b0070 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "devDependencies": { "concurrently": "^5.2.0", "cors": "^2.8.5", + "cypress": "^10.3.0", "express": "^4.17.1", "express-fileupload": "^1.1.7-alpha.3", "morgan": "^1.10.0" @@ -33,6 +34,8 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", + "e2e:run": "cypress run", + "e2e:open": "cypress open", "eject": "react-scripts eject", "start:server": "node ./server.js", "dev": "concurrently --kill-others \"npm run start:server\" \"npm run start\"" diff --git a/src/Step1.js b/src/Step1.js index a641264..3fb22b7 100644 --- a/src/Step1.js +++ b/src/Step1.js @@ -3,7 +3,7 @@ import { useHistory } from "react-router-dom"; import { useData } from "./DataContext"; import Typography from "@material-ui/core/Typography"; import { useForm } from "react-hook-form"; -import { yupResolver } from "@hookform/resolvers/yup"; +import { yupResolver } from "@hookform/resolvers"; import { PrimaryButton } from "./components/PrimaryButton"; import { MainContainer } from "./components/MainContainer"; import { Form } from "./components/Form"; @@ -42,7 +42,7 @@ export const Step1 = () => {