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
450
Connect.Tech 2019: Solving the Boolean Identity Crisis
jfairbank
0
270
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
230
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
AGENTS.md Is Not Enough:Build Skills, Don't Download Them
lx_t
0
130
更なる可用性を求めて、5年間運用したKotlinのアプリケーションをGoでリプレイスする話
ken_tunc
0
370
そのリトライ、死んだコネクションを使い回していませんか ── GoのHTTPクライアントとHTTP/2を実プロダクト障害から学び直す
myus4a
0
270
仕様駆動開発による爆速プロダクト開発 / Bakusoku Spec Driven Development
kobakei
0
130
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
290
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
3.7k
世界の中心で、AI(App Intents)をさけぶ ー App Intents中心設計の実践ガイド
touyou
0
680
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
210
setup-vp GitLab対応の裏側
naokihaba
0
120
速習iPhone Duo対応
yuukiw00w
2
820
海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム
atsuki_seo
0
940
Jetpack Compose メカニズム
skydoves
0
480
Featured
See All Featured
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
340
It's Worth the Effort
3n
188
29k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
120
120k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
520
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
12k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
530
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.2k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
410
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
56
3.5k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
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