Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Connect.Tech 2020: Advanced Cypress Testing
Search
Jeremy Fairbank
September 28, 2020
Programming
260
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Connect.Tech 2020: Advanced Cypress Testing
Jeremy Fairbank
September 28, 2020
More Decks by Jeremy Fairbank
See All by Jeremy Fairbank
CodeMash 2020: Solving the Boolean Identity Crisis
jfairbank
1
230
CodeMash 2020: Practical Functional Programming
jfairbank
1
370
Connect.Tech 2019: Practical Functional Programming
jfairbank
0
440
Connect.Tech 2019: Solving the Boolean Identity Crisis
jfairbank
0
260
Lambda Squared 2019: Solving the Boolean Identity Crisis
jfairbank
0
200
All Things Open 2018: Practical Functional Programming
jfairbank
2
290
Connect.Tech 2018: Effective React Testing
jfairbank
1
220
Fluent Conf 2018: Building web apps with Elm Tutorial
jfairbank
2
940
Codestock 2018: Building a Resilient, API-driven Front-End with Elm
jfairbank
0
310
Other Decks in Programming
See All in Programming
AIは賢い。でも実行環境は? CLIおじさんがAI時代に伝えたいこと ~ CLIおじさんがAI時代に伝えたいこと ~
curekoshimizu
1
240
Java 27新機能 / Java 27 new features
kishida
2
140
巨大モノリシックアプリ モダン化大作戦
ktcryomm
0
1k
AHC070解法紹介
eijirou
0
120
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
1
270
XP祭りでしか伝わらないフリップネタ #xpjug
murabayashi
0
150
cdk deploy JawsSonic #MARATHONしながらAWSリソースをデプロイしてみよう
akihisaikeda
2
130
App Storeの外へ──日本のiOSサイドローディング入門 for iOSDC Japan 2026
yuukiw00w
0
210
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
0
280
スマート反転とウェブアクセシビリティ
camiha
0
210
WebMCP Challenge に星空観察アプリで参加した話
okajun35
0
170
選挙速報を多くのユーザーへ 届ける Live Activities 設計
hamayokokuririn
0
140
Featured
See All Featured
Facilitating Awesome Meetings
lara
57
7.1k
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
900
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
It's Worth the Effort
3n
188
29k
How STYLIGHT went responsive
nonsquared
100
6.3k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
The untapped power of vector embeddings
frankvandijk
2
1.9k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
The Impact of AI in SEO - AI Overviews June 2024 Edition
aleyda
6
1.2k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.4k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.6k
Transcript
Testing Advanced Cypress Testing Jeremy Fairbank @elpapapollo
@testdouble helps improves how the world build software. testdouble.com
Available in print or e-book programming-elm.com
Testing anti-patterns
Assumptions
Assumptions
Assumptions End-to-End
Assumptions End-to-End Real API
Assumptions End-to-End Real API Test Data
The App bit.ly/act-repo
Selector Syndrome
import albums from '../../../server/jazz-albums-test-pristine.json' describe('Home page', function () { it('albums
can be viewed on home page', function () { cy.get('.album-list-item').should('have.length', albums.length) albums.forEach((album) => { cy.get('.album-list-item h2') .contains(album.title) .parent('.album-list-item') .should('exist') .find('h3') .should('contain', album.artists.join(' - ')) }) }) })
import albums from '../../../server/jazz-albums-test-pristine.json' describe('Home page', function () { it('albums
can be viewed on home page', function () { cy.get('.album-list-item').should('have.length', albums.length) albums.forEach((album) => { cy.get('.album-list-item h2') .contains(album.title) .parent('.album-list-item') .should('exist') .find('h3') .should('contain', album.artists.join(' - ')) }) }) })
import albums from '../../../server/jazz-albums-test-pristine.json' describe('Home page', function () { it('albums
can be viewed on home page', function () { cy.get('.album-list-item').should('have.length', albums.length) albums.forEach((album) => { cy.get('.album-list-item h2') .contains(album.title) .parent('.album-list-item') .should('exist') .find('h3') .should('contain', album.artists.join(' - ')) }) }) })
import albums from '../../../server/jazz-albums-test-pristine.json' describe('Home page', function () { it('albums
can be viewed on home page', function () { cy.get('.album-list-item').should('have.length', albums.length) albums.forEach((album) => { cy.get('.album-list-item h2') .contains(album.title) .parent('.album-list-item') .should('exist') .find('h3') .should('contain', album.artists.join(' - ')) }) }) })
import albums from '../../../server/jazz-albums-test-pristine.json' describe('Home page', function () { it('albums
can be viewed on home page', function () { cy.get('.album-list-item').should('have.length', albums.length) albums.forEach((album) => { cy.get('.album-list-item h2') .contains(album.title) .parent('.album-list-item') .should('exist') .find('h3') .should('contain', album.artists.join(' - ')) }) }) })
import albums from '../../../server/jazz-albums-test-pristine.json' describe('Home page', function () { it('albums
can be viewed on home page', function () { cy.get('.album-list-item').should('have.length', albums.length) albums.forEach((album) => { cy.get('.album-list-item h2') .contains(album.title) .parent('.album-list-item') .should('exist') .find('h3') .should('contain', album.artists.join(' - ')) }) }) })
import albums from '../../../server/jazz-albums-test-pristine.json' describe('Home page', function () { it('albums
can be viewed on home page', function () { cy.get('.album-list-item').should('have.length', albums.length) albums.forEach((album) => { cy.get('.album-list-item h2') .contains(album.title) .parent('.album-list-item') .should('exist') .find('h3') .should('contain', album.artists.join(' - ')) }) }) })
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { cy.get('.album-list-item') .eq(index) .find('h2') .should('contain', album.title) }) } cy.contains('.filter-section', 'Sort By').find('select').select('Title') albumsSortedLike(albumsSortedByTitle) cy.contains('.filter-section', 'Sort By').find('select').select('Artist') albumsSortedLike(albumsSortedByArtists) cy.contains('.filter-section', 'Sort By').find('select').select('Default') albumsSortedLike(albumsSortedById) })
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { cy.get('.album-list-item') .eq(index) .find('h2') .should('contain', album.title) }) } cy.contains('.filter-section', 'Sort By').find('select').select('Title') albumsSortedLike(albumsSortedByTitle) cy.contains('.filter-section', 'Sort By').find('select').select('Artist') albumsSortedLike(albumsSortedByArtists) cy.contains('.filter-section', 'Sort By').find('select').select('Default') albumsSortedLike(albumsSortedById) })
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { cy.get('.album-list-item') .eq(index) .find('h2') .should('contain', album.title) }) } cy.contains('.filter-section', 'Sort By').find('select').select('Title') albumsSortedLike(albumsSortedByTitle) cy.contains('.filter-section', 'Sort By').find('select').select('Artist') albumsSortedLike(albumsSortedByArtists) cy.contains('.filter-section', 'Sort By').find('select').select('Default') albumsSortedLike(albumsSortedById) })
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { cy.get('.album-list-item') .eq(index) .find('h2') .should('contain', album.title) }) } cy.contains('.filter-section', 'Sort By').find('select').select('Title') albumsSortedLike(albumsSortedByTitle) cy.contains('.filter-section', 'Sort By').find('select').select('Artist') albumsSortedLike(albumsSortedByArtists) cy.contains('.filter-section', 'Sort By').find('select').select('Default') albumsSortedLike(albumsSortedById) })
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { cy.get('.album-list-item') .eq(index) .find('h2') .should('contain', album.title) }) } cy.contains('.filter-section', 'Sort By').find('select').select('Title') albumsSortedLike(albumsSortedByTitle) cy.contains('.filter-section', 'Sort By').find('select').select('Artist') albumsSortedLike(albumsSortedByArtists) cy.contains('.filter-section', 'Sort By').find('select').select('Default') albumsSortedLike(albumsSortedById) })
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { cy.get('.album-list-item') .eq(index) .find('h2') .should('contain', album.title) }) } cy.contains('.filter-section', 'Sort By').find('select').select('Title') albumsSortedLike(albumsSortedByTitle) cy.contains('.filter-section', 'Sort By').find('select').select('Artist') albumsSortedLike(albumsSortedByArtists) cy.contains('.filter-section', 'Sort By').find('select').select('Default') albumsSortedLike(albumsSortedById) })
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { cy.get('.album-list-item') .eq(index) .find('h2') .should('contain', album.title) }) } cy.contains('.filter-section', 'Sort By').find('select').select('Title') albumsSortedLike(albumsSortedByTitle) cy.contains('.filter-section', 'Sort By').find('select').select('Artist') albumsSortedLike(albumsSortedByArtists) cy.contains('.filter-section', 'Sort By').find('select').select('Default') albumsSortedLike(albumsSortedById) })
it('searches for artists', function () { cy.contains('.filter-section', 'Search Artists') .find('input')
.type('John Coltrane') cy.get('.album-list-item') .should('have.length', 2) .find('h2') .should('contain', 'A Love Supreme') .and('contain', 'Blue Train') .parent('.album-list-item') .find('h3') .should('contain', 'John Coltrane') })
it('searches for artists', function () { cy.contains('.filter-section', 'Search Artists') .find('input')
.type('John Coltrane') cy.get('.album-list-item') .should('have.length', 2) .find('h2') .should('contain', 'A Love Supreme') .and('contain', 'Blue Train') .parent('.album-list-item') .find('h3') .should('contain', 'John Coltrane') })
it('searches for artists', function () { cy.contains('.filter-section', 'Search Artists') .find('input')
.type('John Coltrane') cy.get('.album-list-item') .should('have.length', 2) .find('h2') .should('contain', 'A Love Supreme') .and('contain', 'Blue Train') .parent('.album-list-item') .find('h3') .should('contain', 'John Coltrane') })
it('searches for artists', function () { cy.contains('.filter-section', 'Search Artists') .find('input')
.type('John Coltrane') cy.get('.album-list-item') .should('have.length', 2) .find('h2') .should('contain', 'A Love Supreme') .and('contain', 'Blue Train') .parent('.album-list-item') .find('h3') .should('contain', 'John Coltrane') })
it('searches for artists', function () { cy.contains('.filter-section', 'Search Artists') .find('input')
.type('John Coltrane') cy.get('.album-list-item') .should('have.length', 2) .find('h2') .should('contain', 'A Love Supreme') .and('contain', 'Blue Train') .parent('.album-list-item') .find('h3') .should('contain', 'John Coltrane') })
it('searches for artists', function () { cy.contains('.filter-section', 'Search Artists') .find('input')
.type('John Coltrane') cy.get('.album-list-item') .should('have.length', 2) .find('h2') .should('contain', 'A Love Supreme') .and('contain', 'Blue Train') .parent('.album-list-item') .find('h3') .should('contain', 'John Coltrane') })
it('searches for artists', function () { cy.contains('.filter-section', 'Search Artists') .find('input')
.type('John Coltrane') cy.get('.album-list-item') .should('have.length', 2) .find('h2') .should('contain', 'A Love Supreme') .and('contain', 'Blue Train') .parent('.album-list-item') .find('h3') .should('contain', 'John Coltrane') })
Markup or CSS changes?
const AlbumListItem = ({ album }) => ( <Link className="album-list-item"
to={`/albums/${album.id}`} > <img src={album.coverUrl} alt="" /> <RatingIcon rating={album.rating} /> <h2>{album.title}</h2> <h3>{album.artists.join(' - ')}</h3> </Link> )
const AlbumListItem = ({ album }) => ( <Link className="album"
to={`/albums/${album.id}`} > <img src={album.coverUrl} alt="" /> <RatingIcon rating={album.rating} /> <h2>{album.title}</h2> <h3>{album.artists.join(' - ')}</h3> </Link> )
const AlbumListItem = ({ album }) => ( <Link className="album"
to={`/albums/${album.id}`} > <img src={album.coverUrl} alt="" /> <RatingIcon rating={album.rating} /> <h3>{album.title}</h3> <h4>{album.artists.join(' - ')}</h4> </Link> )
None
const AlbumListItem = ({ album }) => ( <Link className="album-list-item"
to={`/albums/${album.id}`} > <img src={album.coverUrl} alt="" /> <RatingIcon rating={album.rating} /> <h2>{album.title}</h2> <h3>{album.artists.join(' - ')}</h3> </Link> )
const AlbumListItemSelectors = ({ album }) => ( <Link className="album-list-item"
to={`/albums/${album.id}`} data-test="album-list-item" > <img src={album.coverUrl} alt="" /> <RatingIcon rating={album.rating} /> <h2 data-test="title">{album.title}</h2> <h3 data-test="artists">{album.artists.join(' - ')}</h3> </Link> )
const AlbumListItemSelectors = ({ album }) => ( <Link className="album-list-item"
to={`/albums/${album.id}`} data-test="album-list-item" > <img src={album.coverUrl} alt="" /> <RatingIcon rating={album.rating} /> <h2 data-test="title">{album.title}</h2> <h3 data-test="artists">{album.artists.join(' - ')}</h3> </Link> )
it('albums can be viewed on home page', function () {
cy.get('[data-test="album-list-item"]') .should('have.length', albums.length) albums.forEach((album) => { cy.get('[data-test="title"]') .contains(album.title) .parent('[data-test="album-list-item"]') .should('exist') .find('[data-test="artists"]') .should('contain', album.artists.join(' - ')) }) })
it('albums can be viewed on home page', function () {
cy.get('[data-test="album-list-item"]') .should('have.length', albums.length) albums.forEach((album) => { cy.get('[data-test="title"]') .contains(album.title) .parent('[data-test="album-list-item"]') .should('exist') .find('[data-test="artists"]') .should('contain', album.artists.join(' - ')) }) })
it('albums can be viewed on home page', function () {
cy.get('[data-test="album-list-item"]') .should('have.length', albums.length) albums.forEach((album) => { cy.get('[data-test="title"]') .contains(album.title) .parent('[data-test="album-list-item"]') .should('exist') .find('[data-test="artists"]') .should('contain', album.artists.join(' - ')) }) })
it('albums can be viewed on home page', function () {
cy.get('[data-test="album-list-item"]') .should('have.length', albums.length) albums.forEach((album) => { cy.get('[data-test="title"]') .contains(album.title) .parent('[data-test="album-list-item"]') .should('exist') .find('[data-test="artists"]') .should('contain', album.artists.join(' - ')) }) })
it('albums can be viewed on home page', function () {
cy.get('[data-test="album-list-item"]') .should('have.length', albums.length) albums.forEach((album) => { cy.get('[data-test="title"]') .contains(album.title) .parent('[data-test="album-list-item"]') .should('exist') .find('[data-test="artists"]') .should('contain', album.artists.join(' - ')) }) })
it('albums can be viewed on home page', function () {
cy.get('[data-test="album-list-item"]') .should('have.length', albums.length) albums.forEach((album) => { cy.contains('[data-test="album-list-item"]', album.title) .should('contain', album.artists.join(' - ')) }) })
<select value={sorter} onChange={(e) => setSorter(e.target.value)} data-test="sort-by" > <option value={Sorter.Id}>Default</option> <option
value={Sorter.Title}>Title</option> <option value={Sorter.Artist}>Artist</option> </select>
cy.get('[data-test="sort-by"]').select('Title') albumsSortedLike(albumsSortedByTitle) cy.get('[data-test="sort-by"]').select('Artist') albumsSortedLike(albumsSortedByArtists) cy.get('[data-test="sort-by"]').select('Default') albumsSortedLike(albumsSortedById)
it('searches for artists', function () { cy.get('[data-test="search-artists"]') .type('John Coltrane') cy.get('[data-test="album-list-item"]')
.should('have.length', 2) .and('contain', 'A Love Supreme') .and('contain', 'Blue Train') .and('contain', 'John Coltrane') })
it('searches for artists', function () { cy.get('[data-test="search-artists"]') .type('John Coltrane') cy.get('[data-test="album-list-item"]')
.should('have.length', 2) .and('contain', 'A Love Supreme') .and('contain', 'Blue Train') .and('contain', 'John Coltrane') })
it('searches for artists', function () { cy.get('[data-test="search-artists"]') .type('John Coltrane') cy.get('[data-test="album-list-item"]')
.should('have.length', 2) .and('contain', 'A Love Supreme') .and('contain', 'Blue Train') .and('contain', 'John Coltrane') })
it('albums can be viewed on home page', function () {
cy.get('[data-test="album-list-item"]') .should('have.length', albums.length) albums.forEach((album) => { cy.get('[data-test="title"]') .contains(album.title) .parent('[data-test="album-list-item"]') .should('exist') .find('[data-test="artists"]') .should('contain', album.artists.join(' - ')) }) })
it('albums can be viewed on home page', function () {
cy.get('[data-test="album-list-item"]') .should('have.length', albums.length) albums.forEach((album) => { cy.get('[data-test="title"]') .contains(album.title) .parent('[data-test="album-list-item"]') .should('exist') .find('[data-test="artists"]') .should('contain', album.artists.join(' - ')) }) })
Page Objects
// cypress/support/pages/home.js export const albums = () => cy.get('[data-test="album-list-item"]') export
const album = (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.get('[data-test="sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"]')
// cypress/support/pages/home.js export const albums = () => cy.get('[data-test="album-list-item"]') export
const album = (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.get('[data-test="sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"]')
// cypress/support/pages/home.js export const albums = () => cy.get('[data-test="album-list-item"]') export
const album = (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.get('[data-test="sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"]')
import * as homePage from '../support/pages/home' it('albums can be viewed
on home page', function () { homePage .albums() .should('have.length', albums.length) albums.forEach((album) => { homePage .album(album.title) .should('exist') .and('contain', album.artists.join(' - ')) }) })
import * as homePage from '../support/pages/home' it('albums can be viewed
on home page', function () { homePage .albums() .should('have.length', albums.length) albums.forEach((album) => { homePage .album(album.title) .should('exist') .and('contain', album.artists.join(' - ')) }) })
import * as homePage from '../support/pages/home' it('albums can be viewed
on home page', function () { homePage .albums() .should('have.length', albums.length) albums.forEach((album) => { homePage .album(album.title) .should('exist') .and('contain', album.artists.join(' - ')) }) })
import * as homePage from '../support/pages/home' it('albums can be viewed
on home page', function () { homePage .albums() .should('have.length', albums.length) albums.forEach((album) => { homePage .album(album.title) .should('exist') .and('contain', album.artists.join(' - ')) }) })
// cypress/support/pages/home.js export const albums = () => cy.get('[data-test="album-list-item"]') export
const album = (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.get('[data-test="sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"]')
// cypress/support/pages/home.js export const albums = () => cy.get('[data-test="album-list-item"]') export
const album = (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.get('[data-test="sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"]')
// cypress/support/pages/home.js export const albums = () => cy.get('[data-test="album-list-item"]') export
const album = (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.get('[data-test="sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"]')
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { homePage.albums().eq(index).should('contain', album.title) }) } homePage.sortBy().select('Title') albumsSortedLike(albumsSortedByTitle) homePage.sortBy().select('Artist') albumsSortedLike(albumsSortedByArtists) homePage.sortBy().select('Default') albumsSortedLike(albumsSortedById) })
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { homePage.albums().eq(index).should('contain', album.title) }) } homePage.sortBy().select('Title') albumsSortedLike(albumsSortedByTitle) homePage.sortBy().select('Artist') albumsSortedLike(albumsSortedByArtists) homePage.sortBy().select('Default') albumsSortedLike(albumsSortedById) })
it('sorts albums', function () { function albumsSortedLike(albumList) { albumList.forEach((album, index)
=> { homePage.albums().eq(index).should('contain', album.title) }) } homePage.sortBy().select('Title') albumsSortedLike(albumsSortedByTitle) homePage.sortBy().select('Artist') albumsSortedLike(albumsSortedByArtists) homePage.sortBy().select('Default') albumsSortedLike(albumsSortedById) })
it('searches for artists', function () { homePage.searchArtists().type('John Coltrane') homePage .albums()
.should('have.length', 2) .and('contain', 'A Love Supreme') .and('contain', 'Blue Train') .and('contain', 'John Coltrane') })
it('searches for artists', function () { homePage.searchArtists().type('John Coltrane') homePage .albums()
.should('have.length', 2) .and('contain', 'A Love Supreme') .and('contain', 'Blue Train') .and('contain', 'John Coltrane') })
it('searches for artists', function () { homePage.searchArtists().type('John Coltrane') homePage .albums()
.should('have.length', 2) .and('contain', 'A Love Supreme') .and('contain', 'Blue Train') .and('contain', 'John Coltrane') })
Repetition
export const albums = () => cy.get('[data-test="album-list-item"]') export const album
= (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.get('[data-test="sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"]')
export const albums = () => cy.get('[data-test="album-list-item"]') export const album
= (title) => cy.contains('[data-test="album-list-ite"]', title) export const sortBy = () => cy.get('[data-test=sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"')
export const albums = () => cy.get('[data-test="album-list-item"]') export const album
= (title) => cy.contains('[data-test="album-list-ite"]', title) export const sortBy = () => cy.get('[data-test=sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"')
export const albums = () => cy.get('[data-test="album-list-item"]') export const album
= (title) => cy.contains('[data-test="album-list-ite"]', title) export const sortBy = () => cy.get('[data-test=sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"')
Commands
// cypress/support/commands.js Cypress.Commands.add( 'getByDataTest', (key) => cy.get(`[data-test="${key}"]`) ) // cypress/support/index.js
import './commands'
// cypress/support/commands.js Cypress.Commands.add( 'getByDataTest', (key) => cy.get(`[data-test="${key}"]`) ) // cypress/support/index.js
import './commands'
// cypress/support/commands.js Cypress.Commands.add( 'getByDataTest', (key) => cy.get(`[data-test="${key}"]`) ) // cypress/support/index.js
import './commands'
// cypress/support/commands.js Cypress.Commands.add( 'getByDataTest', (key) => cy.get(`[data-test="${key}"]`) ) // cypress/support/index.js
import './commands'
// cypress/support/commands.js Cypress.Commands.add( 'getByDataTest', (key) => cy.get(`[data-test="${key}"]`) ) // cypress/support/index.js
import './commands'
// cypress/support/commands.js Cypress.Commands.add( 'getByDataTest', (key) => cy.get(`[data-test="${key}"]`) ) // cypress/support/index.js
import './commands'
export const albums = () => cy.get('[data-test="album-list-item"]') export const album
= (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.get('[data-test="sort-by"]') export const searchArtists = () => cy.get('[data-test="search-artists"]')
export const albums = () => cy.getByDataTest('album-list-item') export const album
= (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.getByDataTest('sort-by') export const searchArtists = () => cy.getByDataTest('search-artists')
export const albums = () => cy.getByDataTest('album-list-item') export const album
= (title) => cy.contains('[data-test="album-list-item"]', title) export const sortBy = () => cy.getByDataTest('sort-by') export const searchArtists = () => cy.getByDataTest('search-artists')
Cypress.Commands.add( 'containsByDataTest', (key, content) => cy.contains(`[data-test="${key}"]`, content) )
Cypress.Commands.add( 'containsByDataTest', (key, content) => cy.contains(`[data-test="${key}"]`, content) )
export const album = (title) => cy.containsByDataTest('album-list-item', title)
API Timeouts
describe('Album page', function () { // ... it('can be viewed',
function () { albumPage.title().should('contain', this.album.title) albumPage.artists().should('contain', this.album.artists) }) // ... })
describe('Album page', function () { // ... it('can be viewed',
function () { albumPage.title().should('contain', this.album.title) albumPage.artists().should('contain', this.album.artists) }) // ... })
Wait
it('can be viewed', function () { cy.wait(6000) albumPage.title().should('contain', this.album.title) albumPage.artists().should('contain',
this.album.artists) })
it('can be viewed', function () { cy.wait(6000) albumPage.title().should('contain', this.album.title) albumPage.artists().should('contain',
this.album.artists) })
it('can be viewed', function () { cy.wait(6000) albumPage.title().should('contain', this.album.title) albumPage.artists().should('contain',
this.album.artists) })
beforeEach(function () { cy.server() cy.route(buildApiUrl('/albums/*')).as('getAlbum') // ... }) Routes and
Aliases
beforeEach(function () { cy.server() cy.route(buildApiUrl('/albums/*')).as('getAlbum') // ... }) Routes and
Aliases
beforeEach(function () { cy.server() cy.route(buildApiUrl('/albums/*')).as('getAlbum') // ... }) Routes and
Aliases
beforeEach(function () { cy.server() cy.route(buildApiUrl('/albums/*')).as('getAlbum') // ... }) Routes and
Aliases
beforeEach(function () { cy.server() cy.route(buildApiUrl('/albums/*')).as('getAlbum') // ... }) Routes and
Aliases
it('can be viewed', function () { cy.wait('@getAlbum') albumPage.title().should('contain', this.album.title) albumPage.artists().should('contain',
this.album.artists) })
Api Testing
describe('REST API: albums', function () { it('can fetch all albums',
function () { cy.request({ url: buildApiUrl('/albums'), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('have.length', this.albums.length) .each((album) => { expect(this.albumTitles).to.include(album.title) }) }) })
describe('REST API: albums', function () { it('can fetch all albums',
function () { cy.request({ url: buildApiUrl('/albums'), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('have.length', this.albums.length) .each((album) => { expect(this.albumTitles).to.include(album.title) }) }) })
describe('REST API: albums', function () { it('can fetch all albums',
function () { cy.request({ url: buildApiUrl('/albums'), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('have.length', this.albums.length) .each((album) => { expect(this.albumTitles).to.include(album.title) }) }) })
describe('REST API: albums', function () { it('can fetch all albums',
function () { cy.request({ url: buildApiUrl('/albums'), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('have.length', this.albums.length) .each((album) => { expect(this.albumTitles).to.include(album.title) }) }) })
describe('REST API: albums', function () { it('can fetch all albums',
function () { cy.request({ url: buildApiUrl('/albums'), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('have.length', this.albums.length) .each((album) => { expect(this.albumTitles).to.include(album.title) }) }) })
describe('REST API: albums', function () { it('can fetch all albums',
function () { cy.request({ url: buildApiUrl('/albums'), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('have.length', this.albums.length) .each((album) => { expect(this.albumTitles).to.include(album.title) }) }) })
describe('REST API: albums', function () { it('can fetch all albums',
function () { cy.request({ url: buildApiUrl('/albums'), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('have.length', this.albums.length) .each((album) => { expect(this.albumTitles).to.include(album.title) }) }) })
describe('REST API: albums', function () { it('can fetch all albums',
function () { cy.request({ url: buildApiUrl('/albums'), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('have.length', this.albums.length) .each((album) => { expect(this.albumTitles).to.include(album.title) }) }) })
it('can fetch an album', function () { const album =
this.albums[0] cy.request({ url: buildApiUrl(`/albums/${album.id}`), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('containSubset', { id: album.id, title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
it('can fetch an album', function () { const album =
this.albums[0] cy.request({ url: buildApiUrl(`/albums/${album.id}`), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('containSubset', { id: album.id, title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
it('can fetch an album', function () { const album =
this.albums[0] cy.request({ url: buildApiUrl(`/albums/${album.id}`), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('containSubset', { // chai-subset plugin id: album.id, title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
it('can fetch an album', function () { const album =
this.albums[0] cy.request({ url: buildApiUrl(`/albums/${album.id}`), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('containSubset', { id: album.id, title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
it('can fetch an album', function () { const album =
this.albums[0] cy.request({ url: buildApiUrl(`/albums/${album.id}`), headers: { 'Content-Type': 'application/json', }, auth: { bearer: this.token }, }) .its('body') .should('containSubset', { id: album.id, title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiRequest', ({ body, url, ...options }) => { const token
= localStorage.getItem('jwt') return cy.request({ ...options, failOnStatusCode: false, url: buildApiUrl(url), headers: { 'Content-Type': 'application/json', }, auth: { bearer: token }, ...(body && { body: JSON.stringify(body) }), }) })
Cypress.Commands.add('apiGet', (url) => cy.apiRequest({ url, method: 'GET' }) ) Cypress.Commands.add('apiPost',
(url, body = null) => cy.apiRequest({ body, url, method: 'POST' }) )
Cypress.Commands.add('apiGet', (url) => cy.apiRequest({ url, method: 'GET' }) ) Cypress.Commands.add('apiPost',
(url, body = null) => cy.apiRequest({ body, url, method: 'POST' }) )
Cypress.Commands.add('apiGet', (url) => cy.apiRequest({ url, method: 'GET' }) ) Cypress.Commands.add('apiPost',
(url, body = null) => cy.apiRequest({ body, url, method: 'POST' }) )
it('can fetch all albums', function () { cy.apiGet('/albums') .its('body') .should('have.length',
this.albums.length) .each((album) => { expect(this.albumTitles).to.include(album.title) }) })
it('can review an album and remove the review', function ()
{ const album = this.albums[0] const review = 'Great album!' const expectedReview = { review, user: { name: 'Emmett Brown', email: '
[email protected]
' }, } cy.apiPost(`/albums/${album.id}/review`, { review }) .its('body') .should('deep.equal', expectedReview) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', [expectedReview]) cy.apiPost(`/albums/${album.id}/remove-review`) .its('body') .should('deep.equal', { status: 'Removed review' }) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', []) })
it('can review an album and remove the review', function ()
{ const album = this.albums[0] const review = 'Great album!' const expectedReview = { review, user: { name: 'Emmett Brown', email: '
[email protected]
' }, } cy.apiPost(`/albums/${album.id}/review`, { review }) .its('body') .should('deep.equal', expectedReview) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', [expectedReview]) cy.apiPost(`/albums/${album.id}/remove-review`) .its('body') .should('deep.equal', { status: 'Removed review' }) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', []) })
it('can review an album and remove the review', function ()
{ const album = this.albums[0] const review = 'Great album!' const expectedReview = { review, user: { name: 'Emmett Brown', email: '
[email protected]
' }, } cy.apiPost(`/albums/${album.id}/review`, { review }) .its('body') .should('deep.equal', expectedReview) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', [expectedReview]) cy.apiPost(`/albums/${album.id}/remove-review`) .its('body') .should('deep.equal', { status: 'Removed review' }) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', []) })
it('can review an album and remove the review', function ()
{ const album = this.albums[0] const review = 'Great album!' const expectedReview = { review, user: { name: 'Emmett Brown', email: '
[email protected]
' }, } cy.apiPost(`/albums/${album.id}/review`, { review }) .its('body') .should('deep.equal', expectedReview) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', [expectedReview]) cy.apiPost(`/albums/${album.id}/remove-review`) .its('body') .should('deep.equal', { status: 'Removed review' }) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', []) })
it('can review an album and remove the review', function ()
{ const album = this.albums[0] const review = 'Great album!' const expectedReview = { review, user: { name: 'Emmett Brown', email: '
[email protected]
' }, } cy.apiPost(`/albums/${album.id}/review`, { review }) .its('body') .should('deep.equal', expectedReview) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', [expectedReview]) cy.apiPost(`/albums/${album.id}/remove-review`) .its('body') .should('deep.equal', { status: 'Removed review' }) cy.apiGet(`/albums/${album.id}`) .its('body.userReviews') .should('deep.equal', []) })
GraphQL
Cypress.Commands.add('graphQL', (query, variables = {}) => cy.apiPost('/graphql', { query, variables
}) )
Cypress.Commands.add('graphQL', (query, variables = {}) => cy.apiPost('/graphql', { query, variables
}) )
Cypress.Commands.add('graphQL', (query, variables = {}) => cy.apiPost('/graphql', { query, variables
}) )
it('can fetch an album', function () { const album =
this.albums[0] const query = ` query Album($id: ID!) { album(id: $id) { title artists rating userReviews { review } } } ` cy.graphQL(query, { id: album.id }) .its('body.data.album') .should('deep.equal', { title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
it('can fetch an album', function () { const album =
this.albums[0] const query = ` query Album($id: ID!) { album(id: $id) { title artists rating userReviews { review } } } ` cy.graphQL(query, { id: album.id }) .its('body.data.album') .should('deep.equal', { title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
it('can fetch an album', function () { const album =
this.albums[0] const query = ` query Album($id: ID!) { album(id: $id) { title artists rating userReviews { review } } } ` cy.graphQL(query, { id: album.id }) .its('body.data.album') .should('deep.equal', { title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
it('can fetch an album', function () { const album =
this.albums[0] const query = ` query Album($id: ID!) { album(id: $id) { title artists rating userReviews { review } } } ` cy.graphQL(query, { id: album.id }) .its('body.data.album') .should('deep.equal', { title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
it('can fetch an album', function () { const album =
this.albums[0] const query = ` query Album($id: ID!) { album(id: $id) { title artists rating userReviews { review } } } ` cy.graphQL(query, { id: album.id }) .its('body.data.album') .should('deep.equal', { title: album.title, artists: album.artists, rating: 'NOT_RATED', userReviews: [], }) })
Authentication?
Cypress.Commands.add('login', (email, password) => { cy.visit('/login') loginPage.email().type(email) loginPage.password().type(password) loginPage.logIn().click() })
Cypress.Commands.add('login', (email, password) => { cy.visit('/login') loginPage.email().type(email) loginPage.password().type(password) loginPage.logIn().click() })
Cypress.Commands.add( 'login', ( email = Cypress.env('USER_EMAIL'), password = Cypress.env('USER_PASSWORD') )
=> cy .request('POST', buildApiUrl('/login'), { email, password }) .its('body.token') .then((token) => { localStorage.setItem('jwt', token) }) )
Cypress.Commands.add( 'login', ( email = Cypress.env('USER_EMAIL'), password = Cypress.env('USER_PASSWORD') )
=> cy .request('POST', buildApiUrl('/login'), { email, password }) .its('body.token') .then((token) => { localStorage.setItem('jwt', token) }) )
Cypress.Commands.add( 'login', ( email = Cypress.env('USER_EMAIL'), password = Cypress.env('USER_PASSWORD') )
=> cy .request('POST', buildApiUrl('/login'), { email, password }) .its('body.token') .then((token) => { localStorage.setItem('jwt', token) }) )
Cypress.Commands.add( 'login', ( email = Cypress.env('USER_EMAIL'), password = Cypress.env('USER_PASSWORD') )
=> cy .request('POST', buildApiUrl('/login'), { email, password }) .its('body.token') .then((token) => { localStorage.setItem('jwt', token) }) )
Cypress.Commands.add( 'login', ( email = Cypress.env('USER_EMAIL'), password = Cypress.env('USER_PASSWORD') )
=> cy .request('POST', buildApiUrl('/login'), { email, password }) .its('body.token') .then((token) => { localStorage.setItem('jwt', token) }) )
beforeEach(function () { cy.login() })
Selector Syndrome
Selector Syndrome data-test Attributes and Page Objects
Repetitive Code
Repetitive Code Custom Commands
Timeouts/Arbitrary Waiting
Timeouts/Arbitrary Waiting Routes and Aliases
Untested APIs
Untested APIs cy.request and Commands
Authenticating via UI
Authenticating via UI API, localStorage/cookies, commands, and beforeEach
Thanks! Thanks! Jeremy Fairbank @elpapapollo Slides: bit.ly/ct-act Repo: bit.ly/act-repo