Skip to content

Commit d847320

Browse files
committedMar 13, 2020
+ article & cypress test
1 parent 883bf90 commit d847320

37 files changed

+3198
-2111
lines changed
 

‎README.md

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
1-
## Testing the test system: CodeceptJS & Playwright
1+
## Testing the test systems: CodeceptJS + Playwright vs Cypress
22

3-
[CodeceptJS](https://codecept.io/) & [Playwright](https://github.com/microsoft/playwright) seems like a good combo for automation testing. Here we'll log into Facebook & start the process to enter a new event.
3+
[CodeceptJS](https://codecept.io/) & [Playwright](https://github.com/microsoft/playwright) seems like a good, though newer, combo for automation testing. [Cypress](https://github.com/cypress-io/cypress) is mature and has a slick GUI. I used this repo to test the testers, then wrote about on Dev.io.
44

55
### Usage
66

7-
First, copy `SECRETS-template.js` to `SECRETS.js`, then edit the new file with your information. Run the following:
7+
```bash
8+
git clone https://github.com/tomByrer/codecept-playwright-vs-cypressio-fb.git
9+
cd codecept-playwright-vs-cypressio-fb
10+
```
11+
12+
Then copy `SECRETS-template.js` to `SECRETS.js`, and edit the new file with your information.
813

914
```bash
1015
npm i
11-
npx codeceptjs run --steps
1216
```
1317

14-
This will run the test, fill the first field of the event entry form, then pause with an `I.` prompt. Here you can enter more tests; read the `login_test.js` for some ideas, or [read the docs](https://codecept.io/helpers/Playwright/#configuration). When you want to quit the prompt, hit `CTRL-C` 2 times.
18+
#### Run Cypress test
19+
20+
`npm run test-cypress`
21+
22+
After a few seconds, you should see a small window. Change the browser in the uupper-right, close the `examples` folder, & click to run the `fb-start-event_test`:
23+
24+
![cypress boot window](/pic/cypress-start-test-directions.png)
25+
26+
Wait a few more seconds. After the test runs, you should see something like this:
27+
28+
![cypress test ran](/pic/cypress-test-ran-smaller.png)
29+
30+
If so, you're sucessful! If you did not get that far, close the windows and try to re-run; using a busy production website may have lag.
31+
32+
#### Run Codecept test
33+
34+
`npm run test-codecept`
35+
36+
This will run the test, fill the first field of the event entry form, then pause with an `I.` prompt. Here you can enter more tests; read the `cp-fb-start-event_test.js` for some ideas, or [read the docs](https://codecept.io/helpers/Playwright/#configuration). Here is my output with VSCode CLI:
37+
38+
![CodeceptJS running with Playwright](/pic/codecept-test.png)

‎article.md

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
![logo](pic/codeceptjs-vs-cypress.png)
2+
3+
# Testing testers: CodeceptJS + Playwright vs CypressIO
4+
5+
## Why Test
6+
7+
If you are just writing short, quick, throwaway code, you don't need a fancy testing system. Make a quick demo or 3, put the expected output in your README, & move on. You're just going to torture your future self & others in having to download a bunch of testing software, and contribute to global warming for no good reason.
8+
9+
But if you want to work on your own code later, have others to help out, or want to sell or support your hard work, you need more structured testing. The investment will pay off in the long run. There are reasons why it is called "Quality Assurance".
10+
11+
12+
## Testing Types Terminology
13+
14+
There are [many](https://www.pcloudy.com/functional-testing-vs-non-functional-testing/) [many](https://www.testingxperts.com/blog/types-of-software-testing) types of testing! The first stages of testing is grouped under a "functional" where you verify the code's output. (Though there might be UX testing on mockps before the programmer starts.)
15+
16+
### Basic Functional Testing Levels
17+
18+
+ [Unit](https://dzone.com/articles/unit-testing-guidelines-what-to-test-and-what-not) (atomic low level)
19+
+ [Integration](https://www.techrepublic.com/article/integration-testing-will-show-you-how-well-your-modules-get-along/) (mid level checking process flow, [most important](https://kentcdodds.com/blog/write-tests) to some)
20+
+ [End-to-End](https://www.ranorex.com/blog/10-best-practices-test-automation-9-plan-e2e-testing/) (higher level, on deployed code or developer sandbox)
21+
22+
There might be slight overlaps between these levels. But there is greater cross-over with other types of testing.
23+
24+
### Other QA Testing
25+
26+
+ [Component](http://datasift.github.io/storyplayer/v2/learn/test-your-code/why-component-testing.html) (mid level, isolated using dummy data to check functionality)
27+
+ [Regression](https://www.seguetech.com/regression-testing-quality-assurance/) (see if new code degrades rest of app: mid to E2E levels)
28+
+ [Sanity](http://tryqa.com/what-is-sanity-testing/) (focused regression subset, integration-level crossover)
29+
+ [Smoke](https://www.edureka.co/blog/what-is-smoke-testing/) (quickly ensure basic functions are working before builds)
30+
+ [Load](https://www.dotcom-tools.com/load-testing-performance-tools.aspx) &/or Stress (can your service handle a title wave of customers?)
31+
+ [Security/Vulnerablity](https://blog.cobalt.io/a-managers-guide-to-selecting-the-best-testing-approach-for-your-application-security-needs-2a31437bcfd8) (sometimes overlooked until too late...)
32+
+ [Session+Based+Exploratory](https://www.testim.io/blog/exploratory-testing/) (~1 hour focused on a single goal)
33+
+ [Exploratory](https://www.atlassian.com/continuous-delivery/software-testing/exploratory-testing) (1-2 hours of manual digging for non-obvious errors)
34+
+ [Usablity](https://www.usability.gov/how-to-and-tools/methods/usability-testing.html) (UX works for everyone, including less-abled)
35+
+ [System](https://www.guru99.com/system-testing.html) validating everything above at a high level
36+
+ Beta/[User Acceptance](https://usersnap.com/blog/user-acceptance-testing-right/) (actual target users verify if it fits their needs in normal scenarios or automated to check off shareholder's requirements)
37+
38+
Wow, that is quite a bit to digest! Follow the links if you want to dig deeper; each type is worth a full article.
39+
40+
![scientists](/pic/science-in-hd-E1eCQdiO_E4-unsplash.jpg)<br>
41+
<small>Obligatory semi-related Unsplash image, credit [Science in HD](https://unsplash.com/@scienceinhd)</small>
42+
43+
## Why worry about End-to-End testing?
44+
45+
I want to focus on is automating some End-to-End (E2E) testing. This higher level may be ran as Sanity or Smoke or Regression testing before code is merged into the 'master' code base. It can be part of User Acceptance, maybe even usability or even Security testing.. Plus E2E can be difficult and expensive to run. Since there are so many parts of a code base running together, there is more opportunities something can go wrong, so testing costs can be higher.
46+
47+
But there are many rewards to automated End-to-End testing:
48+
49+
* finding bugs that lower-level tests might not
50+
* help document what had been discovered by manual testing
51+
* save money and time
52+
53+
54+
## End-to-End Testing in JavaScript
55+
56+
Since I already know JavaScript, I looked around to see if there were any frameworks or toolkits to conduct in-browser automated testing. Handy that JS runs both inside the browser and as a server/desktop runtime.
57+
58+
Looking around, it seemed [Cypress](https://www.cypress.io/) is one of the top contenders. But I also wanted to check out the new 'web automation' tool: [Playwright](https://github.com/microsoft/playwright#playwright). It allows the three major browsers on the three major operating systems run scripts via JavaScript. While the Playwright GitHub repo is only a few months old (started 2019-11-10) many of the founding devs worked on a similar automated browser, Puppeteer, so there is already years of experience behind that young project. Searching on YouTube for videos for Playwright, I found that [CodeceptJS](https://codecept.io/helpers/Playwright/#configuration)) test runner works very nicely together.
59+
60+
61+
## Quick Cypress vs CodeceptJS+Playwright Comparison
62+
63+
I wanted to try them both out on a.... not simple website, so I chose Facebook. :) FB is familiar to many, and because of its many features FB does not run butter smooth all the time. You could consider it 'legacy', which is dev-talk for several dev teams evolved the code over a long time. So the code is not consistent in technique or optimizations. (eg inconstant selectors)
64+
65+
My example code is short, only 1 E2E test with the following steps:
66+
67+
1. Login via email, password, button click
68+
2. Click first `Create Event` button
69+
3. Click `Create Private Event` button
70+
4. Fill in first field
71+
5. `pause`, to halt the test and enter interactive mode
72+
73+
My code for both testing frameworks is here:
74+
https://github.com/tomByrer/codecept-playwright-vs-cypressio-fb
75+
76+
77+
### CypressIO
78+
79+
![example Cypress output](/pic/cypress-test-ran-smaller.png)
80+
81+
#### Advantages
82+
83+
+ Parallelized runner to complete tests faster
84+
+ [Built-in GUI](https://docs.cypress.io/guides/overview/why-cypress.html#Writing-tests) to see steps right next to the running browser
85+
+ [Commercial product](https://www.cypress.io/pricing) with a generous free tier & free for OSS. Cash flow = good support
86+
+ Mature codebase
87+
+ [Fancy dashbord](https://www.cypress.io/dashboard) brings me fond memories of BrowserStack
88+
+ [Bundled Tools](https://docs.cypress.io/guides/references/bundled-tools.html): popular testing programs like Mocha, Chai, Chai-jQuery, Sinon.JS, minimatch, MomentJS, etc.
89+
90+
#### Disadvantages
91+
92+
- [Listed Trade-offs](https://docs.cypress.io/guides/references/trade-offs.html#Permanent-trade-offs-1)
93+
- not a general purpose automation tool
94+
- NOT a true 'headless browser', not good for: web-scraping, scripting 3rd party sites, performance testing, etc
95+
- always runs inside of a browser (which can also be a pro)
96+
- no multiple browser tabs.
97+
- can't drive two browsers at the same time
98+
- `same-origin` only
99+
- worse than CodeceptJS in a few areas
100+
- runs in Electron, using a slightly older version of Chromium. Firefox support is in Beta, & I don't see any Safari or mobile support
101+
- cold-starts seems slower on my machine (i5, NVMe SSD, 32Gb RAM, Win10)
102+
- selecting elements without id or non-dynamic class name is more difficult, so building my short test took longer than CodeceptJS
103+
104+
### CodeceptJS with Playwright
105+
106+
![CodeceptJS running with Playwright](/pic/codecept-test.png)
107+
108+
#### Advantages
109+
110+
+ totally free
111+
+ [consulting help](https://sdclabs.com/codeceptjs) is available
112+
+ around for years, Codecept-PHP & Puppeter were prior versions
113+
+ CodeceptJS can [use **many** browser configs](https://github.com/Codeception/CodeceptJS#supercharged-e2e-testing): Puppeteer, Playwright (nearly full Firefox & Safari compatibility on all desktops), WebDriver via webdriverio or Protractor, TestCafe, Nightmare, and Appium or Detox for mobile tests; very flexible
114+
+ Playwright keeps "browsers are as pure as they can be", so tests are accurately reflecting real users' browsers
115+
+ better for uses other than 'in-house e2e testing
116+
+ good choice for 3rd party site testing, web scraping
117+
+ I prefer Codecept's more terse syntax of including selectors with actions,
118+
+ Codecept's selecting via text (eg button labels) made building tests quick and fun
119+
+ fast cold-starts
120+
121+
#### Disadvantages vs Cypress
122+
123+
- CodeceptJS has a [smaller dev team](https://github.com/Codeception/CodeceptJS/graphs/contributors); basically only one guy with a second helping. Main dev is productive though; [docs are frequently updated](https://github.com/codecept-js/website/graphs/commit-activity) & he is constantly adding something somewhere
124+
- CodeceptJS' GUI is [very young](https://github.com/codecept-js/ui), though the text-only approach is more than fine, since you can `.pause()` and try new test commands via CLI
125+
- seems to run slower than Cypress (in the little usage I have), I had to add more longer `wait`s to get the page to load enough for next stage
126+
- neither packages are as popular; fewer stars on Github, less training videos around, fewer blog posts, though they both are *popular enough* to get going and find support
127+
128+
## Conclusion: Cypress or CodeceptJS?
129+
130+
My gut feeling (as of March 2020) is to go with CodeceptJS with Playwright; they both are flexible, and a strong team together. Using Playwright to cover Chrome & their siblings plus FIrefox plus Webkit is nice. Plus, if you need mobile testing, or get tired of Playwright for some reason, CodeceptJS makes it convenient to switch browser platforms. CodeceptJS feels feature rich, and is still growing. Also, reducing the need to bother the developers to add testing-only classes or data-attributes could reduce costs. I suspect (with my limited research) that Codecept can be used outside of End-to-End testing.
131+
132+
However, if you have a more junior test team, prefer to use a product backed by a wider commercial venture, &/or like a more mature GUI, then CypressIO is the way to go. Even though tests may take a bit longer to build, it feels Cypress is slightly but consistently faster and smoother to actually run the tests after the cold start. Their GUI+browser angle relieved some of the testing stress. Cypress is a solid choice as well.
133+
134+
*Aside*: if you get tired of Codecept, you *could* use Playwright directly, but you'll have to rewrite the tests. But I feel Codecept makes life more tester-friendly, so it is worth the extra overhead.
135+
136+
I hope you enjoyed this overview! Please add comments & suggestions below.
137+

‎codecept.conf.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ setHeadlessWhen(process.env.HEADLESS);
66

77
exports.config = {
88
tests: './*_test.js',
9-
output: './test-output',
9+
output: './codecept-test-output',
1010
helpers: {
1111
Playwright: {
1212
url: 'https://facebook.com',

‎login_test.js renamed to ‎cp-fb-start-event_test.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
// CodeceptJS example
12
const secrets = require('./SECRETS')
23

3-
Feature('login')
4+
Feature('Add event')
45

5-
Scenario('Add event', (I) => {
6+
Scenario('start entering Create Private Event form', (I) => {
67
I.amOnPage('https://facebook.com')
78
I.fillField('#email', secrets.email)
89
I.fillField(`#pass`, secrets.pass)
910
I.click(`#loginbutton`)
10-
I.click(`Events`)
11-
I.waitInUrl(`/events`,5)
12-
I.wait(3)
11+
I.wait(1)
12+
I.click(`Events`) // or data-testid="left_nav_item_Events"
13+
I.waitInUrl(`/events`,2)
14+
I.wait(1)
1315
I.click(`Create Event`)
1416
I.click(`Create Private Event`)
1517
// I.waitForText(`You're creating a private event`, 15)

‎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,298 @@
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+
cy.get('.action-form').submit()
63+
.next().should('contain', 'Your form has been submitted!')
64+
})
65+
66+
it('.click() - click on a DOM element', () => {
67+
// https://on.cypress.io/click
68+
cy.get('.action-btn').click()
69+
70+
// You can click on 9 specific positions of an element:
71+
// -----------------------------------
72+
// | topLeft top topRight |
73+
// | |
74+
// | |
75+
// | |
76+
// | left center right |
77+
// | |
78+
// | |
79+
// | |
80+
// | bottomLeft bottom bottomRight |
81+
// -----------------------------------
82+
83+
// clicking in the center of the element is the default
84+
cy.get('#action-canvas').click()
85+
86+
cy.get('#action-canvas').click('topLeft')
87+
cy.get('#action-canvas').click('top')
88+
cy.get('#action-canvas').click('topRight')
89+
cy.get('#action-canvas').click('left')
90+
cy.get('#action-canvas').click('right')
91+
cy.get('#action-canvas').click('bottomLeft')
92+
cy.get('#action-canvas').click('bottom')
93+
cy.get('#action-canvas').click('bottomRight')
94+
95+
// .click() accepts an x and y coordinate
96+
// that controls where the click occurs :)
97+
98+
cy.get('#action-canvas')
99+
.click(80, 75) // click 80px on x coord and 75px on y coord
100+
.click(170, 75)
101+
.click(80, 165)
102+
.click(100, 185)
103+
.click(125, 190)
104+
.click(150, 185)
105+
.click(170, 165)
106+
107+
// click multiple elements by passing multiple: true
108+
cy.get('.action-labels>.label').click({ multiple: true })
109+
110+
// Ignore error checking prior to clicking
111+
cy.get('.action-opacity>.btn').click({ force: true })
112+
})
113+
114+
it('.dblclick() - double click on a DOM element', () => {
115+
// https://on.cypress.io/dblclick
116+
117+
// Our app has a listener on 'dblclick' event in our 'scripts.js'
118+
// that hides the div and shows an input on double click
119+
cy.get('.action-div').dblclick().should('not.be.visible')
120+
cy.get('.action-input-hidden').should('be.visible')
121+
})
122+
123+
it('.rightclick() - right click on a DOM element', () => {
124+
// https://on.cypress.io/rightclick
125+
126+
// Our app has a listener on 'contextmenu' event in our 'scripts.js'
127+
// that hides the div and shows an input on right click
128+
cy.get('.rightclick-action-div').rightclick().should('not.be.visible')
129+
cy.get('.rightclick-action-input-hidden').should('be.visible')
130+
})
131+
132+
it('.check() - check a checkbox or radio element', () => {
133+
// https://on.cypress.io/check
134+
135+
// By default, .check() will check all
136+
// matching checkbox or radio elements in succession, one after another
137+
cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]')
138+
.check().should('be.checked')
139+
140+
cy.get('.action-radios [type="radio"]').not('[disabled]')
141+
.check().should('be.checked')
142+
143+
// .check() accepts a value argument
144+
cy.get('.action-radios [type="radio"]')
145+
.check('radio1').should('be.checked')
146+
147+
// .check() accepts an array of values
148+
cy.get('.action-multiple-checkboxes [type="checkbox"]')
149+
.check(['checkbox1', 'checkbox2']).should('be.checked')
150+
151+
// Ignore error checking prior to checking
152+
cy.get('.action-checkboxes [disabled]')
153+
.check({ force: true }).should('be.checked')
154+
155+
cy.get('.action-radios [type="radio"]')
156+
.check('radio3', { force: true }).should('be.checked')
157+
})
158+
159+
it('.uncheck() - uncheck a checkbox element', () => {
160+
// https://on.cypress.io/uncheck
161+
162+
// By default, .uncheck() will uncheck all matching
163+
// checkbox elements in succession, one after another
164+
cy.get('.action-check [type="checkbox"]')
165+
.not('[disabled]')
166+
.uncheck().should('not.be.checked')
167+
168+
// .uncheck() accepts a value argument
169+
cy.get('.action-check [type="checkbox"]')
170+
.check('checkbox1')
171+
.uncheck('checkbox1').should('not.be.checked')
172+
173+
// .uncheck() accepts an array of values
174+
cy.get('.action-check [type="checkbox"]')
175+
.check(['checkbox1', 'checkbox3'])
176+
.uncheck(['checkbox1', 'checkbox3']).should('not.be.checked')
177+
178+
// Ignore error checking prior to unchecking
179+
cy.get('.action-check [disabled]')
180+
.uncheck({ force: true }).should('not.be.checked')
181+
})
182+
183+
it('.select() - select an option in a <select> element', () => {
184+
// https://on.cypress.io/select
185+
186+
// at first, no option should be selected
187+
cy.get('.action-select')
188+
.should('have.value', '--Select a fruit--')
189+
190+
// Select option(s) with matching text content
191+
cy.get('.action-select').select('apples')
192+
// confirm the apples were selected
193+
// note that each value starts with "fr-" in our HTML
194+
cy.get('.action-select').should('have.value', 'fr-apples')
195+
196+
cy.get('.action-select-multiple')
197+
.select(['apples', 'oranges', 'bananas'])
198+
// when getting multiple values, invoke "val" method first
199+
.invoke('val')
200+
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
201+
202+
// Select option(s) with matching value
203+
cy.get('.action-select').select('fr-bananas')
204+
// can attach an assertion right away to the element
205+
.should('have.value', 'fr-bananas')
206+
207+
cy.get('.action-select-multiple')
208+
.select(['fr-apples', 'fr-oranges', 'fr-bananas'])
209+
.invoke('val')
210+
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
211+
// assert the selected values include oranges
212+
cy.get('.action-select-multiple')
213+
.invoke('val').should('include', 'fr-oranges')
214+
})
215+
216+
it('.scrollIntoView() - scroll an element into view', () => {
217+
// https://on.cypress.io/scrollintoview
218+
219+
// normally all of these buttons are hidden,
220+
// because they're not within
221+
// the viewable area of their parent
222+
// (we need to scroll to see them)
223+
cy.get('#scroll-horizontal button')
224+
.should('not.be.visible')
225+
226+
// scroll the button into view, as if the user had scrolled
227+
cy.get('#scroll-horizontal button').scrollIntoView()
228+
.should('be.visible')
229+
230+
cy.get('#scroll-vertical button')
231+
.should('not.be.visible')
232+
233+
// Cypress handles the scroll direction needed
234+
cy.get('#scroll-vertical button').scrollIntoView()
235+
.should('be.visible')
236+
237+
cy.get('#scroll-both button')
238+
.should('not.be.visible')
239+
240+
// Cypress knows to scroll to the right and down
241+
cy.get('#scroll-both button').scrollIntoView()
242+
.should('be.visible')
243+
})
244+
245+
it('.trigger() - trigger an event on a DOM element', () => {
246+
// https://on.cypress.io/trigger
247+
248+
// To interact with a range input (slider)
249+
// we need to set its value & trigger the
250+
// event to signal it changed
251+
252+
// Here, we invoke jQuery's val() method to set
253+
// the value and trigger the 'change' event
254+
cy.get('.trigger-input-range')
255+
.invoke('val', 25)
256+
.trigger('change')
257+
.get('input[type=range]').siblings('p')
258+
.should('have.text', '25')
259+
})
260+
261+
it('cy.scrollTo() - scroll the window or element to a position', () => {
262+
263+
// https://on.cypress.io/scrollTo
264+
265+
// You can scroll to 9 specific positions of an element:
266+
// -----------------------------------
267+
// | topLeft top topRight |
268+
// | |
269+
// | |
270+
// | |
271+
// | left center right |
272+
// | |
273+
// | |
274+
// | |
275+
// | bottomLeft bottom bottomRight |
276+
// -----------------------------------
277+
278+
// if you chain .scrollTo() off of cy, we will
279+
// scroll the entire window
280+
cy.scrollTo('bottom')
281+
282+
cy.get('#scrollable-horizontal').scrollTo('right')
283+
284+
// or you can scroll to a specific coordinate:
285+
// (x axis, y axis) in pixels
286+
cy.get('#scrollable-vertical').scrollTo(250, 250)
287+
288+
// or you can scroll to a specific percentage
289+
// of the (width, height) of the element
290+
cy.get('#scrollable-both').scrollTo('75%', '25%')
291+
292+
// control the easing of the scroll (default is 'swing')
293+
cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' })
294+
295+
// control the duration of the scroll (in ms)
296+
cy.get('#scrollable-both').scrollTo('center', { duration: 2000 })
297+
})
298+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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+
30+
// Alias the route to wait for its response
31+
cy.server()
32+
cy.route('GET', 'comments/*').as('getComment')
33+
34+
// we have code that gets a comment when
35+
// the button is clicked in scripts.js
36+
cy.get('.network-btn').click()
37+
38+
// https://on.cypress.io/wait
39+
cy.wait('@getComment').its('status').should('eq', 200)
40+
41+
})
42+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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+
})
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,78 @@
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+
31+
// each cookie has these properties
32+
expect(cookies[0]).to.have.property('name', 'token')
33+
expect(cookies[0]).to.have.property('value', '123ABC')
34+
expect(cookies[0]).to.have.property('httpOnly', false)
35+
expect(cookies[0]).to.have.property('secure', false)
36+
expect(cookies[0]).to.have.property('domain')
37+
expect(cookies[0]).to.have.property('path')
38+
})
39+
})
40+
41+
it('cy.setCookie() - set a browser cookie', () => {
42+
// https://on.cypress.io/setcookie
43+
cy.getCookies().should('be.empty')
44+
45+
cy.setCookie('foo', 'bar')
46+
47+
// cy.getCookie() yields a cookie object
48+
cy.getCookie('foo').should('have.property', 'value', 'bar')
49+
})
50+
51+
it('cy.clearCookie() - clear a browser cookie', () => {
52+
// https://on.cypress.io/clearcookie
53+
cy.getCookie('token').should('be.null')
54+
55+
cy.get('#clearCookie .set-a-cookie').click()
56+
57+
cy.getCookie('token').should('have.property', 'value', '123ABC')
58+
59+
// cy.clearCookies() yields null
60+
cy.clearCookie('token').should('be.null')
61+
62+
cy.getCookie('token').should('be.null')
63+
})
64+
65+
it('cy.clearCookies() - clear browser cookies', () => {
66+
// https://on.cypress.io/clearcookies
67+
cy.getCookies().should('be.empty')
68+
69+
cy.get('#clearCookies .set-a-cookie').click()
70+
71+
cy.getCookies().should('have.length', 1)
72+
73+
// cy.clearCookies() yields null
74+
cy.clearCookies()
75+
76+
cy.getCookies().should('be.empty')
77+
})
78+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
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+
38+
context('Cypress.Cookies', () => {
39+
beforeEach(() => {
40+
cy.visit('https://example.cypress.io/cypress-api')
41+
})
42+
43+
// https://on.cypress.io/cookies
44+
it('.debug() - enable or disable debugging', () => {
45+
Cypress.Cookies.debug(true)
46+
47+
// Cypress will now log in the console when
48+
// cookies are set or cleared
49+
cy.setCookie('fakeCookie', '123ABC')
50+
cy.clearCookie('fakeCookie')
51+
cy.setCookie('fakeCookie', '123ABC')
52+
cy.clearCookie('fakeCookie')
53+
cy.setCookie('fakeCookie', '123ABC')
54+
})
55+
56+
it('.preserveOnce() - preserve cookies by key', () => {
57+
// normally cookies are reset after each test
58+
cy.getCookie('fakeCookie').should('not.be.ok')
59+
60+
// preserving a cookie will not clear it when
61+
// the next test starts
62+
cy.setCookie('lastCookie', '789XYZ')
63+
Cypress.Cookies.preserveOnce('lastCookie')
64+
})
65+
66+
it('.defaults() - set defaults for all cookies', () => {
67+
// now any cookie with the name 'session_id' will
68+
// not be cleared before each new test runs
69+
Cypress.Cookies.defaults({
70+
whitelist: 'session_id',
71+
})
72+
})
73+
})
74+
75+
76+
context('Cypress.Server', () => {
77+
beforeEach(() => {
78+
cy.visit('https://example.cypress.io/cypress-api')
79+
})
80+
81+
// Permanently override server options for
82+
// all instances of cy.server()
83+
84+
// https://on.cypress.io/cypress-server
85+
it('.defaults() - change default config of server', () => {
86+
Cypress.Server.defaults({
87+
delay: 0,
88+
force404: false,
89+
})
90+
})
91+
})
92+
93+
context('Cypress.arch', () => {
94+
beforeEach(() => {
95+
cy.visit('https://example.cypress.io/cypress-api')
96+
})
97+
98+
it('Get CPU architecture name of underlying OS', () => {
99+
// https://on.cypress.io/arch
100+
expect(Cypress.arch).to.exist
101+
})
102+
})
103+
104+
context('Cypress.config()', () => {
105+
beforeEach(() => {
106+
cy.visit('https://example.cypress.io/cypress-api')
107+
})
108+
109+
it('Get and set configuration options', () => {
110+
// https://on.cypress.io/config
111+
let myConfig = Cypress.config()
112+
113+
expect(myConfig).to.have.property('animationDistanceThreshold', 5)
114+
expect(myConfig).to.have.property('baseUrl', null)
115+
expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
116+
expect(myConfig).to.have.property('requestTimeout', 5000)
117+
expect(myConfig).to.have.property('responseTimeout', 30000)
118+
expect(myConfig).to.have.property('viewportHeight', 660)
119+
expect(myConfig).to.have.property('viewportWidth', 1000)
120+
expect(myConfig).to.have.property('pageLoadTimeout', 60000)
121+
expect(myConfig).to.have.property('waitForAnimations', true)
122+
123+
expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
124+
125+
// this will change the config for the rest of your tests!
126+
Cypress.config('pageLoadTimeout', 20000)
127+
128+
expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
129+
130+
Cypress.config('pageLoadTimeout', 60000)
131+
})
132+
})
133+
134+
context('Cypress.dom', () => {
135+
beforeEach(() => {
136+
cy.visit('https://example.cypress.io/cypress-api')
137+
})
138+
139+
// https://on.cypress.io/dom
140+
it('.isHidden() - determine if a DOM element is hidden', () => {
141+
let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
142+
let visibleP = Cypress.$('.dom-p p.visible').get(0)
143+
144+
// our first paragraph has css class 'hidden'
145+
expect(Cypress.dom.isHidden(hiddenP)).to.be.true
146+
expect(Cypress.dom.isHidden(visibleP)).to.be.false
147+
})
148+
})
149+
150+
context('Cypress.env()', () => {
151+
beforeEach(() => {
152+
cy.visit('https://example.cypress.io/cypress-api')
153+
})
154+
155+
// We can set environment variables for highly dynamic values
156+
157+
// https://on.cypress.io/environment-variables
158+
it('Get environment variables', () => {
159+
// https://on.cypress.io/env
160+
// set multiple environment variables
161+
Cypress.env({
162+
host: 'veronica.dev.local',
163+
api_server: 'http://localhost:8888/v1/',
164+
})
165+
166+
// get environment variable
167+
expect(Cypress.env('host')).to.eq('veronica.dev.local')
168+
169+
// set environment variable
170+
Cypress.env('api_server', 'http://localhost:8888/v2/')
171+
expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
172+
173+
// get all environment variable
174+
expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
175+
expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
176+
})
177+
})
178+
179+
context('Cypress.log', () => {
180+
beforeEach(() => {
181+
cy.visit('https://example.cypress.io/cypress-api')
182+
})
183+
184+
it('Control what is printed to the Command Log', () => {
185+
// https://on.cypress.io/cypress-log
186+
})
187+
})
188+
189+
190+
context('Cypress.platform', () => {
191+
beforeEach(() => {
192+
cy.visit('https://example.cypress.io/cypress-api')
193+
})
194+
195+
it('Get underlying OS name', () => {
196+
// https://on.cypress.io/platform
197+
expect(Cypress.platform).to.be.exist
198+
})
199+
})
200+
201+
context('Cypress.version', () => {
202+
beforeEach(() => {
203+
cy.visit('https://example.cypress.io/cypress-api')
204+
})
205+
206+
it('Get current version of Cypress being run', () => {
207+
// https://on.cypress.io/version
208+
expect(Cypress.version).to.be.exist
209+
})
210+
})
211+
212+
context('Cypress.spec', () => {
213+
beforeEach(() => {
214+
cy.visit('https://example.cypress.io/cypress-api')
215+
})
216+
217+
it('Get current spec information', () => {
218+
// https://on.cypress.io/spec
219+
// wrap the object so we can inspect it easily by clicking in the command log
220+
cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute'])
221+
})
222+
})
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
cy.server()
26+
cy.fixture('example.json').as('comment')
27+
// when application makes an Ajax request matching "GET comments/*"
28+
// Cypress will intercept it and reply with object
29+
// from the "comment" alias
30+
cy.route('GET', 'comments/*', '@comment').as('getComment')
31+
32+
// we have code that gets a comment when
33+
// the button is clicked in scripts.js
34+
cy.get('.fixture-btn').click()
35+
36+
cy.wait('@getComment').its('responseBody')
37+
.should('have.property', 'name')
38+
.and('include', 'Using fixtures to represent data')
39+
40+
// you can also just write the fixture in the route
41+
cy.route('GET', 'comments/*', 'fixture:example.json').as('getComment')
42+
43+
// we have code that gets a comment when
44+
// the button is clicked in scripts.js
45+
cy.get('.fixture-btn').click()
46+
47+
cy.wait('@getComment').its('responseBody')
48+
.should('have.property', 'name')
49+
.and('include', 'Using fixtures to represent data')
50+
51+
// or write fx to represent fixture
52+
// by default it assumes it's .json
53+
cy.route('GET', 'comments/*', 'fx:example').as('getComment')
54+
55+
// we have code that gets a comment when
56+
// the button is clicked in scripts.js
57+
cy.get('.fixture-btn').click()
58+
59+
cy.wait('@getComment').its('responseBody')
60+
.should('have.property', 'name')
61+
.and('include', 'Using fixtures to represent data')
62+
})
63+
64+
it('cy.fixture() or require - load a fixture', function () {
65+
// we are inside the "function () { ... }"
66+
// callback and can use test context object "this"
67+
// "this.example" was loaded in "beforeEach" function callback
68+
expect(this.example, 'fixture in the test context')
69+
.to.deep.equal(requiredExample)
70+
71+
// or use "cy.wrap" and "should('deep.equal', ...)" assertion
72+
// @ts-ignore
73+
cy.wrap(this.example, 'fixture vs require')
74+
.should('deep.equal', requiredExample)
75+
})
76+
77+
it('cy.readFile() - read file contents', () => {
78+
// https://on.cypress.io/readfile
79+
80+
// You can read a file and yield its contents
81+
// The filePath is relative to your project's root.
82+
cy.readFile('cypress.json').then((json) => {
83+
expect(json).to.be.an('object')
84+
})
85+
})
86+
87+
it('cy.writeFile() - write to a file', () => {
88+
// https://on.cypress.io/writefile
89+
90+
// You can write to a file
91+
92+
// Use a response from a request to automatically
93+
// generate a fixture file for use later
94+
cy.request('https://jsonplaceholder.cypress.io/users')
95+
.then((response) => {
96+
cy.writeFile('cypress/fixtures/users.json', response.body)
97+
})
98+
cy.fixture('users').should((users) => {
99+
expect(users[0].name).to.exist
100+
})
101+
102+
// JavaScript arrays and objects are stringified
103+
// and formatted into text.
104+
cy.writeFile('cypress/fixtures/profile.json', {
105+
id: 8739,
106+
name: 'Jane',
107+
email: 'jane@example.com',
108+
})
109+
110+
cy.fixture('profile').should((profile) => {
111+
expect(profile.name).to.eq('Jane')
112+
})
113+
})
114+
})
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+
// Clear key matching string in Local Storage
27+
cy.get('.ls-btn').click().should(() => {
28+
expect(localStorage.getItem('prop1')).to.eq('red')
29+
expect(localStorage.getItem('prop2')).to.eq('blue')
30+
expect(localStorage.getItem('prop3')).to.eq('magenta')
31+
})
32+
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+
// Clear keys matching regex in Local Storage
40+
cy.get('.ls-btn').click().should(() => {
41+
expect(localStorage.getItem('prop1')).to.eq('red')
42+
expect(localStorage.getItem('prop2')).to.eq('blue')
43+
expect(localStorage.getItem('prop3')).to.eq('magenta')
44+
})
45+
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+
})
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
return
40+
}
41+
42+
cy.exec('echo Jane Lane')
43+
.its('stdout').should('contain', 'Jane Lane')
44+
45+
if (Cypress.platform === 'win32') {
46+
cy.exec('print cypress.json')
47+
.its('stderr').should('be.empty')
48+
} else {
49+
cy.exec('cat cypress.json')
50+
.its('stderr').should('be.empty')
51+
52+
cy.exec('pwd')
53+
.its('code').should('eq', 0)
54+
}
55+
})
56+
57+
it('cy.focused() - get the DOM element that has focus', () => {
58+
// https://on.cypress.io/focused
59+
cy.get('.misc-form').find('#name').click()
60+
cy.focused().should('have.id', 'name')
61+
62+
cy.get('.misc-form').find('#description').click()
63+
cy.focused().should('have.id', 'description')
64+
})
65+
66+
context('Cypress.Screenshot', function () {
67+
it('cy.screenshot() - take a screenshot', () => {
68+
// https://on.cypress.io/screenshot
69+
cy.screenshot('my-image')
70+
})
71+
72+
it('Cypress.Screenshot.defaults() - change default config of screenshots', function () {
73+
Cypress.Screenshot.defaults({
74+
blackout: ['.foo'],
75+
capture: 'viewport',
76+
clip: { x: 0, y: 0, width: 200, height: 200 },
77+
scale: false,
78+
disableTimersAndAnimations: true,
79+
screenshotOnRunFailure: true,
80+
beforeScreenshot () { },
81+
afterScreenshot () { },
82+
})
83+
})
84+
})
85+
86+
it('cy.wrap() - wrap an object', () => {
87+
// https://on.cypress.io/wrap
88+
cy.wrap({ foo: 'bar' })
89+
.should('have.property', 'foo')
90+
.and('include', 'bar')
91+
})
92+
})
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,195 @@
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 AJAX / XHR requests in your app
9+
10+
it('cy.server() - control behavior of network requests and responses', () => {
11+
// https://on.cypress.io/server
12+
13+
cy.server().should((server) => {
14+
// the default options on server
15+
// you can override any of these options
16+
expect(server.delay).to.eq(0)
17+
expect(server.method).to.eq('GET')
18+
expect(server.status).to.eq(200)
19+
expect(server.headers).to.be.null
20+
expect(server.response).to.be.null
21+
expect(server.onRequest).to.be.undefined
22+
expect(server.onResponse).to.be.undefined
23+
expect(server.onAbort).to.be.undefined
24+
25+
// These options control the server behavior
26+
// affecting all requests
27+
28+
// pass false to disable existing route stubs
29+
expect(server.enable).to.be.true
30+
// forces requests that don't match your routes to 404
31+
expect(server.force404).to.be.false
32+
// whitelists requests from ever being logged or stubbed
33+
expect(server.whitelist).to.be.a('function')
34+
})
35+
36+
cy.server({
37+
method: 'POST',
38+
delay: 1000,
39+
status: 422,
40+
response: {},
41+
})
42+
43+
// any route commands will now inherit the above options
44+
// from the server. anything we pass specifically
45+
// to route will override the defaults though.
46+
})
47+
48+
it('cy.request() - make an XHR request', () => {
49+
// https://on.cypress.io/request
50+
cy.request('https://jsonplaceholder.cypress.io/comments')
51+
.should((response) => {
52+
expect(response.status).to.eq(200)
53+
expect(response.body).to.have.length(500)
54+
expect(response).to.have.property('headers')
55+
expect(response).to.have.property('duration')
56+
})
57+
})
58+
59+
60+
it('cy.request() - verify response using BDD syntax', () => {
61+
cy.request('https://jsonplaceholder.cypress.io/comments')
62+
.then((response) => {
63+
// https://on.cypress.io/assertions
64+
expect(response).property('status').to.equal(200)
65+
expect(response).property('body').to.have.length(500)
66+
expect(response).to.include.keys('headers', 'duration')
67+
})
68+
})
69+
70+
it('cy.request() with query parameters', () => {
71+
// will execute request
72+
// https://jsonplaceholder.cypress.io/comments?postId=1&id=3
73+
cy.request({
74+
url: 'https://jsonplaceholder.cypress.io/comments',
75+
qs: {
76+
postId: 1,
77+
id: 3,
78+
},
79+
})
80+
.its('body')
81+
.should('be.an', 'array')
82+
.and('have.length', 1)
83+
.its('0') // yields first element of the array
84+
.should('contain', {
85+
postId: 1,
86+
id: 3,
87+
})
88+
})
89+
90+
it('cy.request() - pass result to the second request', () => {
91+
// first, let's find out the userId of the first user we have
92+
cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
93+
.its('body') // yields the response object
94+
.its('0') // yields the first element of the returned list
95+
// the above two commands its('body').its('0')
96+
// can be written as its('body.0')
97+
// if you do not care about TypeScript checks
98+
.then((user) => {
99+
expect(user).property('id').to.be.a('number')
100+
// make a new post on behalf of the user
101+
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
102+
userId: user.id,
103+
title: 'Cypress Test Runner',
104+
body: 'Fast, easy and reliable testing for anything that runs in a browser.',
105+
})
106+
})
107+
// note that the value here is the returned value of the 2nd request
108+
// which is the new post object
109+
.then((response) => {
110+
expect(response).property('status').to.equal(201) // new entity created
111+
expect(response).property('body').to.contain({
112+
id: 101, // there are already 100 posts, so new entity gets id 101
113+
title: 'Cypress Test Runner',
114+
})
115+
// we don't know the user id here - since it was in above closure
116+
// so in this test just confirm that the property is there
117+
expect(response.body).property('userId').to.be.a('number')
118+
})
119+
})
120+
121+
it('cy.request() - save response in the shared test context', () => {
122+
// https://on.cypress.io/variables-and-aliases
123+
cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
124+
.its('body').its('0') // yields the first element of the returned list
125+
.as('user') // saves the object in the test context
126+
.then(function () {
127+
// NOTE 👀
128+
// By the time this callback runs the "as('user')" command
129+
// has saved the user object in the test context.
130+
// To access the test context we need to use
131+
// the "function () { ... }" callback form,
132+
// otherwise "this" points at a wrong or undefined object!
133+
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
134+
userId: this.user.id,
135+
title: 'Cypress Test Runner',
136+
body: 'Fast, easy and reliable testing for anything that runs in a browser.',
137+
})
138+
.its('body').as('post') // save the new post from the response
139+
})
140+
.then(function () {
141+
// When this callback runs, both "cy.request" API commands have finished
142+
// and the test context has "user" and "post" objects set.
143+
// Let's verify them.
144+
expect(this.post, 'post has the right user id').property('userId').to.equal(this.user.id)
145+
})
146+
})
147+
148+
it('cy.route() - route responses to matching requests', () => {
149+
// https://on.cypress.io/route
150+
151+
let message = 'whoa, this comment does not exist'
152+
153+
cy.server()
154+
155+
// Listen to GET to comments/1
156+
cy.route('GET', 'comments/*').as('getComment')
157+
158+
// we have code that gets a comment when
159+
// the button is clicked in scripts.js
160+
cy.get('.network-btn').click()
161+
162+
// https://on.cypress.io/wait
163+
cy.wait('@getComment').its('status').should('eq', 200)
164+
165+
// Listen to POST to comments
166+
cy.route('POST', '/comments').as('postComment')
167+
168+
// we have code that posts a comment when
169+
// the button is clicked in scripts.js
170+
cy.get('.network-post').click()
171+
cy.wait('@postComment').should((xhr) => {
172+
expect(xhr.requestBody).to.include('email')
173+
expect(xhr.requestHeaders).to.have.property('Content-Type')
174+
expect(xhr.responseBody).to.have.property('name', 'Using POST in cy.route()')
175+
})
176+
177+
// Stub a response to PUT comments/ ****
178+
cy.route({
179+
method: 'PUT',
180+
url: 'comments/*',
181+
status: 404,
182+
response: { error: message },
183+
delay: 500,
184+
}).as('putComment')
185+
186+
// we have code that puts a comment when
187+
// the button is clicked in scripts.js
188+
cy.get('.network-put').click()
189+
190+
cy.wait('@putComment')
191+
192+
// our 404 statusCode logic in scripts.js executed
193+
cy.get('.network-put-comment').should('contain', message)
194+
})
195+
})
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,95 @@
1+
/// <reference types="cypress" />
2+
3+
context('Spies, Stubs, and Clock', () => {
4+
it('cy.spy() - wrap a method in a spy', () => {
5+
// https://on.cypress.io/spy
6+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
7+
8+
const obj = {
9+
foo () {},
10+
}
11+
12+
const spy = cy.spy(obj, 'foo').as('anyArgs')
13+
14+
obj.foo()
15+
16+
expect(spy).to.be.called
17+
})
18+
19+
it('cy.spy() retries until assertions pass', () => {
20+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
21+
22+
const obj = {
23+
/**
24+
* Prints the argument passed
25+
* @param x {any}
26+
*/
27+
foo (x) {
28+
console.log('obj.foo called with', x)
29+
},
30+
}
31+
32+
cy.spy(obj, 'foo').as('foo')
33+
34+
setTimeout(() => {
35+
obj.foo('first')
36+
}, 500)
37+
38+
setTimeout(() => {
39+
obj.foo('second')
40+
}, 2500)
41+
42+
cy.get('@foo').should('have.been.calledTwice')
43+
})
44+
45+
it('cy.stub() - create a stub and/or replace a function with stub', () => {
46+
// https://on.cypress.io/stub
47+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
48+
49+
const obj = {
50+
/**
51+
* prints both arguments to the console
52+
* @param a {string}
53+
* @param b {string}
54+
*/
55+
foo (a, b) {
56+
console.log('a', a, 'b', b)
57+
},
58+
}
59+
60+
const stub = cy.stub(obj, 'foo').as('foo')
61+
62+
obj.foo('foo', 'bar')
63+
64+
expect(stub).to.be.called
65+
})
66+
67+
it('cy.clock() - control time in the browser', () => {
68+
// https://on.cypress.io/clock
69+
70+
// create the date in UTC so its always the same
71+
// no matter what local timezone the browser is running in
72+
const now = new Date(Date.UTC(2017, 2, 14)).getTime()
73+
74+
cy.clock(now)
75+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
76+
cy.get('#clock-div').click()
77+
.should('have.text', '1489449600')
78+
})
79+
80+
it('cy.tick() - move time in the browser', () => {
81+
// https://on.cypress.io/tick
82+
83+
// create the date in UTC so its always the same
84+
// no matter what local timezone the browser is running in
85+
const now = new Date(Date.UTC(2017, 2, 14)).getTime()
86+
87+
cy.clock(now)
88+
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
89+
cy.get('#tick-div').click()
90+
.should('have.text', '1489449600')
91+
cy.tick(10000) // 10 seconds passed
92+
cy.get('#tick-div').click()
93+
.should('have.text', '1489449610')
94+
})
95+
})
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,133 @@
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+
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+
it('Cypress.minimatch - test out glob patterns against strings', () => {
49+
// https://on.cypress.io/minimatch
50+
let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', {
51+
matchBase: true,
52+
})
53+
54+
expect(matching, 'matching wildcard').to.be.true
55+
56+
matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', {
57+
matchBase: true,
58+
})
59+
expect(matching, 'comments').to.be.false
60+
61+
// ** matches against all downstream path segments
62+
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', {
63+
matchBase: true,
64+
})
65+
expect(matching, 'comments').to.be.true
66+
67+
// whereas * matches only the next path segment
68+
69+
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', {
70+
matchBase: false,
71+
})
72+
expect(matching, 'comments').to.be.false
73+
})
74+
75+
76+
it('Cypress.moment() - format or parse dates using a moment method', () => {
77+
// https://on.cypress.io/moment
78+
const time = Cypress.moment('2014-04-25T19:38:53.196Z').utc().format('h:mm A')
79+
80+
expect(time).to.be.a('string')
81+
82+
cy.get('.utility-moment').contains('3:38 PM')
83+
.should('have.class', 'badge')
84+
85+
// the time in the element should be between 3pm and 5pm
86+
const start = Cypress.moment('3:00 PM', 'LT')
87+
const end = Cypress.moment('5:00 PM', 'LT')
88+
89+
cy.get('.utility-moment .badge')
90+
.should(($el) => {
91+
// parse American time like "3:38 PM"
92+
const m = Cypress.moment($el.text().trim(), 'LT')
93+
94+
// display hours + minutes + AM|PM
95+
const f = 'h:mm A'
96+
97+
expect(m.isBetween(start, end),
98+
`${m.format(f)} should be between ${start.format(f)} and ${end.format(f)}`).to.be.true
99+
})
100+
})
101+
102+
103+
it('Cypress.Promise - instantiate a bluebird promise', () => {
104+
// https://on.cypress.io/promise
105+
let waited = false
106+
107+
/**
108+
* @return Bluebird<string>
109+
*/
110+
function waitOneSecond () {
111+
// return a promise that resolves after 1 second
112+
// @ts-ignore TS2351 (new Cypress.Promise)
113+
return new Cypress.Promise((resolve, reject) => {
114+
setTimeout(() => {
115+
// set waited to true
116+
waited = true
117+
118+
// resolve with 'foo' string
119+
resolve('foo')
120+
}, 1000)
121+
})
122+
}
123+
124+
cy.then(() =>
125+
// return a promise to cy.then() that
126+
// is awaited until it resolves
127+
// @ts-ignore TS7006
128+
waitOneSecond().then((str) => {
129+
expect(str).to.eq('foo')
130+
expect(waited).to.be.true
131+
}))
132+
})
133+
})
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,34 @@
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+
cy.server()
22+
23+
// Listen to GET to comments/1
24+
cy.route('GET', 'comments/*').as('getComment')
25+
26+
// we have code that gets a comment when
27+
// the button is clicked in scripts.js
28+
cy.get('.network-btn').click()
29+
30+
// wait for GET comments/1
31+
cy.wait('@getComment').its('status').should('eq', 200)
32+
})
33+
34+
})
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+
})
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference types="cypress" />
2+
const secrets = require('../../SECRETS')
3+
4+
context('Add event', ()=>{
5+
6+
it('start entering Create Private Event form', ()=>{
7+
cy.visit('https://facebook.com')
8+
.get('#email').type(secrets.email)
9+
.get('#pass').type(secrets.pass)
10+
.get('#loginbutton').click()
11+
.get('[data-testid="left_nav_item_Events"]').click()
12+
.url().should('include', '/events')
13+
.wait(300)
14+
.get('#entity_sidebar > div').eq(2).click() // ugly, but .contains('Create Event') didn't work
15+
.get('.uiContextualLayerBelowLeft > div > div > ul > li').eq(0).click()
16+
.wait(300)
17+
.get('[aria-labelledby="eventCreateTitle"]').type('test: ignore')
18+
.pause()
19+
})
20+
})

