Cypress.io
When testing is easy, developers build better things faster and with confidence.
@bahmutov
Ask Questions at Sli.do event code #cy-netlify
@jlengstorf
Sli.do event code #cy-netlify
Source: https://github.com/cypress-io/cypress-example-netlify-store
which is a fork of sdras/ecommerce-netlify
cy.visit('/')
cy.contains('nav li', 'Men').click()
cy.log("**men' items**")
cy.location('pathname').should('equal', '/men')
// let's not go crazy here, $30 is plenty!
// we change the max value by setting the value
// of the slider and then triggering the "input" event
cy.get('#pricerange').invoke('val', 30).trigger('input')
// check the number of items < $30
cy.get('.content .item').should('have.length', 3).first().click()
cy.log('**item page**')
cy.location('pathname').should('match', /\/product\//)
cy.contains('.product-info h1', 'Armin Basilio')
cy.get('.size-picker').select('Large')
// this jacket is so lovely, let's order 2
cy.contains('.update-num', '+').click()
cy.get('.quantity input[type=number]').should('have.value', 2)
cy.contains('.purchase', 'Add to Cart').click()
cy.contains('nav .carttotal', 2) // the little badge is present
// spy on the Netlify function call
cy.intercept({
method: 'POST',
pathname: '/.netlify/functions/create-payment-intent',
}).as('paymentIntent')
cy.contains('nav li', 'Cart').click()
cy.log('**cart page**')
cy.location('pathname').should('equal', '/cart')
// confirm the products in the cart
cy.get('[data-cy=item-in-cart]')
.should('have.length', 1)
.first()
.within(() => {
cy.contains('.product-size', 'Size: Large')
cy.contains('[data-cy=item-quantity]', 2)
})
cy.visit('/')
cy.contains('nav li', 'Men').click()
cy.log("**men' items**")
cy.location('pathname').should('equal', '/men')
// let's not go crazy here, $30 is plenty!
// we change the max value by setting the value
// of the slider and then triggering the "input" event
cy.get('#pricerange').invoke('val', 30).trigger('input')
// check the number of items < $30
cy.get('.content .item').should('have.length', 3).first().click()
cy.log('**item page**')
cy.location('pathname').should('match', /\/product\//)
cy.contains('.product-info h1', 'Armin Basilio')
cy.get('.size-picker').select('Large')
// this jacket is so lovely, let's order 2
cy.contains('.update-num', '+').click()
cy.get('.quantity input[type=number]').should('have.value', 2)
cy.contains('.purchase', 'Add to Cart').click()
cy.contains('nav .carttotal', 2) // the little badge is present
// spy on the Netlify function call
cy.intercept({
method: 'POST',
pathname: '/.netlify/functions/create-payment-intent',
}).as('paymentIntent')
cy.contains('nav li', 'Cart').click()
cy.log('**cart page**')
cy.location('pathname').should('equal', '/cart')
// confirm the products in the cart
cy.get('[data-cy=item-in-cart]')
.should('have.length', 1)
.first()
.within(() => {
cy.contains('.product-size', 'Size: Large')
cy.contains('[data-cy=item-quantity]', 2)
})
cy.visit('/')
cy.contains('nav li', 'Men').click()
cy.log("**men' items**")
cy.location('pathname').should('equal', '/men')
// let's not go crazy here, $30 is plenty!
// we change the max value by setting the value
// of the slider and then triggering the "input" event
cy.get('#pricerange').invoke('val', 30).trigger('input')
// check the number of items < $30
cy.get('.content .item').should('have.length', 3).first().click()
cy.log('**item page**')
cy.location('pathname').should('match', /\/product\//)
cy.contains('.product-info h1', 'Armin Basilio')
cy.get('.size-picker').select('Large')
// this jacket is so lovely, let's order 2
cy.contains('.update-num', '+').click()
cy.get('.quantity input[type=number]').should('have.value', 2)
cy.contains('.purchase', 'Add to Cart').click()
cy.contains('nav .carttotal', 2) // the little badge is present
// spy on the Netlify function call
cy.intercept({
method: 'POST',
pathname: '/.netlify/functions/create-payment-intent',
}).as('paymentIntent')
cy.contains('nav li', 'Cart').click()
cy.log('**cart page**')
cy.location('pathname').should('equal', '/cart')
// confirm the products in the cart
cy.get('[data-cy=item-in-cart]')
.should('have.length', 1)
.first()
.within(() => {
cy.contains('.product-size', 'Size: Large')
cy.contains('[data-cy=item-quantity]', 2)
})
cy.visit('/')
cy.contains('nav li', 'Men').click()
cy.log("**men' items**")
cy.location('pathname').should('equal', '/men')
// let's not go crazy here, $30 is plenty!
// we change the max value by setting the value
// of the slider and then triggering the "input" event
cy.get('#pricerange').invoke('val', 30).trigger('input')
// check the number of items < $30
cy.get('.content .item').should('have.length', 3).first().click()
cy.log('**item page**')
cy.location('pathname').should('match', /\/product\//)
cy.contains('.product-info h1', 'Armin Basilio')
cy.get('.size-picker').select('Large')
// this jacket is so lovely, let's order 2
cy.contains('.update-num', '+').click()
cy.get('.quantity input[type=number]').should('have.value', 2)
cy.contains('.purchase', 'Add to Cart').click()
cy.contains('nav .carttotal', 2) // the little badge is present
// spy on the Netlify function call
cy.intercept({
method: 'POST',
pathname: '/.netlify/functions/create-payment-intent',
}).as('paymentIntent')
cy.contains('nav li', 'Cart').click()
cy.log('**cart page**')
cy.location('pathname').should('equal', '/cart')
// confirm the products in the cart
cy.get('[data-cy=item-in-cart]')
.should('have.length', 1)
.first()
.within(() => {
cy.contains('.product-size', 'Size: Large')
cy.contains('[data-cy=item-quantity]', 2)
})
cy.visit('/')
cy.contains('nav li', 'Men').click()
cy.log("**men' items**")
cy.location('pathname').should('equal', '/men')
// let's not go crazy here, $30 is plenty!
// we change the max value by setting the value
// of the slider and then triggering the "input" event
cy.get('#pricerange').invoke('val', 30).trigger('input')
// check the number of items < $30
cy.get('.content .item').should('have.length', 3).first().click()
cy.log('**item page**')
cy.location('pathname').should('match', /\/product\//)
cy.contains('.product-info h1', 'Armin Basilio')
cy.get('.size-picker').select('Large')
// this jacket is so lovely, let's order 2
cy.contains('.update-num', '+').click()
cy.get('.quantity input[type=number]').should('have.value', 2)
cy.contains('.purchase', 'Add to Cart').click()
cy.contains('nav .carttotal', 2) // the little badge is present
// spy on the Netlify function call
cy.intercept({
method: 'POST',
pathname: '/.netlify/functions/create-payment-intent',
}).as('paymentIntent')
cy.contains('nav li', 'Cart').click()
cy.log('**cart page**')
cy.location('pathname').should('equal', '/cart')
// confirm the products in the cart
cy.get('[data-cy=item-in-cart]')
.should('have.length', 1)
.first()
.within(() => {
cy.contains('.product-size', 'Size: Large')
cy.contains('[data-cy=item-quantity]', 2)
})
const ccNumber = '4242424242424242'
const month = '12'
const year = '30'
const cvc = '123'
const zipCode = '90210'
getIframeBody('.stripe-card iframe')
.find('input[name=cardnumber]')
.type(`${ccNumber}${month}${year}${cvc}${zipCode}`)
cy.contains('.pay-with-stripe', 'Pay with credit card').click()
// if the payment went through
cy.contains('.success', 'Success!').should('be.visible')
// automatically resets to empty cart
cy.scrollTo('top')
cy.get('nav .carttotal', { timeout: 6000 })
.should('not.exist') // the little badge is gone
cy.contains('Your cart is empty, fill it up!')
.should('be.visible')
const getIframeBody = (iframeSelector) =>
cy.get(iframeSelector)
.its('0.contentDocument.body')
.should('not.be.empty')
.then(cy.wrap)
const ccNumber = '4242424242424242'
const month = '12'
const year = '30'
const cvc = '123'
const zipCode = '90210'
getIframeBody('.stripe-card iframe')
.find('input[name=cardnumber]')
.type(`${ccNumber}${month}${year}${cvc}${zipCode}`)
cy.contains('.pay-with-stripe', 'Pay with credit card').click()
// if the payment went through
cy.contains('.success', 'Success!').should('be.visible')
// automatically resets to empty cart
cy.scrollTo('top')
cy.get('nav .carttotal', { timeout: 6000 })
.should('not.exist') // the little badge is gone
cy.contains('Your cart is empty, fill it up!')
.should('be.visible')
const ccNumber = '4242424242424242'
const month = '12'
const year = '30'
const cvc = '123'
const zipCode = '90210'
getIframeBody('.stripe-card iframe')
.find('input[name=cardnumber]')
.type(`${ccNumber}${month}${year}${cvc}${zipCode}`)
cy.contains('.pay-with-stripe', 'Pay with credit card').click()
// if the payment went through
cy.contains('.success', 'Success!').should('be.visible')
// automatically resets to empty cart
cy.scrollTo('top')
cy.get('nav .carttotal', { timeout: 6000 })
.should('not.exist') // the little badge is gone
cy.contains('Your cart is empty, fill it up!')
.should('be.visible')
How do I demo a new feature before merging?
How do I test the full site before merging?
Need to test it
Need to test it
~/git/cypress-example-netlify-store on feature1
$ npx cypress run --config baseUrl=https://deploy-preview-1--cypress-example-netlify-store.netlify.app/
Run E2E tests against a local site
Run E2E tests against a deployed site
Run E2E tests against a local site
Run E2E tests against a deployed site
(opt-in)
(default)
[build]
command = "yarn generate"
functions = "functions"
publish = "dist"
[build]
command = "yarn generate"
functions = "functions"
publish = "dist"
[[plugins]]
# runs Cypress tests against the deployed URL
package = "netlify-plugin-cypress"
$ yarn add -D netlify-plugin-cypress
info Direct dependencies
└─ netlify-plugin-cypress@2.1.0
netlify.toml
[build]
command = "yarn generate"
functions = "functions"
publish = "dist"
[[plugins]]
# runs Cypress tests against the deployed URL
package = "netlify-plugin-cypress"
[plugins.inputs]
record = true
# you can pass spec, group, tag, etc
[build]
command = "yarn generate"
functions = "functions"
publish = "dist"
[[plugins]]
# runs Cypress tests against the deployed URL
package = "netlify-plugin-cypress"
[plugins.inputs]
# do not run the tests after deploy
enable = false
<li>
<div class="carttotal" v-if="cartCount > 0">{{ cartCount }}</div>
<!-- <nuxt-link to="/cart">Cart</nuxt-link> -->
</li>
We have tests, right
We have tests, right
GitHub Repo
Netlify Build
Cypress Dashboard
Site
record the tests on Cypress Dashboard
record the tests on Cypress Dashboard
set CYPRESS_RECORD_KEY in Netlify build environment
record the tests on the Dashboard
[[plugins]]
# runs Cypress tests against the deployed URL
package = "netlify-plugin-cypress"
[plugins.inputs]
record = true
enable Cypress GitHub integration https://on.cypress.io/gitlab-integration
pull request is failing!
to avoid deploying the broken code
[[plugins]]
package = "netlify-plugin-cypress"
# runs Cypress tests against the deployed URL
[plugins.inputs]
record = true
# run Cypress tests before building and deploying
[plugins.inputs.preBuild]
enable = true
# call the same commands as we do locally
start = 'nuxt start'
wait-on = 'http://localhost:3000'
Watch the test run's recording on Cypress Dashboard
[[plugins]]
package = "netlify-plugin-cypress"
# runs Cypress tests against the deployed URL
[plugins.inputs]
record = true
# run Cypress tests before building and deploying
[plugins.inputs.preBuild]
enable = true
# full Netlify local environment:
# redirects, functions
start = 'netlify dev'
wait-on = 'http://localhost:3000'
[[plugins]]
package = "netlify-plugin-cypress"
# runs few smoke tests against deployed URL
[plugins.inputs]
record = true
spec = "cypress/integration/smoke/**/*.js"
# run all tests before building and deploying
[plugins.inputs.preBuild]
enable = true
# full Netlify local environment:
# redirects, functions
start = 'netlify dev'
wait-on = 'http://localhost:3000'
(but first, check if there is already a plugin doing what you need!)
module.exports = {
async onPreBuild ({ constants, utils, inputs }) {
...
},
async onPostBuild ({ constants, utils, inputs }) {
...
},
async onSuccess ({ constants, utils, inputs }) {
...
}
}
const cypress = require('cypress')
module.exports = {
async onPreBuild ({ constants, utils, inputs })
async onPostBuild ({ constants, utils, inputs })
async onSuccess ({ constants, utils, inputs }) {
const deployPrimeUrl = process.env.DEPLOY_PRIME_URL
if (!deployPrimeUrl) {
return utils.build.failPlugin('Missing DEPLOY_PRIME_URL')
}
const testResults = await cypress.run({
config: {
baseUrl: deployPrimeUrl
}
})
}
}
name: netlify-plugin-cypress
inputs:
# by default the Cypress tests run against the deployed URL
# and these settings apply during the "onSuccess" step
- name: enable
description: Run tests against the preview or production deploy
default: true
# Cypress comes with built-in Electron browser
# and this NPM package installs Chromium browser
- name: browser
description: Allowed values are chromium, electron
default: chromium
...
manifest.yml
[[plugins]]
# require the plugin's code from relative folder
package = "./netlify-plugins/my-plugin"
[plugins.inputs]
# my plugin's inputs
netlify.toml
@bahmutov
@jlengstorf
By Cypress.io
In this webcast, we'll show you how to use the Cypress Netlify build plugin to make sure your Jamstack site deploys are bug-free, every time. Video at https://www.youtube.com/watch?v=7kHgOCqWgKE
When testing is easy, developers build better things faster and with confidence.