Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
bahmutov committed Jul 12, 2021
0 parents commit cf212dd
Show file tree
Hide file tree
Showing 196 changed files with 38,297 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
node_modules/
*/cypress/videos
*/cypress/screenshots
cypress/videos
cypress/screenshots
cypress/results
mochawesome-report/
mochawesome.json
cypress/logs
dist
.nyc_output
coverage
npm-debug.log
todomvc-redux/.cache
posted.json
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package-lock=true
save-exact=true
6 changes: 6 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
143 changes: 143 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# cypress-workshop-basics
> Basics of end-to-end testing with [Cypress.io](https://www.cypress.io) test runner
## Requirements

- Any computer: Mac, Windows, Linux
- [Node 12.0.0+ (LTS)](https://nodejs.org/)
- [git](https://git-scm.com)

In order to get the code and install dependencies

```bash
git clone [email protected]:bahmutov/cypress-workshop-basics.git
cd cypress-workshop-basics
npm install
```

If necessary, install dependencies inside TodoMVC folder

```bash
cd todomvc
npm install
```

### Quick check ✅

You can test the installation by starting TodoMVC in the first terminal window

```shell
npm start
```

and you should see in the terminal

```text
> json-server --static . data.json --middlewares ./node_modules/json-server-reset
\{^_^}/ hi!
Loading data.json
Loading ./node_modules/json-server-reset
Done
Resources
http://localhost:3000/todos
Home
http://localhost:3000
```

From the second terminal window you should be able to open Cypress in the root of the project with

```bash
$ npm run cy:open

> [email protected] cy:open /git/cypress-workshop-basics
> cypress open
```

### Tip

You can use the installed [start-server-and-test](https://github.com/bahmutov/start-server-and-test) utility to start the app, open Cypress and then shutdown the app when you exit Cypress.

```bash
$ npm run dev
```

## Application

[Vue.js](https://vuejs.org/) + [Vuex](https://vuex.vuejs.org/) + REST server application that we are going to test is in the folder `todomvc`. This application and its full testing is described in [this blog post](https://www.cypress.io/blog/2017/11/28/testing-vue-web-application-with-vuex-data-store-and-rest-backend/). The application should run fine without network access.

## Slides

## Content

### Beginner

| | topic | folder | contents | slides |
| --- | -------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------ |
| [🔗](#intro) | Introduction, TodoMVC application | [todomvc](todomvc) | [intro.md](slides/intro/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=intro)
| [🔗](#start) | Loading page | [00-start](00-start) | [00-start](slides/00-start/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=00-start)
| [🔗](#basic) | `cypress open` vs `cypress run` | [01-basic](cypress/integration/01-basic) | [01-basic](slides/01-basic/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=01-basic)
| [🔗](#adding-items) | Adding items test, `cypress.json` file | [02-adding-items](cypress/integration/02-adding-items) | [02-adding-items](slides/02-adding-items/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=02-adding-items)
| [🔗](#selector-playground) | Selector Playground | [03-selector-playground](cypress/integration/03-selector-playground) | [03-selector-playground](slides/03-selector-playground/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=03-selector-playground)
| [🔗](#reset-state) | Reset database using `cy.request` | [04-reset-state](cypress/integration/04-reset-state) | [04-reset-state](slides/04-reset-state/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=04-reset-state)
| [🔗](#xhr) | Spy and stub XHR requests, fixtures | [05-xhr](cypress/integration/05-xhr) | [05-xhr](slides/05-xhr/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=05-xhr)
| [🔗](#app-data-store) | Access application code and data | [06-app-data-store](cypress/integration/06-app-data-store) | [06-app-data-store](slides/06-app-data-store/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=06-app-data-store)

### Intermediate
| | topic | folder | contents | slides |
| --- | -------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------ |
| [🔗](#ci) | Setting up E2E tests on CI | [07-ci](cypress/integration/07-ci) | [07-ci](slides/07-ci/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=07-ci)
| [🔗](#dashboard) | Setting up Cypress Dashboard | [07-ci](cypress/integration/07-ci) | [08-dashboard](slides/08-dashboard/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=08-dashboard)
| [🔗](#reporters) | Test reporters | - | [09-reporters](slides/09-reporters/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=09-reporters)
| [🔗](#configuration) | Configuration | - | [10-configuration](slides/10-configuration/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=10-configuration)
| [🔗](#retry-ability) | Retry-ability | [11-retry-ability](cypress/integration/11-retry-ability) | [11-retry-ability](slides/11-retry-ability/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=11-retry-ability)
| [🔗](#custom-commands) | Custom commands | [12-custom-commands](cypress/integration/12-custom-commands) | [12-custom-commands](slides/12-custom-commands/PITCHME.md)| [link](https://testing-workshop-cypress.netlify.app?p=12-custom-commands)

### Advanced
| | topic | folder | contents | slides |
| --- | -------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------ |
| [🔗](#app-actions) | Page Objects vs App Actions | [13-app-actions](cypress/integration/13-app-actions) | [13-app-actions](slides/13-app-actions/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=13-app-actions)
| [🔗](#fixtures) | Fixtures | [14-fixtures](cypress/integration/14-fixtures) | [14-fixtures](slides/14-fixtures/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=14-fixtures)
| [🔗](#debugging) | Debugging | [02-adding-items/demo.js](cypress/integration/02-adding-items/demo.js) | [15-debugging](slides/15-debugging/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=15-debugging)
| [🔗](#preprocessors) | Preprocessors | [16-preprocessors](cypress/integration/16-preprocessors) | [16-preprocessors](slides/16-preprocessors/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=16-preprocessors)
| [🔗](#component-testing) | Component testing | [17-component-testing](cypress/integration/17-component-testing) | [17-component-testing](slides/17-component-testing/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=17-component-testing)
| [🔗](#backend) | Backend code | [18-backend](cypress/integration/18-backend) | [18-backend](slides/18-backend/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=18-backend)
| [🔗](#code-coverage) | Code coverage | [19-code-coverage](cypress/integration/19-code-coverage) | [19-code-coverage](slides/19-code-coverage/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=19-code-coverage)
| [🔗](#stubbing-methods) | Stubbing methods | [20-stubbing](./cypress/integration/20-stubbing) | [20-stubbing](./slides/20-stubbing/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=20-stubbing)
| | The end | - | [end](slides/end/PITCHME.md) | [link](https://testing-workshop-cypress.netlify.app?p=end)

## For speakers 🎙

[![Netlify Status](https://api.netlify.com/api/v1/badges/de48e52e-e2ee-4092-a626-ab4fa358e441/deploy-status)](https://app.netlify.com/sites/testing-workshop-cypress/deploys)

This workshop can take all day, but you can pick the sections you are interested in teaching at will and customize it into any time duration. Everyone is coding for the most part, except for CI and the Cypress Dashboard sections, where the usage was shown via slides and actual sites.

During the workshop, keep the `todomvc` app running in one shell, while each section `01-basic`, `02-...`, `03-...` etc. has its own Cypress and specs subfolders `cypress/integration/...`. Usually a spec has several tests with placeholder comments. The workshop attendees are expected to make the tests pass using the knowledge from the slides and hints (and [Cypress documentation](https://docs.cypress.io/)). Note that most folders have a prepared `spec.js` file and an `answer.js` file. The `answer.js` file is ignored by Cypress using a setting in `cypress.json`.

The only exception is the folder `00-start`. This is a folder for students to see how Cypress scaffolds example specs when you open Cypress for the very first time. In this folder students should execute...

```
cd 00-start
npm run cy:open
```

...and see the list of created example specs.

The slides are generated using Reveal.js from Markdown sources in the [slides](slides) folder. You can show the slides locally by running

```shell
npm run slides:dev
```

## Additional information

- https://www.cypress.io/
- https://docs.cypress.io/
- https://glebbahmutov.com/cypress-examples/

[renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg
[renovate-app]: https://renovateapp.com/
19 changes: 19 additions & 0 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"viewportWidth": 600,
"viewportHeight": 800,
"ignoreTestFiles": [
"**/answer/*",
"*answer.js",
"*.d.ts",
"**/__snapshots__/*",
"**/__image_snapshots__/*",
"**/13-app-actions/todo-page-object.js",
"**/13-app-actions/utils.js",
"**/17-component-testing/filters.js",
"**/17-component-testing/*.jsx"
],
"baseUrl": "http://localhost:3000",
"env": {},
"$schema": "https://on.cypress.io/cypress.schema.json",
"experimentalStudio": true
}
1 change: 1 addition & 0 deletions cypress/fixtures/empty-list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
5 changes: 5 additions & 0 deletions cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
17 changes: 17 additions & 0 deletions cypress/fixtures/three-items.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[
{
"title": "first item from fixture",
"completed": false,
"id": 1
},
{
"title": "second item from fixture",
"completed": true,
"id": 2
},
{
"title": "third item",
"completed": false,
"id": 3
}
]
12 changes: 12 additions & 0 deletions cypress/fixtures/two-items.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"title": "first item from fixture",
"completed": false,
"id": 1
},
{
"title": "second item from fixture",
"completed": true,
"id": 2
}
]
21 changes: 21 additions & 0 deletions cypress/integration/01-basic/answer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference types="cypress" />
// @ts-check
it('loads', () => {
cy.visit('localhost:3000')

// passing assertions
// https://on.cypress.io/get
cy.get('.new-todo').get('footer')

// https://on.cypress.io/get
// use ("selector", "text") arguments to "cy.contains"
cy.contains('h1', 'todos')

// or can use regular expression
cy.contains('h1', /^todos$/)

// also good practice is to use data attributes specifically for testing
// see https://on.cypress.io/best-practices#Selecting-Elements
// which play well with "Selector Playground" tool
cy.contains('[data-cy=app-title]', 'todos')
})
23 changes: 23 additions & 0 deletions cypress/integration/01-basic/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference types="cypress" />
// @ts-check
it('loads', () => {
// application should be running at port 3000
cy.visit('localhost:3000')

// passing assertions
// https://on.cypress.io/get
cy.get('.new-todo').get('footer')

// this assertion fails on purpose
// can you fix it?
// https://on.cypress.io/get
cy.contains('h1', 'Todos App')

// can you write "cy.contains" using regular expression?
// cy.contains('h1', /.../)

// also good practice is to use data attributes specifically for testing
// see https://on.cypress.io/best-practices#Selecting-Elements
// which play well with "Selector Playground" tool
// how would you do select this element?
})
98 changes: 98 additions & 0 deletions cypress/integration/02-adding-items/answer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/// <reference types="cypress" />
// IMPORTANT ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
// remember to manually delete all items before running the test
// IMPORTANT ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️

beforeEach(() => {
cy.visit('localhost:3000')
})

it('loads', () => {
cy.contains('h1', 'todos')
})

it('adds two items', () => {
cy.get('.new-todo').type('first item{enter}')
cy.contains('li.todo', 'first item').should('be.visible')
cy.get('.new-todo').type('second item{enter}')
cy.contains('li.todo', 'second item').should('be.visible')
})

it('can mark an item as completed', () => {
// adds a few items
addItem('simple')
addItem('hard')

// marks the first item as completed
cy.contains('li.todo', 'simple').should('exist').find('.toggle').check()

// confirms the first item has the expected completed class
cy.contains('li.todo', 'simple').should('have.class', 'completed')
// confirms the other items are still incomplete
cy.contains('li.todo', 'hard').should('not.have.class', 'completed')
})

it('can delete an item', () => {
// adds a few items
addItem('simple')
addItem('hard')
// deletes the first item
cy.contains('li.todo', 'simple')
.should('exist')
.find('.destroy')
// use force: true because we don't wsnt to hover
.click({ force: true })

// confirm the deleted item is gone from the dom
cy.contains('li.todo', 'simple').should('not.exist')
// confirm the other item still exists
cy.contains('li.todo', 'hard').should('exist')
})

/**
* Adds a todo item
* @param {string} text
*/
const addItem = (text) => {
cy.get('.new-todo').type(`${text}{enter}`)
}

it('can add many items', () => {
// assumes there are no items at the beginning

const N = 5
for (let k = 0; k < N; k += 1) {
addItem(`item ${k}`)
}
// check number of items
cy.get('li.todo').should('have.length', 5)
})

it('adds item with random text', () => {
const randomLabel = `Item ${Math.random().toString().slice(2, 14)}`

addItem(randomLabel)
cy.contains('li.todo', randomLabel)
.should('be.visible')
.and('not.have.class', 'completed')
})

it('starts with zero items', () => {
cy.get('li.todo').should('have.length', 0)
})

it('does not allow adding blank todos', () => {
cy.on('uncaught:exception', (e) => {
// what will happen if this assertion fails?
// will the test fail?
// expect(e.message).to.include('Cannot add a blank todo')
// return false

// a better shortcut
return !e.message.includes('Cannot add a blank todo')
})
addItem(' ')
})

// what a challenge?
// test more UI at http://todomvc.com/examples/vue/
Loading

0 comments on commit cf212dd

Please sign in to comment.