‎cypress/plugins/index.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
module.exports = (on, config) => {
19+
// `on` is used to hook into various events Cypress emits
20+
// `config` is the resolved Cypress config
21+
}

‎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-lock.json

+897-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
{
2-
"name": "codecept-playwright-fb",
3-
"version": "1.0.0",
4-
"description": "testing fb landing page with CodeceptJS & MS-Playwright",
2+
"name": "codecept-playwright-vs-cypressio-fb",
3+
"version": "1.1.0",
4+
"description": "testing fb landing page with CodeceptJS + MS-Playwright vs CypressIO",
55
"scripts": {
6-
"test": "npx codeceptjs run --steps"
6+
"test-codecept": "npx codeceptjs run --steps",
7+
"test-cypress": "node_modules/.bin/cypress open"
78
},
89
"repository": {
910
"type": "git",
10-
"url": "git+https://github.com/tomByrer/codecept-playwright-fb.git"
11+
"url": "git+https://github.com/tomByrer/codecept-playwright-vs-cypressio-fb.git"
1112
},
1213
"keywords": [
1314
"CodeceptJS",
1415
"Playwright",
16+
"CypressIO",
1517
"testing"
1618
],
1719
"author": "tom byrer",
1820
"license": "ISC",
1921
"bugs": {
20-
"url": "https://github.com/tomByrer/codecept-playwright-fb/issues"
22+
"url": "https://github.com/tomByrer/codecept-playwright-vs-cypressio-fb/issues"
2123
},
22-
"homepage": "https://github.com/tomByrer/codecept-playwright-fb#readme",
24+
"homepage": "https://github.com/tomByrer/codecept-playwright-vs-cypressio-fb#readme",
2325
"devDependencies": {
2426
"codeceptjs": "^2.5.0",
25-
"playwright": "^0.11.1"
27+
"playwright": "^0.11.1",
28+
"cypress": "^4.1.0"
2629
}
2730
}

‎pic/codecept-test.png

614 KB
Loading

‎pic/codeceptjs-vs-cypress.png

18.7 KB
Loading

‎pic/cypress-start-test-directions.png

45.5 KB
Loading

‎pic/cypress-test-ran-smaller.png

403 KB
Loading
61.7 KB
Loading

‎pnpm-lock.yaml

-2,091
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.