Skip to content

Commit c867f0d

Browse files
authored
fixes #35, add angular + api testing recipes (#36)
* fixes #35, add angular + api testing recipes * start json server properly
1 parent 360e3b2 commit c867f0d

File tree

19 files changed

+435
-0
lines changed

19 files changed

+435
-0
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Recipe | Category | Description
1717
[Drag and Drop](#drag-and-drop) | Testing the DOM | Use `.trigger()` to test drag and drop
1818
[Typescript with Browserify](#typescript-with-browserify) | Preprocessors | Add typescript support with browserify
1919
[Typescript with Webpack](#typescript-with-webpack) | Preprocessors | Add typescript support with webpack
20+
[Direct Control of AngularJS](#direct-control-of-angularjs) | Blogs | Bypass the DOM and control AngularJS
21+
[E2E API Testing](#e2e-api-testing) | Blogs | Run your API Tests with a GUI
2022
[Stubbing Functions](#stubbing-functions) | Stubbing, Spying | Use `cy.stub()` to test function calls
2123
[Stubbing `window.fetch`](#stubbing-windowfetch) | Stubbing, Spying | Use `cy.stub()` to control fetch requests
2224
[Application Code](#application-code) | Unit Testing | Import and test your own application code
@@ -160,6 +162,19 @@ Get around the lack of a `.hover()` command.
160162

161163
- Use [`@cypress/webpack-preprocessor`](https://github.com/cypress-io/cypress-webpack-preprocessor) to write Cypress tests in Typescript
162164

165+
### [Direct Control of AngularJS](./examples/blogs__direct-control-angular)
166+
167+
- [Blog article written here](https://www.cypress.io/blog/2017/11/15/Control-Angular-Application-From-E2E-Tests)
168+
- Programmatically control AngularJS
169+
- Bypass the DOM, update scopes directly
170+
- Create custom command for controlling services
171+
172+
### [E2E API Testing](./examples/blogs__e2e-api-testing)
173+
174+
- [Blog article written here](https://www.cypress.io/blog/2017/11/07/Add-GUI-to-Your-E2E-API-Tests)
175+
- Use `cy.request()` to perform API Testing
176+
- Use the Cypress GUI to help debug requests
177+
163178
### [Stubbing Functions](./examples/stubbing-spying__functions)
164179

165180
- Use [`cy.stub()`](https://on.cypress.io/stub) to stub dependencies in a unit test.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Direct Control AngularJS
2+
3+
This is an example showing direct control of Angular 1.x application from Cypress.
4+
5+
## Blog Post
6+
7+
We wrote a [companion blog post](https://www.cypress.io/blog/2017/11/15/Control-Angular-Application-From-E2E-Tests) that provides more details and explanation of this recipe.
8+
9+
Please read that if you'd like to understand more about programmatically controlling your Angular applications.
10+
11+
![E2E tests closely controlling Angular application](img/angular-console.png)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"baseUrl": "http://todomvc.com/examples/angularjs"
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "Using fixtures to represent data",
3+
"email": "[email protected]",
4+
"body": "Fixtures are a great way to mock data for responses to routes"
5+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
describe('Angular TodoMVC', () => {
2+
beforeEach(() => {
3+
cy.visit('/')
4+
})
5+
6+
it('loads page', () => {
7+
cy.title()
8+
.should('include', 'AngularJS')
9+
.and('include', 'TodoMVC')
10+
})
11+
12+
it('adds a todo', () => {
13+
cy.focused()
14+
.type('new todo')
15+
.type('{enter}')
16+
cy.get('#todo-list')
17+
.find('li')
18+
.should('have.length', 1)
19+
.first()
20+
.contains('new todo')
21+
})
22+
23+
it('can get angular library', () => {
24+
cy.window()
25+
.then(win => {
26+
console.log('got app window object', win)
27+
return win
28+
})
29+
.its('angular')
30+
.then(ng => {
31+
console.log('got angular object', ng.version)
32+
})
33+
})
34+
35+
const getAngular = () =>
36+
cy.window()
37+
.its('angular')
38+
39+
const getFirstTodoElement = () =>
40+
cy.get('#todo-list')
41+
.find('li')
42+
.first()
43+
44+
const getElementScope = (selector) =>
45+
cy.get(selector)
46+
.then($el =>
47+
getAngular()
48+
.then(ng => ng.element($el).scope())
49+
)
50+
51+
const getElementInjector = (selector) =>
52+
cy.get(selector)
53+
.then($el =>
54+
getAngular()
55+
.then(ng => ng.element($el).injector())
56+
)
57+
58+
const addTodo = (text) =>
59+
cy.focused()
60+
.type(text)
61+
.type('{enter}')
62+
63+
it('has todo object in scope', () => {
64+
cy.focused()
65+
.type('new todo')
66+
.type('{enter}')
67+
68+
getElementScope('#todo-list li:first')
69+
.its('todo')
70+
.should('deep.equal', {
71+
title: 'new todo',
72+
completed: false
73+
})
74+
})
75+
76+
it('can get todo', () => {
77+
const title = 'learn E2E testing with Cypress'
78+
addTodo(title)
79+
80+
getElementScope('#todo-list li:first')
81+
.its('todo')
82+
.should('deep.equal', {
83+
title,
84+
completed: false
85+
})
86+
})
87+
88+
it('can change todo via Angular scope', () => {
89+
const title = 'learn E2E testing with Cypress'
90+
addTodo(title)
91+
92+
getElementScope('#todo-list li:first')
93+
.its('todo')
94+
.then(todo => {
95+
todo.completed = true
96+
})
97+
98+
getElementScope('#todo-list li:first')
99+
.then(scope => {
100+
// must run digest cycle so Angular
101+
// updates DOM
102+
scope.$apply()
103+
})
104+
105+
// check UI - it should have been updated
106+
cy.get('#todo-list li:first')
107+
.find('input.toggle')
108+
.should('be.checked')
109+
})
110+
111+
it('set several todos at once', () => {
112+
// home app handles missing "completed" property
113+
const todos = [{
114+
title: 'first todo'
115+
}, {
116+
title: 'second todo'
117+
}, {
118+
title: 'third todo'
119+
}]
120+
121+
getElementScope('#todo-list')
122+
.then(scope => {
123+
scope.todos = todos
124+
scope.$apply()
125+
})
126+
127+
// we should have 3 elements in the list
128+
cy.get('#todo-list li')
129+
.should('have.length', 3)
130+
})
131+
132+
it('shows completed items', () => {
133+
// home app handles missing "completed" property
134+
const todos = [{
135+
title: 'first todo'
136+
}, {
137+
title: 'second todo',
138+
completed: true
139+
}, {
140+
title: 'third todo'
141+
}]
142+
143+
getElementScope('#todo-list')
144+
.then(scope => {
145+
scope.todos = todos
146+
scope.$apply()
147+
})
148+
149+
getElementInjector('#todo-list')
150+
.then(injector => {
151+
const store = injector.get('localStorage')
152+
todos.forEach(t => store.insert(t))
153+
})
154+
155+
cy.get('#filters')
156+
.contains('Completed')
157+
.click()
158+
159+
cy.get('#todo-list li')
160+
.should('have.length', 1)
161+
})
162+
163+
it('sets todo using addTodo scope method', () => {
164+
getElementScope('#todo-list')
165+
.then(scope => {
166+
scope.newTodo = 'this is a todo'
167+
scope.addTodo()
168+
scope.$apply()
169+
})
170+
171+
cy.get('#todo-list li')
172+
.should('have.length', 1)
173+
})
174+
})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// ***********************************************
2+
// This example commands.js shows you how to
3+
// create various custom commands and overwrite
4+
// existing commands.
5+
//
6+
// For more comprehensive examples of custom
7+
// commands please read more here:
8+
// https://on.cypress.io/custom-commands
9+
// ***********************************************
10+
//
11+
//
12+
// -- This is a parent command --
13+
// Cypress.Commands.add("login", (email, password) => { ... })
14+
//
15+
//
16+
// -- This is a child command --
17+
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18+
//
19+
//
20+
// -- This is a dual command --
21+
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22+
//
23+
//
24+
// -- This is will overwrite an existing command --
25+
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// ***********************************************************
2+
// This example support/index.js is processed and
3+
// loaded automatically before your test files.
4+
//
5+
// This is a great place to put global configuration and
6+
// behavior that modifies Cypress.
7+
//
8+
// You can change the location of this file or turn off
9+
// automatically serving support files with the
10+
// 'supportFile' configuration option.
11+
//
12+
// You can read more here:
13+
// https://on.cypress.io/configuration
14+
// ***********************************************************
15+
16+
// Import commands.js using ES2015 syntax:
17+
import './commands'
18+
19+
// Alternatively you can use CommonJS syntax:
20+
// require('./commands')
Loading
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "cypress-example-angular",
3+
"version": "1.0.0",
4+
"description": "Example showing direct control of Angular 1.x application from E2E tests",
5+
"scripts": {
6+
"cypress:run": "../../node_modules/.bin/cypress run",
7+
"cypress:open": "../../node_modules/.bin/cypress open"
8+
}
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# E2E API Testing
2+
3+
This is an example of using Cypress to test REST APIs.
4+
5+
## Blog Post
6+
7+
We wrote a [companion blog post](https://www.cypress.io/blog/2017/11/07/Add-GUI-to-Your-E2E-API-Tests) that provides more details and explanation of this recipe.
8+
9+
Please read that if you'd like to understand more about programmatically controlling your Angular applications.
10+
11+
![API testing using Cypress](img/demo.png)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"baseUrl": "http://localhost:7081"
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "Using fixtures to represent data",
3+
"email": "[email protected]",
4+
"body": "Fixtures are a great way to mock data for responses to routes"
5+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// @ts-check
2+
/* eslint-env mocha */
3+
describe('todos API', () => {
4+
/**
5+
* @typedef {Object} Todo
6+
* @property {number} id
7+
* @property {string} task
8+
*/
9+
10+
/** @type {Todo[]} */
11+
const initialItems = [
12+
{
13+
"id": 1,
14+
"task": "read something"
15+
},
16+
{
17+
"id": 2,
18+
"task": "write something"
19+
}
20+
]
21+
22+
const getItems = () =>
23+
cy.request('/todos')
24+
.its('body')
25+
26+
/** @type {(todo:Todo) => Cypress.Chainable} */
27+
const add = item =>
28+
cy.request('POST', '/todos', item)
29+
30+
const deleteItem = item =>
31+
cy.request('DELETE', `/todos/${item.id}`)
32+
33+
const deleteAll = () =>
34+
getItems()
35+
.each(deleteItem)
36+
37+
const reset = () => {
38+
deleteAll()
39+
initialItems.forEach(add)
40+
}
41+
42+
beforeEach(reset)
43+
afterEach(reset)
44+
45+
it('returns JSON', () => {
46+
cy.request('/todos')
47+
.its('headers')
48+
.its('content-type')
49+
.should('include', 'application/json')
50+
})
51+
52+
it('loads 2 items', () => {
53+
cy.request('/todos')
54+
.its('body')
55+
.should('have.length', 2)
56+
})
57+
58+
it('loads the initial items', () => {
59+
getItems()
60+
.should('deep.eq', initialItems)
61+
})
62+
63+
it('returns id + task objects', () => {
64+
getItems()
65+
.each(value =>
66+
expect(value).to.have.all.keys('id', 'task')
67+
)
68+
})
69+
70+
it('adds an item', () => {
71+
const randomId = Cypress._.random(0, 10000)
72+
const item = {id:randomId, task:'life'}
73+
74+
add(item)
75+
cy.request(`/todos/${randomId}`)
76+
.its('body')
77+
.should('deep.eq', item)
78+
})
79+
80+
it('deletes an item', () => {
81+
const id = initialItems[0].id
82+
cy.request('DELETE', `/todos/${id}`)
83+
getItems()
84+
.should('have.length', 1)
85+
})
86+
})

0 commit comments

Comments
 (0)