Skip to content

Commit dcf8b18

Browse files
committedJan 3, 2022
ADD: Cypress & Testing Library;
Installing and configuring Cypress and its dependencies
1 parent 514d45b commit dcf8b18

28 files changed

+2282
-3
lines changed
 

‎.eslintrc.json

+11-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
"env": {
33
"node": true,
44
"es2021": true,
5-
"browser": true
5+
"browser": true,
6+
"cypress/globals": true
67
},
78
"extends": [
89
"eslint:recommended",
910
"next/core-web-vitals",
1011
"plugin:react/recommended",
12+
"plugin:cypress/recommended",
1113
"plugin:@typescript-eslint/recommended",
1214
"plugin:prettier/recommended"
1315
],
@@ -19,7 +21,7 @@
1921
"sourceType": "module",
2022
"ecmaVersion": "latest"
2123
},
22-
"plugins": ["react", "@typescript-eslint"],
24+
"plugins": ["react", "cypress", "@typescript-eslint"],
2325
"rules": {
2426
"prettier/prettier": [
2527
"error",
@@ -30,7 +32,13 @@
3032
"usePrettierrc": true
3133
}
3234
],
33-
"react/react-in-jsx-scope": "off"
35+
"cypress/no-force": "warn",
36+
"cypress/no-pause": "error",
37+
"cypress/no-async-tests": "error",
38+
"react/react-in-jsx-scope": "off",
39+
"cypress/no-unnecessary-waiting": "error",
40+
"cypress/no-assigning-return-values": "error",
41+
"cypress/assertion-before-screenshot": "warn"
3442
},
3543
"settings": {
3644
"react": {

‎cypress.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

‎cypress/fixtures/example.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "Using fixtures to represent data",
3+
"email": "hello@cypress.io",
4+
"body": "Fixtures are a great way to mock data for responses to routes"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/// <reference types="cypress" />
2+
3+
// Welcome to Cypress!
4+
//
5+
// This spec file contains a variety of sample tests
6+
// for a todo list app that are designed to demonstrate
7+
// the power of writing tests in Cypress.
8+
//
9+
// To learn more about how Cypress works and
10+
// what makes it such an awesome testing tool,
11+
// please read our getting started guide:
12+
// https://on.cypress.io/introduction-to-cypress
13+
14+
describe('example to-do app', () => {
15+
beforeEach(() => {
16+
// Cypress starts out with a blank slate for each test
17+
// so we must tell it to visit our website with the `cy.visit()` command.
18+
// Since we want to visit the same URL at the start of all our tests,
19+
// we include it in our beforeEach function so that it runs before each test
20+
cy.visit('https://example.cypress.io/todo')
21+
})
22+
23+
it('displays two todo items by default', () => {
24+
// We use the `cy.get()` command to get all elements that match the selector.
25+
// Then, we use `should` to assert that there are two matched items,
26+
// which are the two default items.
27+
cy.get('.todo-list li').should('have.length', 2)
28+
29+
// We can go even further and check that the default todos each contain
30+
// the correct text. We use the `first` and `last` functions
31+
// to get just the first and last matched elements individually,
32+
// and then perform an assertion with `should`.
33+
cy.get('.todo-list li').first().should('have.text', 'Pay electric bill')
34+
cy.get('.todo-list li').last().should('have.text', 'Walk the dog')
35+
})
36+
37+
it('can add new todo items', () => {
38+
// We'll store our item text in a variable so we can reuse it
39+
const newItem = 'Feed the cat'
40+
41+
// Let's get the input element and use the `type` command to
42+
// input our new list item. After typing the content of our item,
43+
// we need to type the enter key as well in order to submit the input.
44+
// This input has a data-test attribute so we'll use that to select the
45+
// element in accordance with best practices:
46+
// https://on.cypress.io/selecting-elements
47+
cy.get('[data-test=new-todo]').type(`${newItem}{enter}`)
48+
49+
// Now that we've typed our new item, let's check that it actually was added to the list.
50+
// Since it's the newest item, it should exist as the last element in the list.
51+
// In addition, with the two default items, we should have a total of 3 elements in the list.
52+
// Since assertions yield the element that was asserted on,
53+
// we can chain both of these assertions together into a single statement.
54+
cy.get('.todo-list li')
55+
.should('have.length', 3)
56+
.last()
57+
.should('have.text', newItem)
58+
})
59+
60+
it('can check off an item as completed', () => {
61+
// In addition to using the `get` command to get an element by selector,
62+
// we can also use the `contains` command to get an element by its contents.
63+
// However, this will yield the <label>, which is lowest-level element that contains the text.
64+
// In order to check the item, we'll find the <input> element for this <label>
65+
// by traversing up the dom to the parent element. From there, we can `find`
66+
// the child checkbox <input> element and use the `check` command to check it.
67+
cy.contains('Pay electric bill')
68+
.parent()
69+
.find('input[type=checkbox]')
70+
.check()
71+
72+
// Now that we've checked the button, we can go ahead and make sure
73+
// that the list element is now marked as completed.
74+
// Again we'll use `contains` to find the <label> element and then use the `parents` command
75+
// to traverse multiple levels up the dom until we find the corresponding <li> element.
76+
// Once we get that element, we can assert that it has the completed class.
77+
cy.contains('Pay electric bill')
78+
.parents('li')
79+
.should('have.class', 'completed')
80+
})
81+
82+
context('with a checked task', () => {
83+
beforeEach(() => {
84+
// We'll take the command we used above to check off an element
85+
// Since we want to perform multiple tests that start with checking
86+
// one element, we put it in the beforeEach hook
87+
// so that it runs at the start of every test.
88+
cy.contains('Pay electric bill')
89+
.parent()
90+
.find('input[type=checkbox]')
91+
.check()
92+
})
93+
94+
it('can filter for uncompleted tasks', () => {
95+
// We'll click on the "active" button in order to
96+
// display only incomplete items
97+
cy.contains('Active').click()
98+
99+
// After filtering, we can assert that there is only the one
100+
// incomplete item in the list.
101+
cy.get('.todo-list li')
102+
.should('have.length', 1)
103+
.first()
104+
.should('have.text', 'Walk the dog')
105+
106+
// For good measure, let's also assert that the task we checked off
107+
// does not exist on the page.
108+
cy.contains('Pay electric bill').should('not.exist')
109+
})
110+
111+
it('can filter for completed tasks', () => {
112+
// We can perform similar steps as the test above to ensure
113+
// that only completed tasks are shown
114+
cy.contains('Completed').click()
115+
116+
cy.get('.todo-list li')
117+
.should('have.length', 1)
118+
.first()
119+
.should('have.text', 'Pay electric bill')
120+
121+
cy.contains('Walk the dog').should('not.exist')
122+
})
123+
124+
it('can delete all completed tasks', () => {
125+
// First, let's click the "Clear completed" button
126+
// `contains` is actually serving two purposes here.
127+
// First, it's ensuring that the button exists within the dom.
128+
// This button only appears when at least one task is checked
129+
// so this command is implicitly verifying that it does exist.
130+
// Second, it selects the button so we can click it.
131+
cy.contains('Clear completed').click()
132+
133+
// Then we can make sure that there is only one element
134+
// in the list and our element does not exist
135+
cy.get('.todo-list li')
136+
.should('have.length', 1)
137+
.should('not.have.text', 'Pay electric bill')
138+
139+
// Finally, make sure that the clear button no longer exists.
140+
cy.contains('Clear completed').should('not.exist')
141+
})
142+
})
143+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
/// <reference types="cypress" />
2+
3+
context('Actions', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/actions')
6+
})
7+
8+
// https://on.cypress.io/interacting-with-elements
9+
10+
it('.type() - type into a DOM element', () => {
11+
// https://on.cypress.io/type
12+
cy.get('.action-email')
13+
.type('fake@email.com').should('have.value', 'fake@email.com')
14+
15+
// .type() with special character sequences
16+
.type('{leftarrow}{rightarrow}{uparrow}{downarrow}')
17+
.type('{del}{selectall}{backspace}')
18+
19+
// .type() with key modifiers
20+
.type('{alt}{option}') //these are equivalent
21+
.type('{ctrl}{control}') //these are equivalent
22+
.type('{meta}{command}{cmd}') //these are equivalent
23+
.type('{shift}')
24+
25+
// Delay each keypress by 0.1 sec
26+
.type('slow.typing@email.com', { delay: 100 })
27+
.should('have.value', 'slow.typing@email.com')
28+
29+
cy.get('.action-disabled')
30+
// Ignore error checking prior to type
31+
// like whether the input is visible or disabled
32+
.type('disabled error checking', { force: true })
33+
.should('have.value', 'disabled error checking')
34+
})
35+
36+
it('.focus() - focus on a DOM element', () => {
37+
// https://on.cypress.io/focus
38+
cy.get('.action-focus').focus()
39+
.should('have.class', 'focus')
40+
.prev().should('have.attr', 'style', 'color: orange;')
41+
})
42+
43+
it('.blur() - blur off a DOM element', () => {
44+
// https://on.cypress.io/blur
45+
cy.get('.action-blur').type('About to blur').blur()
46+
.should('have.class', 'error')
47+
.prev().should('have.attr', 'style', 'color: red;')
48+
})
49+
50+
it('.clear() - clears an input or textarea element', () => {
51+
// https://on.cypress.io/clear
52+
cy.get('.action-clear').type('Clear this text')
53+
.should('have.value', 'Clear this text')
54+
.clear()
55+
.should('have.value', '')
56+
})
57+
58+
it('.submit() - submit a form', () => {
59+
// https://on.cypress.io/submit
60+
cy.get('.action-form')
61+
.find('[type="text"]').type('HALFOFF')
62+
63+
cy.get('.action-form').submit()
64+
.next().should('contain', 'Your form has been submitted!')
65+
})
66+
67+
it('.click() - click on a DOM element', () => {
68+
// https://on.cypress.io/click
69+
cy.get('.action-btn').click()
70+
71+
// You can click on 9 specific positions of an element:
72+
// -----------------------------------
73+
// | topLeft top topRight |
74+
// | |
75+
// | |
76+
// | |
77+
// | left center right |
78+
// | |
79+
// | |
80+
// | |
81+
// | bottomLeft bottom bottomRight |
82+
// -----------------------------------
83+
84+
// clicking in the center of the element is the default
85+
cy.get('#action-canvas').click()
86+
87+
cy.get('#action-canvas').click('topLeft')
88+
cy.get('#action-canvas').click('top')
89+
cy.get('#action-canvas').click('topRight')
90+
cy.get('#action-canvas').click('left')
91+
cy.get('#action-canvas').click('right')
92+
cy.get('#action-canvas').click('bottomLeft')
93+
cy.get('#action-canvas').click('bottom')
94+
cy.get('#action-canvas').click('bottomRight')
95+
96+
// .click() accepts an x and y coordinate
97+
// that controls where the click occurs :)
98+
99+
cy.get('#action-canvas')
100+
.click(80, 75) // click 80px on x coord and 75px on y coord
101+
.click(170, 75)
102+
.click(80, 165)
103+
.click(100, 185)
104+
.click(125, 190)
105+
.click(150, 185)
106+
.click(170, 165)
107+
108+
// click multiple elements by passing multiple: true
109+
cy.get('.action-labels>.label').click({ multiple: true })
110+
111+
// Ignore error checking prior to clicking
112+
cy.get('.action-opacity>.btn').click({ force: true })
113+
})
114+
115+
it('.dblclick() - double click on a DOM element', () => {
116+
// https://on.cypress.io/dblclick
117+
118+
// Our app has a listener on 'dblclick' event in our 'scripts.js'
119+
// that hides the div and shows an input on double click
120+
cy.get('.action-div').dblclick().should('not.be.visible')
121+
cy.get('.action-input-hidden').should('be.visible')
122+
})
123+
124+
it('.rightclick() - right click on a DOM element', () => {
125+
// https://on.cypress.io/rightclick
126+
127+
// Our app has a listener on 'contextmenu' event in our 'scripts.js'
128+
// that hides the div and shows an input on right click
129+
cy.get('.rightclick-action-div').rightclick().should('not.be.visible')
130+
cy.get('.rightclick-action-input-hidden').should('be.visible')
131+
})
132+
133+
it('.check() - check a checkbox or radio element', () => {
134+
// https://on.cypress.io/check
135+
136+
// By default, .check() will check all
137+
// matching checkbox or radio elements in succession, one after another
138+
cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]')
139+
.check().should('be.checked')
140+
141+
cy.get('.action-radios [type="radio"]').not('[disabled]')
142+
.check().should('be.checked')
143+
144+
// .check() accepts a value argument
145+
cy.get('.action-radios [type="radio"]')
146+
.check('radio1').should('be.checked')
147+
148+
// .check() accepts an array of values
149+
cy.get('.action-multiple-checkboxes [type="checkbox"]')
150+
.check(['checkbox1', 'checkbox2']).should('be.checked')
151+
152+
// Ignore error checking prior to checking
153+
cy.get('.action-checkboxes [disabled]')
154+
.check({ force: true }).should('be.checked')
155+
156+
cy.get('.action-radios [type="radio"]')
157+
.check('radio3', { force: true }).should('be.checked')
158+
})
159+
160+
it('.uncheck() - uncheck a checkbox element', () => {
161+
// https://on.cypress.io/uncheck
162+
163+
// By default, .uncheck() will uncheck all matching
164+
// checkbox elements in succession, one after another
165+
cy.get('.action-check [type="checkbox"]')
166+
.not('[disabled]')
167+
.uncheck().should('not.be.checked')
168+
169+
// .uncheck() accepts a value argument
170+
cy.get('.action-check [type="checkbox"]')
171+
.check('checkbox1')
172+
.uncheck('checkbox1').should('not.be.checked')
173+
174+
// .uncheck() accepts an array of values
175+
cy.get('.action-check [type="checkbox"]')
176+
.check(['checkbox1', 'checkbox3'])
177+
.uncheck(['checkbox1', 'checkbox3']).should('not.be.checked')
178+
179+
// Ignore error checking prior to unchecking
180+
cy.get('.action-check [disabled]')
181+
.uncheck({ force: true }).should('not.be.checked')
182+
})
183+
184+
it('.select() - select an option in a <select> element', () => {
185+
// https://on.cypress.io/select
186+
187+
// at first, no option should be selected
188+
cy.get('.action-select')
189+
.should('have.value', '--Select a fruit--')
190+
191+
// Select option(s) with matching text content
192+
cy.get('.action-select').select('apples')
193+
// confirm the apples were selected
194+
// note that each value starts with "fr-" in our HTML
195+
cy.get('.action-select').should('have.value', 'fr-apples')
196+
197+
cy.get('.action-select-multiple')
198+
.select(['apples', 'oranges', 'bananas'])
199+
// when getting multiple values, invoke "val" method first
200+
.invoke('val')
201+
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
202+
203+
// Select option(s) with matching value
204+
cy.get('.action-select').select('fr-bananas')
205+
// can attach an assertion right away to the element
206+
.should('have.value', 'fr-bananas')
207+
208+
cy.get('.action-select-multiple')
209+
.select(['fr-apples', 'fr-oranges', 'fr-bananas'])
210+
.invoke('val')
211+
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
212+
213+
// assert the selected values include oranges
214+
cy.get('.action-select-multiple')
215+
.invoke('val').should('include', 'fr-oranges')
216+
})
217+
218+
it('.scrollIntoView() - scroll an element into view', () => {
219+
// https://on.cypress.io/scrollintoview
220+
221+
// normally all of these buttons are hidden,
222+
// because they're not within
223+
// the viewable area of their parent
224+
// (we need to scroll to see them)
225+
cy.get('#scroll-horizontal button')
226+
.should('not.be.visible')
227+
228+
// scroll the button into view, as if the user had scrolled
229+
cy.get('#scroll-horizontal button').scrollIntoView()
230+
.should('be.visible')
231+
232+
cy.get('#scroll-vertical button')
233+
.should('not.be.visible')
234+
235+
// Cypress handles the scroll direction needed
236+
cy.get('#scroll-vertical button').scrollIntoView()
237+
.should('be.visible')
238+
239+
cy.get('#scroll-both button')
240+
.should('not.be.visible')
241+
242+
// Cypress knows to scroll to the right and down
243+
cy.get('#scroll-both button').scrollIntoView()
244+
.should('be.visible')
245+
})
246+
247+
it('.trigger() - trigger an event on a DOM element', () => {
248+
// https://on.cypress.io/trigger
249+
250+
// To interact with a range input (slider)
251+
// we need to set its value & trigger the
252+
// event to signal it changed
253+
254+
// Here, we invoke jQuery's val() method to set
255+
// the value and trigger the 'change' event
256+
cy.get('.trigger-input-range')
257+
.invoke('val', 25)
258+
.trigger('change')
259+
.get('input[type=range]').siblings('p')
260+
.should('have.text', '25')
261+
})
262+
263+
it('cy.scrollTo() - scroll the window or element to a position', () => {
264+
// https://on.cypress.io/scrollto
265+
266+
// You can scroll to 9 specific positions of an element:
267+
// -----------------------------------
268+
// | topLeft top topRight |
269+
// | |
270+
// | |
271+
// | |
272+
// | left center right |
273+
// | |
274+
// | |
275+
// | |
276+
// | bottomLeft bottom bottomRight |
277+
// -----------------------------------
278+
279+
// if you chain .scrollTo() off of cy, we will
280+
// scroll the entire window
281+
cy.scrollTo('bottom')
282+
283+
cy.get('#scrollable-horizontal').scrollTo('right')
284+
285+
// or you can scroll to a specific coordinate:
286+
// (x axis, y axis) in pixels
287+
cy.get('#scrollable-vertical').scrollTo(250, 250)
288+
289+
// or you can scroll to a specific percentage
290+
// of the (width, height) of the element
291+
cy.get('#scrollable-both').scrollTo('75%', '25%')
292+
293+
// control the easing of the scroll (default is 'swing')
294+
cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' })
295+
296+
// control the duration of the scroll (in ms)
297+
cy.get('#scrollable-both').scrollTo('center', { duration: 2000 })
298+
})
299+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/// <reference types="cypress" />
2+
3+
context('Aliasing', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/aliasing')
6+
})
7+
8+
it('.as() - alias a DOM element for later use', () => {
9+
// https://on.cypress.io/as
10+
11+
// Alias a DOM element for use later
12+
// We don't have to traverse to the element
13+
// later in our code, we reference it with @
14+
15+
cy.get('.as-table').find('tbody>tr')
16+
.first().find('td').first()
17+
.find('button').as('firstBtn')
18+
19+
// when we reference the alias, we place an
20+
// @ in front of its name
21+
cy.get('@firstBtn').click()
22+
23+
cy.get('@firstBtn')
24+
.should('have.class', 'btn-success')
25+
.and('contain', 'Changed')
26+
})
27+
28+
it('.as() - alias a route for later use', () => {
29+
// Alias the route to wait for its response
30+
cy.intercept('GET', '**/comments/*').as('getComment')
31+
32+
// we have code that gets a comment when
33+
// the button is clicked in scripts.js
34+
cy.get('.network-btn').click()
35+
36+
// https://on.cypress.io/wait
37+
cy.wait('@getComment').its('response.statusCode').should('eq', 200)
38+
})
39+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/// <reference types="cypress" />
2+
3+
context('Assertions', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/assertions')
6+
})
7+
8+
describe('Implicit Assertions', () => {
9+
it('.should() - make an assertion about the current subject', () => {
10+
// https://on.cypress.io/should
11+
cy.get('.assertion-table')
12+
.find('tbody tr:last')
13+
.should('have.class', 'success')
14+
.find('td')
15+
.first()
16+
// checking the text of the <td> element in various ways
17+
.should('have.text', 'Column content')
18+
.should('contain', 'Column content')
19+
.should('have.html', 'Column content')
20+
// chai-jquery uses "is()" to check if element matches selector
21+
.should('match', 'td')
22+
// to match text content against a regular expression
23+
// first need to invoke jQuery method text()
24+
// and then match using regular expression
25+
.invoke('text')
26+
.should('match', /column content/i)
27+
28+
// a better way to check element's text content against a regular expression
29+
// is to use "cy.contains"
30+
// https://on.cypress.io/contains
31+
cy.get('.assertion-table')
32+
.find('tbody tr:last')
33+
// finds first <td> element with text content matching regular expression
34+
.contains('td', /column content/i)
35+
.should('be.visible')
36+
37+
// for more information about asserting element's text
38+
// see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents
39+
})
40+
41+
it('.and() - chain multiple assertions together', () => {
42+
// https://on.cypress.io/and
43+
cy.get('.assertions-link')
44+
.should('have.class', 'active')
45+
.and('have.attr', 'href')
46+
.and('include', 'cypress.io')
47+
})
48+
})
49+
50+
describe('Explicit Assertions', () => {
51+
// https://on.cypress.io/assertions
52+
it('expect - make an assertion about a specified subject', () => {
53+
// We can use Chai's BDD style assertions
54+
expect(true).to.be.true
55+
const o = { foo: 'bar' }
56+
57+
expect(o).to.equal(o)
58+
expect(o).to.deep.equal({ foo: 'bar' })
59+
// matching text using regular expression
60+
expect('FooBar').to.match(/bar$/i)
61+
})
62+
63+
it('pass your own callback function to should()', () => {
64+
// Pass a function to should that can have any number
65+
// of explicit assertions within it.
66+
// The ".should(cb)" function will be retried
67+
// automatically until it passes all your explicit assertions or times out.
68+
cy.get('.assertions-p')
69+
.find('p')
70+
.should(($p) => {
71+
// https://on.cypress.io/$
72+
// return an array of texts from all of the p's
73+
// @ts-ignore TS6133 unused variable
74+
const texts = $p.map((i, el) => Cypress.$(el).text())
75+
76+
// jquery map returns jquery object
77+
// and .get() convert this to simple array
78+
const paragraphs = texts.get()
79+
80+
// array should have length of 3
81+
expect(paragraphs, 'has 3 paragraphs').to.have.length(3)
82+
83+
// use second argument to expect(...) to provide clear
84+
// message with each assertion
85+
expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([
86+
'Some text from first p',
87+
'More text from second p',
88+
'And even more text from third p',
89+
])
90+
})
91+
})
92+
93+
it('finds element by class name regex', () => {
94+
cy.get('.docs-header')
95+
.find('div')
96+
// .should(cb) callback function will be retried
97+
.should(($div) => {
98+
expect($div).to.have.length(1)
99+
100+
const className = $div[0].className
101+
102+
expect(className).to.match(/heading-/)
103+
})
104+
// .then(cb) callback is not retried,
105+
// it either passes or fails
106+
.then(($div) => {
107+
expect($div, 'text content').to.have.text('Introduction')
108+
})
109+
})
110+
111+
it('can throw any error', () => {
112+
cy.get('.docs-header')
113+
.find('div')
114+
.should(($div) => {
115+
if ($div.length !== 1) {
116+
// you can throw your own errors
117+
throw new Error('Did not find 1 element')
118+
}
119+
120+
const className = $div[0].className
121+
122+
if (!className.match(/heading-/)) {
123+
throw new Error(`Could not find class "heading-" in ${className}`)
124+
}
125+
})
126+
})
127+
128+
it('matches unknown text between two elements', () => {
129+
/**
130+
* Text from the first element.
131+
* @type {string}
132+
*/
133+
let text
134+
135+
/**
136+
* Normalizes passed text,
137+
* useful before comparing text with spaces and different capitalization.
138+
* @param {string} s Text to normalize
139+
*/
140+
const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase()
141+
142+
cy.get('.two-elements')
143+
.find('.first')
144+
.then(($first) => {
145+
// save text from the first element
146+
text = normalizeText($first.text())
147+
})
148+
149+
cy.get('.two-elements')
150+
.find('.second')
151+
.should(($div) => {
152+
// we can massage text before comparing
153+
const secondText = normalizeText($div.text())
154+
155+
expect(secondText, 'second text').to.equal(text)
156+
})
157+
})
158+
159+
it('assert - assert shape of an object', () => {
160+
const person = {
161+
name: 'Joe',
162+
age: 20,
163+
}
164+
165+
assert.isObject(person, 'value is object')
166+
})
167+
168+
it('retries the should callback until assertions pass', () => {
169+
cy.get('#random-number')
170+
.should(($div) => {
171+
const n = parseFloat($div.text())
172+
173+
expect(n).to.be.gte(1).and.be.lte(10)
174+
})
175+
})
176+
})
177+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/// <reference types="cypress" />
2+
3+
context('Connectors', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/connectors')
6+
})
7+
8+
it('.each() - iterate over an array of elements', () => {
9+
// https://on.cypress.io/each
10+
cy.get('.connectors-each-ul>li')
11+
.each(($el, index, $list) => {
12+
console.log($el, index, $list)
13+
})
14+
})
15+
16+
it('.its() - get properties on the current subject', () => {
17+
// https://on.cypress.io/its
18+
cy.get('.connectors-its-ul>li')
19+
// calls the 'length' property yielding that value
20+
.its('length')
21+
.should('be.gt', 2)
22+
})
23+
24+
it('.invoke() - invoke a function on the current subject', () => {
25+
// our div is hidden in our script.js
26+
// $('.connectors-div').hide()
27+
28+
// https://on.cypress.io/invoke
29+
cy.get('.connectors-div').should('be.hidden')
30+
// call the jquery method 'show' on the 'div.container'
31+
.invoke('show')
32+
.should('be.visible')
33+
})
34+
35+
it('.spread() - spread an array as individual args to callback function', () => {
36+
// https://on.cypress.io/spread
37+
const arr = ['foo', 'bar', 'baz']
38+
39+
cy.wrap(arr).spread((foo, bar, baz) => {
40+
expect(foo).to.eq('foo')
41+
expect(bar).to.eq('bar')
42+
expect(baz).to.eq('baz')
43+
})
44+
})
45+
46+
describe('.then()', () => {
47+
it('invokes a callback function with the current subject', () => {
48+
// https://on.cypress.io/then
49+
cy.get('.connectors-list > li')
50+
.then(($lis) => {
51+
expect($lis, '3 items').to.have.length(3)
52+
expect($lis.eq(0), 'first item').to.contain('Walk the dog')
53+
expect($lis.eq(1), 'second item').to.contain('Feed the cat')
54+
expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
55+
})
56+
})
57+
58+
it('yields the returned value to the next command', () => {
59+
cy.wrap(1)
60+
.then((num) => {
61+
expect(num).to.equal(1)
62+
63+
return 2
64+
})
65+
.then((num) => {
66+
expect(num).to.equal(2)
67+
})
68+
})
69+
70+
it('yields the original subject without return', () => {
71+
cy.wrap(1)
72+
.then((num) => {
73+
expect(num).to.equal(1)
74+
// note that nothing is returned from this callback
75+
})
76+
.then((num) => {
77+
// this callback receives the original unchanged value 1
78+
expect(num).to.equal(1)
79+
})
80+
})
81+
82+
it('yields the value yielded by the last Cypress command inside', () => {
83+
cy.wrap(1)
84+
.then((num) => {
85+
expect(num).to.equal(1)
86+
// note how we run a Cypress command
87+
// the result yielded by this Cypress command
88+
// will be passed to the second ".then"
89+
cy.wrap(2)
90+
})
91+
.then((num) => {
92+
// this callback receives the value yielded by "cy.wrap(2)"
93+
expect(num).to.equal(2)
94+
})
95+
})
96+
})
97+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/// <reference types="cypress" />
2+
3+
context('Cookies', () => {
4+
beforeEach(() => {
5+
Cypress.Cookies.debug(true)
6+
7+
cy.visit('https://example.cypress.io/commands/cookies')
8+
9+
// clear cookies again after visiting to remove
10+
// any 3rd party cookies picked up such as cloudflare
11+
cy.clearCookies()
12+
})
13+
14+
it('cy.getCookie() - get a browser cookie', () => {
15+
// https://on.cypress.io/getcookie
16+
cy.get('#getCookie .set-a-cookie').click()
17+
18+
// cy.getCookie() yields a cookie object
19+
cy.getCookie('token').should('have.property', 'value', '123ABC')
20+
})
21+
22+
it('cy.getCookies() - get browser cookies', () => {
23+
// https://on.cypress.io/getcookies
24+
cy.getCookies().should('be.empty')
25+
26+
cy.get('#getCookies .set-a-cookie').click()
27+
28+
// cy.getCookies() yields an array of cookies
29+
cy.getCookies().should('have.length', 1).should((cookies) => {
30+
// each cookie has these properties
31+
expect(cookies[0]).to.have.property('name', 'token')
32+
expect(cookies[0]).to.have.property('value', '123ABC')
33+
expect(cookies[0]).to.have.property('httpOnly', false)
34+
expect(cookies[0]).to.have.property('secure', false)
35+
expect(cookies[0]).to.have.property('domain')
36+
expect(cookies[0]).to.have.property('path')
37+
})
38+
})
39+
40+
it('cy.setCookie() - set a browser cookie', () => {
41+
// https://on.cypress.io/setcookie
42+
cy.getCookies().should('be.empty')
43+
44+
cy.setCookie('foo', 'bar')
45+
46+
// cy.getCookie() yields a cookie object
47+
cy.getCookie('foo').should('have.property', 'value', 'bar')
48+
})
49+
50+
it('cy.clearCookie() - clear a browser cookie', () => {
51+
// https://on.cypress.io/clearcookie
52+
cy.getCookie('token').should('be.null')
53+
54+
cy.get('#clearCookie .set-a-cookie').click()
55+
56+
cy.getCookie('token').should('have.property', 'value', '123ABC')
57+
58+
// cy.clearCookies() yields null
59+
cy.clearCookie('token').should('be.null')
60+
61+
cy.getCookie('token').should('be.null')
62+
})
63+
64+
it('cy.clearCookies() - clear browser cookies', () => {
65+
// https://on.cypress.io/clearcookies
66+
cy.getCookies().should('be.empty')
67+
68+
cy.get('#clearCookies .set-a-cookie').click()
69+
70+
cy.getCookies().should('have.length', 1)
71+
72+
// cy.clearCookies() yields null
73+
cy.clearCookies()
74+
75+
cy.getCookies().should('be.empty')
76+
})
77+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/// <reference types="cypress" />
2+
3+
context('Cypress.Commands', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/cypress-api')
6+
})
7+
8+
// https://on.cypress.io/custom-commands
9+
10+
it('.add() - create a custom command', () => {
11+
Cypress.Commands.add('console', {
12+
prevSubject: true,
13+
}, (subject, method) => {
14+
// the previous subject is automatically received
15+
// and the commands arguments are shifted
16+
17+
// allow us to change the console method used
18+
method = method || 'log'
19+
20+
// log the subject to the console
21+
// @ts-ignore TS7017
22+
console[method]('The subject is', subject)
23+
24+
// whatever we return becomes the new subject
25+
// we don't want to change the subject so
26+
// we return whatever was passed in
27+
return subject
28+
})
29+
30+
// @ts-ignore TS2339
31+
cy.get('button').console('info').then(($button) => {
32+
// subject is still $button
33+
})
34+
})
35+
})
36+
37+
context('Cypress.Cookies', () => {
38+
beforeEach(() => {
39+
cy.visit('https://example.cypress.io/cypress-api')
40+
})
41+
42+
// https://on.cypress.io/cookies
43+
it('.debug() - enable or disable debugging', () => {
44+
Cypress.Cookies.debug(true)
45+
46+
// Cypress will now log in the console when
47+
// cookies are set or cleared
48+
cy.setCookie('fakeCookie', '123ABC')
49+
cy.clearCookie('fakeCookie')
50+
cy.setCookie('fakeCookie', '123ABC')
51+
cy.clearCookie('fakeCookie')
52+
cy.setCookie('fakeCookie', '123ABC')
53+
})
54+
55+
it('.preserveOnce() - preserve cookies by key', () => {
56+
// normally cookies are reset after each test
57+
cy.getCookie('fakeCookie').should('not.be.ok')
58+
59+
// preserving a cookie will not clear it when
60+
// the next test starts
61+
cy.setCookie('lastCookie', '789XYZ')
62+
Cypress.Cookies.preserveOnce('lastCookie')
63+
})
64+
65+
it('.defaults() - set defaults for all cookies', () => {
66+
// now any cookie with the name 'session_id' will
67+
// not be cleared before each new test runs
68+
Cypress.Cookies.defaults({
69+
preserve: 'session_id',
70+
})
71+
})
72+
})
73+
74+
context('Cypress.arch', () => {
75+
beforeEach(() => {
76+
cy.visit('https://example.cypress.io/cypress-api')
77+
})
78+
79+
it('Get CPU architecture name of underlying OS', () => {
80+
// https://on.cypress.io/arch
81+
expect(Cypress.arch).to.exist
82+
})
83+
})
84+
85+
context('Cypress.config()', () => {
86+
beforeEach(() => {
87+
cy.visit('https://example.cypress.io/cypress-api')
88+
})
89+
90+
it('Get and set configuration options', () => {
91+
// https://on.cypress.io/config
92+
let myConfig = Cypress.config()
93+
94+
expect(myConfig).to.have.property('animationDistanceThreshold', 5)
95+
expect(myConfig).to.have.property('baseUrl', null)
96+
expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
97+
expect(myConfig).to.have.property('requestTimeout', 5000)
98+
expect(myConfig).to.have.property('responseTimeout', 30000)
99+
expect(myConfig).to.have.property('viewportHeight', 660)
100+
expect(myConfig).to.have.property('viewportWidth', 1000)
101+
expect(myConfig).to.have.property('pageLoadTimeout', 60000)
102+
expect(myConfig).to.have.property('waitForAnimations', true)
103+
104+
expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
105+
106+
// this will change the config for the rest of your tests!
107+
Cypress.config('pageLoadTimeout', 20000)
108+
109+
expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
110+
111+
Cypress.config('pageLoadTimeout', 60000)
112+
})
113+
})
114+
115+
context('Cypress.dom', () => {
116+
beforeEach(() => {
117+
cy.visit('https://example.cypress.io/cypress-api')
118+
})
119+
120+
// https://on.cypress.io/dom
121+
it('.isHidden() - determine if a DOM element is hidden', () => {
122+
let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
123+
let visibleP = Cypress.$('.dom-p p.visible').get(0)
124+
125+
// our first paragraph has css class 'hidden'
126+
expect(Cypress.dom.isHidden(hiddenP)).to.be.true
127+
expect(Cypress.dom.isHidden(visibleP)).to.be.false
128+
})
129+
})
130+
131+
context('Cypress.env()', () => {
132+
beforeEach(() => {
133+
cy.visit('https://example.cypress.io/cypress-api')
134+
})
135+
136+
// We can set environment variables for highly dynamic values
137+
138+
// https://on.cypress.io/environment-variables
139+
it('Get environment variables', () => {
140+
// https://on.cypress.io/env
141+
// set multiple environment variables
142+
Cypress.env({
143+
host: 'veronica.dev.local',
144+
api_server: 'http://localhost:8888/v1/',
145+
})
146+
147+
// get environment variable
148+
expect(Cypress.env('host')).to.eq('veronica.dev.local')
149+
150+
// set environment variable
151+
Cypress.env('api_server', 'http://localhost:8888/v2/')
152+
expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
153+
154+
// get all environment variable
155+
expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
156+
expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
157+
})
158+
})
159+
160+
context('Cypress.log', () => {
161+
beforeEach(() => {
162+
cy.visit('https://example.cypress.io/cypress-api')
163+
})
164+
165+
it('Control what is printed to the Command Log', () => {
166+
// https://on.cypress.io/cypress-log
167+
})
168+
})
169+
170+
context('Cypress.platform', () => {
171+
beforeEach(() => {
172+
cy.visit('https://example.cypress.io/cypress-api')
173+
})
174+
175+
it('Get underlying OS name', () => {
176+
// https://on.cypress.io/platform
177+
expect(Cypress.platform).to.be.exist
178+
})
179+
})
180+
181+
context('Cypress.version', () => {
182+
beforeEach(() => {
183+
cy.visit('https://example.cypress.io/cypress-api')
184+
})
185+
186+
it('Get current version of Cypress being run', () => {
187+
// https://on.cypress.io/version
188+
expect(Cypress.version).to.be.exist
189+
})
190+
})
191+
192+
context('Cypress.spec', () => {
193+
beforeEach(() => {
194+
cy.visit('https://example.cypress.io/cypress-api')
195+
})
196+
197+
it('Get current spec information', () => {
198+
// https://on.cypress.io/spec
199+
// wrap the object so we can inspect it easily by clicking in the command log
200+
cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute'])
201+
})
202+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/// <reference types="cypress" />
2+
3+
/// JSON fixture file can be loaded directly using
4+
// the built-in JavaScript bundler
5+
// @ts-ignore
6+
const requiredExample = require('../../fixtures/example')
7+
8+
context('Files', () => {
9+
beforeEach(() => {
10+
cy.visit('https://example.cypress.io/commands/files')
11+
})
12+
13+
beforeEach(() => {
14+
// load example.json fixture file and store
15+
// in the test context object
16+
cy.fixture('example.json').as('example')
17+
})
18+
19+
it('cy.fixture() - load a fixture', () => {
20+
// https://on.cypress.io/fixture
21+
22+
// Instead of writing a response inline you can
23+
// use a fixture file's content.
24+
25+
// when application makes an Ajax request matching "GET **/comments/*"
26+
// Cypress will intercept it and reply with the object in `example.json` fixture
27+
cy.intercept('GET', '**/comments/*', { fixture: 'example.json' }).as('getComment')
28+
29+
// we have code that gets a comment when
30+
// the button is clicked in scripts.js
31+
cy.get('.fixture-btn').click()
32+
33+
cy.wait('@getComment').its('response.body')
34+
.should('have.property', 'name')
35+
.and('include', 'Using fixtures to represent data')
36+
})
37+
38+
it('cy.fixture() or require - load a fixture', function () {
39+
// we are inside the "function () { ... }"
40+
// callback and can use test context object "this"
41+
// "this.example" was loaded in "beforeEach" function callback
42+
expect(this.example, 'fixture in the test context')
43+
.to.deep.equal(requiredExample)
44+
45+
// or use "cy.wrap" and "should('deep.equal', ...)" assertion
46+
cy.wrap(this.example)
47+
.should('deep.equal', requiredExample)
48+
})
49+
50+
it('cy.readFile() - read file contents', () => {
51+
// https://on.cypress.io/readfile
52+
53+
// You can read a file and yield its contents
54+
// The filePath is relative to your project's root.
55+
cy.readFile('cypress.json').then((json) => {
56+
expect(json).to.be.an('object')
57+
})
58+
})
59+
60+
it('cy.writeFile() - write to a file', () => {
61+
// https://on.cypress.io/writefile
62+
63+
// You can write to a file
64+
65+
// Use a response from a request to automatically
66+
// generate a fixture file for use later
67+
cy.request('https://jsonplaceholder.cypress.io/users')
68+
.then((response) => {
69+
cy.writeFile('cypress/fixtures/users.json', response.body)
70+
})
71+
72+
cy.fixture('users').should((users) => {
73+
expect(users[0].name).to.exist
74+
})
75+
76+
// JavaScript arrays and objects are stringified
77+
// and formatted into text.
78+
cy.writeFile('cypress/fixtures/profile.json', {
79+
id: 8739,
80+
name: 'Jane',
81+
email: 'jane@example.com',
82+
})
83+
84+
cy.fixture('profile').should((profile) => {
85+
expect(profile.name).to.eq('Jane')
86+
})
87+
})
88+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/// <reference types="cypress" />
2+
3+
context('Local Storage', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/local-storage')
6+
})
7+
// Although local storage is automatically cleared
8+
// in between tests to maintain a clean state
9+
// sometimes we need to clear the local storage manually
10+
11+
it('cy.clearLocalStorage() - clear all data in local storage', () => {
12+
// https://on.cypress.io/clearlocalstorage
13+
cy.get('.ls-btn').click().should(() => {
14+
expect(localStorage.getItem('prop1')).to.eq('red')
15+
expect(localStorage.getItem('prop2')).to.eq('blue')
16+
expect(localStorage.getItem('prop3')).to.eq('magenta')
17+
})
18+
19+
// clearLocalStorage() yields the localStorage object
20+
cy.clearLocalStorage().should((ls) => {
21+
expect(ls.getItem('prop1')).to.be.null
22+
expect(ls.getItem('prop2')).to.be.null
23+
expect(ls.getItem('prop3')).to.be.null
24+
})
25+
26+
cy.get('.ls-btn').click().should(() => {
27+
expect(localStorage.getItem('prop1')).to.eq('red')
28+
expect(localStorage.getItem('prop2')).to.eq('blue')
29+
expect(localStorage.getItem('prop3')).to.eq('magenta')
30+
})
31+
32+
// Clear key matching string in Local Storage
33+
cy.clearLocalStorage('prop1').should((ls) => {
34+
expect(ls.getItem('prop1')).to.be.null
35+
expect(ls.getItem('prop2')).to.eq('blue')
36+
expect(ls.getItem('prop3')).to.eq('magenta')
37+
})
38+
39+
cy.get('.ls-btn').click().should(() => {
40+
expect(localStorage.getItem('prop1')).to.eq('red')
41+
expect(localStorage.getItem('prop2')).to.eq('blue')
42+
expect(localStorage.getItem('prop3')).to.eq('magenta')
43+
})
44+
45+
// Clear keys matching regex in Local Storage
46+
cy.clearLocalStorage(/prop1|2/).should((ls) => {
47+
expect(ls.getItem('prop1')).to.be.null
48+
expect(ls.getItem('prop2')).to.be.null
49+
expect(ls.getItem('prop3')).to.eq('magenta')
50+
})
51+
})
52+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/// <reference types="cypress" />
2+
3+
context('Location', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/location')
6+
})
7+
8+
it('cy.hash() - get the current URL hash', () => {
9+
// https://on.cypress.io/hash
10+
cy.hash().should('be.empty')
11+
})
12+
13+
it('cy.location() - get window.location', () => {
14+
// https://on.cypress.io/location
15+
cy.location().should((location) => {
16+
expect(location.hash).to.be.empty
17+
expect(location.href).to.eq('https://example.cypress.io/commands/location')
18+
expect(location.host).to.eq('example.cypress.io')
19+
expect(location.hostname).to.eq('example.cypress.io')
20+
expect(location.origin).to.eq('https://example.cypress.io')
21+
expect(location.pathname).to.eq('/commands/location')
22+
expect(location.port).to.eq('')
23+
expect(location.protocol).to.eq('https:')
24+
expect(location.search).to.be.empty
25+
})
26+
})
27+
28+
it('cy.url() - get the current URL', () => {
29+
// https://on.cypress.io/url
30+
cy.url().should('eq', 'https://example.cypress.io/commands/location')
31+
})
32+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/// <reference types="cypress" />
2+
3+
context('Misc', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/misc')
6+
})
7+
8+
it('.end() - end the command chain', () => {
9+
// https://on.cypress.io/end
10+
11+
// cy.end is useful when you want to end a chain of commands
12+
// and force Cypress to re-query from the root element
13+
cy.get('.misc-table').within(() => {
14+
// ends the current chain and yields null
15+
cy.contains('Cheryl').click().end()
16+
17+
// queries the entire table again
18+
cy.contains('Charles').click()
19+
})
20+
})
21+
22+
it('cy.exec() - execute a system command', () => {
23+
// execute a system command.
24+
// so you can take actions necessary for
25+
// your test outside the scope of Cypress.
26+
// https://on.cypress.io/exec
27+
28+
// we can use Cypress.platform string to
29+
// select appropriate command
30+
// https://on.cypress/io/platform
31+
cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`)
32+
33+
// on CircleCI Windows build machines we have a failure to run bash shell
34+
// https://github.com/cypress-io/cypress/issues/5169
35+
// so skip some of the tests by passing flag "--env circle=true"
36+
const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle')
37+
38+
if (isCircleOnWindows) {
39+
cy.log('Skipping test on CircleCI')
40+
41+
return
42+
}
43+
44+
// cy.exec problem on Shippable CI
45+
// https://github.com/cypress-io/cypress/issues/6718
46+
const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable')
47+
48+
if (isShippable) {
49+
cy.log('Skipping test on ShippableCI')
50+
51+
return
52+
}
53+
54+
cy.exec('echo Jane Lane')
55+
.its('stdout').should('contain', 'Jane Lane')
56+
57+
if (Cypress.platform === 'win32') {
58+
cy.exec('print cypress.json')
59+
.its('stderr').should('be.empty')
60+
} else {
61+
cy.exec('cat cypress.json')
62+
.its('stderr').should('be.empty')
63+
64+
cy.exec('pwd')
65+
.its('code').should('eq', 0)
66+
}
67+
})
68+
69+
it('cy.focused() - get the DOM element that has focus', () => {
70+
// https://on.cypress.io/focused
71+
cy.get('.misc-form').find('#name').click()
72+
cy.focused().should('have.id', 'name')
73+
74+
cy.get('.misc-form').find('#description').click()
75+
cy.focused().should('have.id', 'description')
76+
})
77+
78+
context('Cypress.Screenshot', function () {
79+
it('cy.screenshot() - take a screenshot', () => {
80+
// https://on.cypress.io/screenshot
81+
cy.screenshot('my-image')
82+
})
83+
84+
it('Cypress.Screenshot.defaults() - change default config of screenshots', function () {
85+
Cypress.Screenshot.defaults({
86+
blackout: ['.foo'],
87+
capture: 'viewport',
88+
clip: { x: 0, y: 0, width: 200, height: 200 },
89+
scale: false,
90+
disableTimersAndAnimations: true,
91+
screenshotOnRunFailure: true,
92+
onBeforeScreenshot () { },
93+
onAfterScreenshot () { },
94+
})
95+
})
96+
})
97+
98+
it('cy.wrap() - wrap an object', () => {
99+
// https://on.cypress.io/wrap
100+
cy.wrap({ foo: 'bar' })
101+
.should('have.property', 'foo')
102+
.and('include', 'bar')
103+
})
104+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/// <reference types="cypress" />
2+
3+
context('Navigation', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io')
6+
cy.get('.navbar-nav').contains('Commands').click()
7+
cy.get('.dropdown-menu').contains('Navigation').click()
8+
})
9+
10+
it('cy.go() - go back or forward in the browser\'s history', () => {
11+
// https://on.cypress.io/go
12+
13+
cy.location('pathname').should('include', 'navigation')
14+
15+
cy.go('back')
16+
cy.location('pathname').should('not.include', 'navigation')
17+
18+
cy.go('forward')
19+
cy.location('pathname').should('include', 'navigation')
20+
21+
// clicking back
22+
cy.go(-1)
23+
cy.location('pathname').should('not.include', 'navigation')
24+
25+
// clicking forward
26+
cy.go(1)
27+
cy.location('pathname').should('include', 'navigation')
28+
})
29+
30+
it('cy.reload() - reload the page', () => {
31+
// https://on.cypress.io/reload
32+
cy.reload()
33+
34+
// reload the page without using the cache
35+
cy.reload(true)
36+
})
37+
38+
it('cy.visit() - visit a remote url', () => {
39+
// https://on.cypress.io/visit
40+
41+
// Visit any sub-domain of your current domain
42+
43+
// Pass options to the visit
44+
cy.visit('https://example.cypress.io/commands/navigation', {
45+
timeout: 50000, // increase total time for the visit to resolve
46+
onBeforeLoad (contentWindow) {
47+
// contentWindow is the remote page's window object
48+
expect(typeof contentWindow === 'object').to.be.true
49+
},
50+
onLoad (contentWindow) {
51+
// contentWindow is the remote page's window object
52+
expect(typeof contentWindow === 'object').to.be.true
53+
},
54+
})
55+
})
56+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/// <reference types="cypress" />
2+
3+
context('Network Requests', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/network-requests')
6+
})
7+
8+
// Manage HTTP requests in your app
9+
10+
it('cy.request() - make an XHR request', () => {
11+
// https://on.cypress.io/request
12+
cy.request('https://jsonplaceholder.cypress.io/comments')
13+
.should((response) => {
14+
expect(response.status).to.eq(200)
15+
// the server sometimes gets an extra comment posted from another machine
16+
// which gets returned as 1 extra object
17+
expect(response.body).to.have.property('length').and.be.oneOf([500, 501])
18+
expect(response).to.have.property('headers')
19+
expect(response).to.have.property('duration')
20+
})
21+
})
22+
23+
it('cy.request() - verify response using BDD syntax', () => {
24+
cy.request('https://jsonplaceholder.cypress.io/comments')
25+
.then((response) => {
26+
// https://on.cypress.io/assertions
27+
expect(response).property('status').to.equal(200)
28+
expect(response).property('body').to.have.property('length').and.be.oneOf([500, 501])
29+
expect(response).to.include.keys('headers', 'duration')
30+
})
31+
})
32+
33+
it('cy.request() with query parameters', () => {
34+
// will execute request
35+
// https://jsonplaceholder.cypress.io/comments?postId=1&id=3
36+
cy.request({
37+
url: 'https://jsonplaceholder.cypress.io/comments',
38+
qs: {
39+
postId: 1,
40+
id: 3,
41+
},
42+
})
43+
.its('body')
44+
.should('be.an', 'array')
45+
.and('have.length', 1)
46+
.its('0') // yields first element of the array
47+
.should('contain', {
48+
postId: 1,
49+
id: 3,
50+
})
51+
})
52+
53+
it('cy.request() - pass result to the second request', () => {
54+
// first, let's find out the userId of the first user we have
55+
cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
56+
.its('body') // yields the response object
57+
.its('0') // yields the first element of the returned list
58+
// the above two commands its('body').its('0')
59+
// can be written as its('body.0')
60+
// if you do not care about TypeScript checks
61+
.then((user) => {
62+
expect(user).property('id').to.be.a('number')
63+
// make a new post on behalf of the user
64+
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
65+
userId: user.id,
66+
title: 'Cypress Test Runner',
67+
body: 'Fast, easy and reliable testing for anything that runs in a browser.',
68+
})
69+
})
70+
// note that the value here is the returned value of the 2nd request
71+
// which is the new post object
72+
.then((response) => {
73+
expect(response).property('status').to.equal(201) // new entity created
74+
expect(response).property('body').to.contain({
75+
title: 'Cypress Test Runner',
76+
})
77+
78+
// we don't know the exact post id - only that it will be > 100
79+
// since JSONPlaceholder has built-in 100 posts
80+
expect(response.body).property('id').to.be.a('number')
81+
.and.to.be.gt(100)
82+
83+
// we don't know the user id here - since it was in above closure
84+
// so in this test just confirm that the property is there
85+
expect(response.body).property('userId').to.be.a('number')
86+
})
87+
})
88+
89+
it('cy.request() - save response in the shared test context', () => {
90+
// https://on.cypress.io/variables-and-aliases
91+
cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
92+
.its('body').its('0') // yields the first element of the returned list
93+
.as('user') // saves the object in the test context
94+
.then(function () {
95+
// NOTE 👀
96+
// By the time this callback runs the "as('user')" command
97+
// has saved the user object in the test context.
98+
// To access the test context we need to use
99+
// the "function () { ... }" callback form,
100+
// otherwise "this" points at a wrong or undefined object!
101+
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
102+
userId: this.user.id,
103+
title: 'Cypress Test Runner',
104+
body: 'Fast, easy and reliable testing for anything that runs in a browser.',
105+
})
106+
.its('body').as('post') // save the new post from the response
107+
})
108+
.then(function () {
109+
// When this callback runs, both "cy.request" API commands have finished
110+
// and the test context has "user" and "post" objects set.
111+
// Let's verify them.
112+
expect(this.post, 'post has the right user id').property('userId').to.equal(this.user.id)
113+
})
114+
})
115+
116+
it('cy.intercept() - route responses to matching requests', () => {
117+
// https://on.cypress.io/intercept
118+
119+
let message = 'whoa, this comment does not exist'
120+
121+
// Listen to GET to comments/1
122+
cy.intercept('GET', '**/comments/*').as('getComment')
123+
124+
// we have code that gets a comment when
125+
// the button is clicked in scripts.js
126+
cy.get('.network-btn').click()
127+
128+
// https://on.cypress.io/wait
129+
cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
130+
131+
// Listen to POST to comments
132+
cy.intercept('POST', '**/comments').as('postComment')
133+
134+
// we have code that posts a comment when
135+
// the button is clicked in scripts.js
136+
cy.get('.network-post').click()
137+
cy.wait('@postComment').should(({ request, response }) => {
138+
expect(request.body).to.include('email')
139+
expect(request.headers).to.have.property('content-type')
140+
expect(response && response.body).to.have.property('name', 'Using POST in cy.intercept()')
141+
})
142+
143+
// Stub a response to PUT comments/ ****
144+
cy.intercept({
145+
method: 'PUT',
146+
url: '**/comments/*',
147+
}, {
148+
statusCode: 404,
149+
body: { error: message },
150+
headers: { 'access-control-allow-origin': '*' },
151+
delayMs: 500,
152+
}).as('putComment')
153+
154+
// we have code that puts a comment when
155+
// the button is clicked in scripts.js
156+
cy.get('.network-put').click()
157+
158+
cy.wait('@putComment')
159+
160+
// our 404 statusCode logic in scripts.js executed
161+
cy.get('.network-put-comment').should('contain', message)
162+
})
163+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/// <reference types="cypress" />
2+
3+
context('Querying', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/querying')
6+
})
7+
8+
// The most commonly used query is 'cy.get()', you can
9+
// think of this like the '$' in jQuery
10+
11+
it('cy.get() - query DOM elements', () => {
12+
// https://on.cypress.io/get
13+
14+
cy.get('#query-btn').should('contain', 'Button')
15+
16+
cy.get('.query-btn').should('contain', 'Button')
17+
18+
cy.get('#querying .well>button:first').should('contain', 'Button')
19+
// ↲
20+
// Use CSS selectors just like jQuery
21+
22+
cy.get('[data-test-id="test-example"]').should('have.class', 'example')
23+
24+
// 'cy.get()' yields jQuery object, you can get its attribute
25+
// by invoking `.attr()` method
26+
cy.get('[data-test-id="test-example"]')
27+
.invoke('attr', 'data-test-id')
28+
.should('equal', 'test-example')
29+
30+
// or you can get element's CSS property
31+
cy.get('[data-test-id="test-example"]')
32+
.invoke('css', 'position')
33+
.should('equal', 'static')
34+
35+
// or use assertions directly during 'cy.get()'
36+
// https://on.cypress.io/assertions
37+
cy.get('[data-test-id="test-example"]')
38+
.should('have.attr', 'data-test-id', 'test-example')
39+
.and('have.css', 'position', 'static')
40+
})
41+
42+
it('cy.contains() - query DOM elements with matching content', () => {
43+
// https://on.cypress.io/contains
44+
cy.get('.query-list')
45+
.contains('bananas')
46+
.should('have.class', 'third')
47+
48+
// we can pass a regexp to `.contains()`
49+
cy.get('.query-list')
50+
.contains(/^b\w+/)
51+
.should('have.class', 'third')
52+
53+
cy.get('.query-list')
54+
.contains('apples')
55+
.should('have.class', 'first')
56+
57+
// passing a selector to contains will
58+
// yield the selector containing the text
59+
cy.get('#querying')
60+
.contains('ul', 'oranges')
61+
.should('have.class', 'query-list')
62+
63+
cy.get('.query-button')
64+
.contains('Save Form')
65+
.should('have.class', 'btn')
66+
})
67+
68+
it('.within() - query DOM elements within a specific element', () => {
69+
// https://on.cypress.io/within
70+
cy.get('.query-form').within(() => {
71+
cy.get('input:first').should('have.attr', 'placeholder', 'Email')
72+
cy.get('input:last').should('have.attr', 'placeholder', 'Password')
73+
})
74+
})
75+
76+
it('cy.root() - query the root DOM element', () => {
77+
// https://on.cypress.io/root
78+
79+
// By default, root is the document
80+
cy.root().should('match', 'html')
81+
82+
cy.get('.query-ul').within(() => {
83+
// In this within, the root is now the ul DOM element
84+
cy.root().should('have.class', 'query-ul')
85+
})
86+
})
87+
88+
it('best practices - selecting elements', () => {
89+
// https://on.cypress.io/best-practices#Selecting-Elements
90+
cy.get('[data-cy=best-practices-selecting-elements]').within(() => {
91+
// Worst - too generic, no context
92+
cy.get('button').click()
93+
94+
// Bad. Coupled to styling. Highly subject to change.
95+
cy.get('.btn.btn-large').click()
96+
97+
// Average. Coupled to the `name` attribute which has HTML semantics.
98+
cy.get('[name=submission]').click()
99+
100+
// Better. But still coupled to styling or JS event listeners.
101+
cy.get('#main').click()
102+
103+
// Slightly better. Uses an ID but also ensures the element
104+
// has an ARIA role attribute
105+
cy.get('#main[role=button]').click()
106+
107+
// Much better. But still coupled to text content that may change.
108+
cy.contains('Submit').click()
109+
110+
// Best. Insulated from all changes.
111+
cy.get('[data-cy=submit]').click()
112+
})
113+
})
114+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/// <reference types="cypress" />
2+
// remove no check once Cypress.sinon is typed
3+
// https://github.com/cypress-io/cypress/issues/6720
4+
5+
context('Spies, Stubs, and Clock', () => {
6+
it('cy.spy() - wrap a method in a spy', () => {
7+
// https://on.cypress.io/spy
8+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
9+
10+
const obj = {
11+
foo () {},
12+
}
13+
14+
const spy = cy.spy(obj, 'foo').as('anyArgs')
15+
16+
obj.foo()
17+
18+
expect(spy).to.be.called
19+
})
20+
21+
it('cy.spy() retries until assertions pass', () => {
22+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
23+
24+
const obj = {
25+
/**
26+
* Prints the argument passed
27+
* @param x {any}
28+
*/
29+
foo (x) {
30+
console.log('obj.foo called with', x)
31+
},
32+
}
33+
34+
cy.spy(obj, 'foo').as('foo')
35+
36+
setTimeout(() => {
37+
obj.foo('first')
38+
}, 500)
39+
40+
setTimeout(() => {
41+
obj.foo('second')
42+
}, 2500)
43+
44+
cy.get('@foo').should('have.been.calledTwice')
45+
})
46+
47+
it('cy.stub() - create a stub and/or replace a function with stub', () => {
48+
// https://on.cypress.io/stub
49+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
50+
51+
const obj = {
52+
/**
53+
* prints both arguments to the console
54+
* @param a {string}
55+
* @param b {string}
56+
*/
57+
foo (a, b) {
58+
console.log('a', a, 'b', b)
59+
},
60+
}
61+
62+
const stub = cy.stub(obj, 'foo').as('foo')
63+
64+
obj.foo('foo', 'bar')
65+
66+
expect(stub).to.be.called
67+
})
68+
69+
it('cy.clock() - control time in the browser', () => {
70+
// https://on.cypress.io/clock
71+
72+
// create the date in UTC so its always the same
73+
// no matter what local timezone the browser is running in
74+
const now = new Date(Date.UTC(2017, 2, 14)).getTime()
75+
76+
cy.clock(now)
77+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
78+
cy.get('#clock-div').click()
79+
.should('have.text', '1489449600')
80+
})
81+
82+
it('cy.tick() - move time in the browser', () => {
83+
// https://on.cypress.io/tick
84+
85+
// create the date in UTC so its always the same
86+
// no matter what local timezone the browser is running in
87+
const now = new Date(Date.UTC(2017, 2, 14)).getTime()
88+
89+
cy.clock(now)
90+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
91+
cy.get('#tick-div').click()
92+
.should('have.text', '1489449600')
93+
94+
cy.tick(10000) // 10 seconds passed
95+
cy.get('#tick-div').click()
96+
.should('have.text', '1489449610')
97+
})
98+
99+
it('cy.stub() matches depending on arguments', () => {
100+
// see all possible matchers at
101+
// https://sinonjs.org/releases/latest/matchers/
102+
const greeter = {
103+
/**
104+
* Greets a person
105+
* @param {string} name
106+
*/
107+
greet (name) {
108+
return `Hello, ${name}!`
109+
},
110+
}
111+
112+
cy.stub(greeter, 'greet')
113+
.callThrough() // if you want non-matched calls to call the real method
114+
.withArgs(Cypress.sinon.match.string).returns('Hi')
115+
.withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name'))
116+
117+
expect(greeter.greet('World')).to.equal('Hi')
118+
// @ts-ignore
119+
expect(() => greeter.greet(42)).to.throw('Invalid name')
120+
expect(greeter.greet).to.have.been.calledTwice
121+
122+
// non-matched calls goes the actual method
123+
// @ts-ignore
124+
expect(greeter.greet()).to.equal('Hello, undefined!')
125+
})
126+
127+
it('matches call arguments using Sinon matchers', () => {
128+
// see all possible matchers at
129+
// https://sinonjs.org/releases/latest/matchers/
130+
const calculator = {
131+
/**
132+
* returns the sum of two arguments
133+
* @param a {number}
134+
* @param b {number}
135+
*/
136+
add (a, b) {
137+
return a + b
138+
},
139+
}
140+
141+
const spy = cy.spy(calculator, 'add').as('add')
142+
143+
expect(calculator.add(2, 3)).to.equal(5)
144+
145+
// if we want to assert the exact values used during the call
146+
expect(spy).to.be.calledWith(2, 3)
147+
148+
// let's confirm "add" method was called with two numbers
149+
expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number)
150+
151+
// alternatively, provide the value to match
152+
expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3))
153+
154+
// match any value
155+
expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3)
156+
157+
// match any value from a list
158+
expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3)
159+
160+
/**
161+
* Returns true if the given number is event
162+
* @param {number} x
163+
*/
164+
const isEven = (x) => x % 2 === 0
165+
166+
// expect the value to pass a custom predicate function
167+
// the second argument to "sinon.match(predicate, message)" is
168+
// shown if the predicate does not pass and assertion fails
169+
expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3)
170+
171+
/**
172+
* Returns a function that checks if a given number is larger than the limit
173+
* @param {number} limit
174+
* @returns {(x: number) => boolean}
175+
*/
176+
const isGreaterThan = (limit) => (x) => x > limit
177+
178+
/**
179+
* Returns a function that checks if a given number is less than the limit
180+
* @param {number} limit
181+
* @returns {(x: number) => boolean}
182+
*/
183+
const isLessThan = (limit) => (x) => x < limit
184+
185+
// you can combine several matchers using "and", "or"
186+
expect(spy).to.be.calledWith(
187+
Cypress.sinon.match.number,
188+
Cypress.sinon.match(isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4')),
189+
)
190+
191+
expect(spy).to.be.calledWith(
192+
Cypress.sinon.match.number,
193+
Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3)),
194+
)
195+
196+
// matchers can be used from BDD assertions
197+
cy.get('@add').should('have.been.calledWith',
198+
Cypress.sinon.match.number, Cypress.sinon.match(3))
199+
200+
// you can alias matchers for shorter test code
201+
const { match: M } = Cypress.sinon
202+
203+
cy.get('@add').should('have.been.calledWith', M.number, M(3))
204+
})
205+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/// <reference types="cypress" />
2+
3+
context('Traversal', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/traversal')
6+
})
7+
8+
it('.children() - get child DOM elements', () => {
9+
// https://on.cypress.io/children
10+
cy.get('.traversal-breadcrumb')
11+
.children('.active')
12+
.should('contain', 'Data')
13+
})
14+
15+
it('.closest() - get closest ancestor DOM element', () => {
16+
// https://on.cypress.io/closest
17+
cy.get('.traversal-badge')
18+
.closest('ul')
19+
.should('have.class', 'list-group')
20+
})
21+
22+
it('.eq() - get a DOM element at a specific index', () => {
23+
// https://on.cypress.io/eq
24+
cy.get('.traversal-list>li')
25+
.eq(1).should('contain', 'siamese')
26+
})
27+
28+
it('.filter() - get DOM elements that match the selector', () => {
29+
// https://on.cypress.io/filter
30+
cy.get('.traversal-nav>li')
31+
.filter('.active').should('contain', 'About')
32+
})
33+
34+
it('.find() - get descendant DOM elements of the selector', () => {
35+
// https://on.cypress.io/find
36+
cy.get('.traversal-pagination')
37+
.find('li').find('a')
38+
.should('have.length', 7)
39+
})
40+
41+
it('.first() - get first DOM element', () => {
42+
// https://on.cypress.io/first
43+
cy.get('.traversal-table td')
44+
.first().should('contain', '1')
45+
})
46+
47+
it('.last() - get last DOM element', () => {
48+
// https://on.cypress.io/last
49+
cy.get('.traversal-buttons .btn')
50+
.last().should('contain', 'Submit')
51+
})
52+
53+
it('.next() - get next sibling DOM element', () => {
54+
// https://on.cypress.io/next
55+
cy.get('.traversal-ul')
56+
.contains('apples').next().should('contain', 'oranges')
57+
})
58+
59+
it('.nextAll() - get all next sibling DOM elements', () => {
60+
// https://on.cypress.io/nextall
61+
cy.get('.traversal-next-all')
62+
.contains('oranges')
63+
.nextAll().should('have.length', 3)
64+
})
65+
66+
it('.nextUntil() - get next sibling DOM elements until next el', () => {
67+
// https://on.cypress.io/nextuntil
68+
cy.get('#veggies')
69+
.nextUntil('#nuts').should('have.length', 3)
70+
})
71+
72+
it('.not() - remove DOM elements from set of DOM elements', () => {
73+
// https://on.cypress.io/not
74+
cy.get('.traversal-disabled .btn')
75+
.not('[disabled]').should('not.contain', 'Disabled')
76+
})
77+
78+
it('.parent() - get parent DOM element from DOM elements', () => {
79+
// https://on.cypress.io/parent
80+
cy.get('.traversal-mark')
81+
.parent().should('contain', 'Morbi leo risus')
82+
})
83+
84+
it('.parents() - get parent DOM elements from DOM elements', () => {
85+
// https://on.cypress.io/parents
86+
cy.get('.traversal-cite')
87+
.parents().should('match', 'blockquote')
88+
})
89+
90+
it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => {
91+
// https://on.cypress.io/parentsuntil
92+
cy.get('.clothes-nav')
93+
.find('.active')
94+
.parentsUntil('.clothes-nav')
95+
.should('have.length', 2)
96+
})
97+
98+
it('.prev() - get previous sibling DOM element', () => {
99+
// https://on.cypress.io/prev
100+
cy.get('.birds').find('.active')
101+
.prev().should('contain', 'Lorikeets')
102+
})
103+
104+
it('.prevAll() - get all previous sibling DOM elements', () => {
105+
// https://on.cypress.io/prevall
106+
cy.get('.fruits-list').find('.third')
107+
.prevAll().should('have.length', 2)
108+
})
109+
110+
it('.prevUntil() - get all previous sibling DOM elements until el', () => {
111+
// https://on.cypress.io/prevuntil
112+
cy.get('.foods-list').find('#nuts')
113+
.prevUntil('#veggies').should('have.length', 3)
114+
})
115+
116+
it('.siblings() - get all sibling DOM elements', () => {
117+
// https://on.cypress.io/siblings
118+
cy.get('.traversal-pills .active')
119+
.siblings().should('have.length', 2)
120+
})
121+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/// <reference types="cypress" />
2+
3+
context('Utilities', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/utilities')
6+
})
7+
8+
it('Cypress._ - call a lodash method', () => {
9+
// https://on.cypress.io/_
10+
cy.request('https://jsonplaceholder.cypress.io/users')
11+
.then((response) => {
12+
let ids = Cypress._.chain(response.body).map('id').take(3).value()
13+
14+
expect(ids).to.deep.eq([1, 2, 3])
15+
})
16+
})
17+
18+
it('Cypress.$ - call a jQuery method', () => {
19+
// https://on.cypress.io/$
20+
let $li = Cypress.$('.utility-jquery li:first')
21+
22+
cy.wrap($li)
23+
.should('not.have.class', 'active')
24+
.click()
25+
.should('have.class', 'active')
26+
})
27+
28+
it('Cypress.Blob - blob utilities and base64 string conversion', () => {
29+
// https://on.cypress.io/blob
30+
cy.get('.utility-blob').then(($div) => {
31+
// https://github.com/nolanlawson/blob-util#imgSrcToDataURL
32+
// get the dataUrl string for the javascript-logo
33+
return Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous')
34+
.then((dataUrl) => {
35+
// create an <img> element and set its src to the dataUrl
36+
let img = Cypress.$('<img />', { src: dataUrl })
37+
38+
// need to explicitly return cy here since we are initially returning
39+
// the Cypress.Blob.imgSrcToDataURL promise to our test
40+
// append the image
41+
$div.append(img)
42+
43+
cy.get('.utility-blob img').click()
44+
.should('have.attr', 'src', dataUrl)
45+
})
46+
})
47+
})
48+
49+
it('Cypress.minimatch - test out glob patterns against strings', () => {
50+
// https://on.cypress.io/minimatch
51+
let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', {
52+
matchBase: true,
53+
})
54+
55+
expect(matching, 'matching wildcard').to.be.true
56+
57+
matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', {
58+
matchBase: true,
59+
})
60+
61+
expect(matching, 'comments').to.be.false
62+
63+
// ** matches against all downstream path segments
64+
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', {
65+
matchBase: true,
66+
})
67+
68+
expect(matching, 'comments').to.be.true
69+
70+
// whereas * matches only the next path segment
71+
72+
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', {
73+
matchBase: false,
74+
})
75+
76+
expect(matching, 'comments').to.be.false
77+
})
78+
79+
it('Cypress.Promise - instantiate a bluebird promise', () => {
80+
// https://on.cypress.io/promise
81+
let waited = false
82+
83+
/**
84+
* @return Bluebird<string>
85+
*/
86+
function waitOneSecond () {
87+
// return a promise that resolves after 1 second
88+
// @ts-ignore TS2351 (new Cypress.Promise)
89+
return new Cypress.Promise((resolve, reject) => {
90+
setTimeout(() => {
91+
// set waited to true
92+
waited = true
93+
94+
// resolve with 'foo' string
95+
resolve('foo')
96+
}, 1000)
97+
})
98+
}
99+
100+
cy.then(() => {
101+
// return a promise to cy.then() that
102+
// is awaited until it resolves
103+
// @ts-ignore TS7006
104+
return waitOneSecond().then((str) => {
105+
expect(str).to.eq('foo')
106+
expect(waited).to.be.true
107+
})
108+
})
109+
})
110+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/// <reference types="cypress" />
2+
3+
context('Viewport', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/viewport')
6+
})
7+
8+
it('cy.viewport() - set the viewport size and dimension', () => {
9+
// https://on.cypress.io/viewport
10+
11+
cy.get('#navbar').should('be.visible')
12+
cy.viewport(320, 480)
13+
14+
// the navbar should have collapse since our screen is smaller
15+
cy.get('#navbar').should('not.be.visible')
16+
cy.get('.navbar-toggle').should('be.visible').click()
17+
cy.get('.nav').find('a').should('be.visible')
18+
19+
// lets see what our app looks like on a super large screen
20+
cy.viewport(2999, 2999)
21+
22+
// cy.viewport() accepts a set of preset sizes
23+
// to easily set the screen to a device's width and height
24+
25+
// We added a cy.wait() between each viewport change so you can see
26+
// the change otherwise it is a little too fast to see :)
27+
28+
cy.viewport('macbook-15')
29+
cy.wait(200)
30+
cy.viewport('macbook-13')
31+
cy.wait(200)
32+
cy.viewport('macbook-11')
33+
cy.wait(200)
34+
cy.viewport('ipad-2')
35+
cy.wait(200)
36+
cy.viewport('ipad-mini')
37+
cy.wait(200)
38+
cy.viewport('iphone-6+')
39+
cy.wait(200)
40+
cy.viewport('iphone-6')
41+
cy.wait(200)
42+
cy.viewport('iphone-5')
43+
cy.wait(200)
44+
cy.viewport('iphone-4')
45+
cy.wait(200)
46+
cy.viewport('iphone-3')
47+
cy.wait(200)
48+
49+
// cy.viewport() accepts an orientation for all presets
50+
// the default orientation is 'portrait'
51+
cy.viewport('ipad-2', 'portrait')
52+
cy.wait(200)
53+
cy.viewport('iphone-4', 'landscape')
54+
cy.wait(200)
55+
56+
// The viewport will be reset back to the default dimensions
57+
// in between tests (the default can be set in cypress.json)
58+
})
59+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/// <reference types="cypress" />
2+
3+
context('Waiting', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/waiting')
6+
})
7+
// BE CAREFUL of adding unnecessary wait times.
8+
// https://on.cypress.io/best-practices#Unnecessary-Waiting
9+
10+
// https://on.cypress.io/wait
11+
it('cy.wait() - wait for a specific amount of time', () => {
12+
cy.get('.wait-input1').type('Wait 1000ms after typing')
13+
cy.wait(1000)
14+
cy.get('.wait-input2').type('Wait 1000ms after typing')
15+
cy.wait(1000)
16+
cy.get('.wait-input3').type('Wait 1000ms after typing')
17+
cy.wait(1000)
18+
})
19+
20+
it('cy.wait() - wait for a specific route', () => {
21+
// Listen to GET to comments/1
22+
cy.intercept('GET', '**/comments/*').as('getComment')
23+
24+
// we have code that gets a comment when
25+
// the button is clicked in scripts.js
26+
cy.get('.network-btn').click()
27+
28+
// wait for GET comments/1
29+
cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
30+
})
31+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference types="cypress" />
2+
3+
context('Window', () => {
4+
beforeEach(() => {
5+
cy.visit('https://example.cypress.io/commands/window')
6+
})
7+
8+
it('cy.window() - get the global window object', () => {
9+
// https://on.cypress.io/window
10+
cy.window().should('have.property', 'top')
11+
})
12+
13+
it('cy.document() - get the document object', () => {
14+
// https://on.cypress.io/document
15+
cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
16+
})
17+
18+
it('cy.title() - get the title', () => {
19+
// https://on.cypress.io/title
20+
cy.title().should('include', 'Kitchen Sink')
21+
})
22+
})

‎cypress/plugins/index.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference types="cypress" />
2+
// ***********************************************************
3+
// This example plugins/index.js can be used to load plugins
4+
//
5+
// You can change the location of this file or turn off loading
6+
// the plugins file with the 'pluginsFile' configuration option.
7+
//
8+
// You can read more here:
9+
// https://on.cypress.io/plugins-guide
10+
// ***********************************************************
11+
12+
// This function is called when a project is opened or re-opened (e.g. due to
13+
// the project's config changing)
14+
15+
/**
16+
* @type {Cypress.PluginConfig}
17+
*/
18+
// eslint-disable-next-line no-unused-vars
19+
module.exports = (on, config) => {
20+
// `on` is used to hook into various events Cypress emits
21+
// `config` is the resolved Cypress config
22+
}

‎cypress/support/commands.js

+25
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 will overwrite an existing command --
25+
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

‎cypress/support/index.js

+20
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')

‎package.json

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"build": "next build",
77
"start": "next start",
88
"lint": "next lint",
9+
"cypress:open": "cypress open",
910
"storybook": "start-storybook -p 4000",
1011
"build-storybook": "build-storybook"
1112
},
@@ -15,19 +16,24 @@
1516
"react-dom": "17.0.2"
1617
},
1718
"devDependencies": {
19+
"@babel/core": "^7.16.7",
1820
"@storybook/addon-actions": "^6.5.0-alpha.5",
1921
"@storybook/addon-essentials": "^6.5.0-alpha.5",
2022
"@storybook/addon-links": "^6.5.0-alpha.5",
2123
"@storybook/addon-postcss": "^2.0.0",
2224
"@storybook/react": "^6.5.0-alpha.5",
25+
"@testing-library/cypress": "^8.0.2",
2326
"@types/node": "17.0.6",
2427
"@types/react": "17.0.38",
2528
"@typescript-eslint/eslint-plugin": "^5.8.1",
2629
"@typescript-eslint/parser": "^5.8.1",
2730
"autoprefixer": "^10.4.1",
31+
"babel-loader": "^8.2.3",
32+
"cypress": "^9.2.0",
2833
"eslint": "^8.6.0",
2934
"eslint-config-next": "12.0.7",
3035
"eslint-config-prettier": "^8.3.0",
36+
"eslint-plugin-cypress": "^2.12.1",
3137
"eslint-plugin-prettier": "^4.0.0",
3238
"eslint-plugin-react": "^7.28.0",
3339
"eslint-plugin-storybook": "^0.5.5",

‎tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "es5",
4+
"types": ["cypress", "@testing-library/cypress"],
45
"lib": ["dom", "dom.iterable", "esnext"],
56
"allowJs": true,
67
"skipLibCheck": true,

0 commit comments

Comments
 (0)
Please sign in to comment.