Cypress Tips October 2023
Run specs in parallel for free plus Cypress Pagination Challenge solutions ๐
๐ธ Split Specs To Run In Parallel For Free
Now that Cypress has blocked the competing dashboards, how do you run your specs in parallel for free? Using my cypress-split plugin, of course! I even added using previous spec timings to better allocate specs to machines. Read the announcement blog post.
Even more, if you use my Cypress reusable workflows for GitHub, it is as easy as adding a single flag! Here is a complete workflow from the cypress-split-timings-example repo:
name: split
on:
# launch this workflow from GitHub Actions page
workflow_dispatch:
jobs:
split:
# https://github.com/bahmutov/cypress-workflows
uses: bahmutov/cypress-workflows/.github/workflows/split.yml@v2
with:
nE2E: 2
# use timings to split E2E specs across 2 machines efficiently
split-file: 'timings.json'
# this job grab the output for the `split` workflow
# and writes it into a JSON file "timings.json"
# and then commits the updated file to the repository
commit-updated-timings:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-22.04
needs: split
steps:
- name: Checkout ๐
uses: actions/checkout@v4
- name: Show merged timings ๐จ๏ธ
run: |
echo off
echo '${{ needs.split.outputs.merged-timings }}'
- name: Write updated timings ๐พ
# pretty-print json string into a file
run: echo '${{ toJson(fromJson(needs.split.outputs.merged-timings)) }}' > timings.json
- name: Commit changed spec timings โฑ๏ธ
# https://github.com/stefanzweifel/git-auto-commit-action
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Updated spec timings
branch: main
file_pattern: timings.json
๐ Cypress Pagination Challenge solutions
About a month ago I posted Cypress Pagination Challenge: can you click on the โNextโ button until you reach the end of the paginated table? You can find multiple solutions in the blog post โCypress Pagination Challengeโ. Below are some of my solutions that I think you will like.
1. Plain Cypress syntax
In short, our solution should:
check the โNextโ button
if it is disabled, we are done
else click on the button
go to step 1
Here is the solution using plain Cypress commands and recursion
beforeEach(() => {
cy.visit('public/index.html')
})
function maybeClickNext() {
// the Next button is always present
cy.get('[value=next]').then(($button) => {
// look at the attribute "disabled"
// to see if we reached the end of the table
if ($button.attr('disabled') === 'disabled') {
cy.log('Last page!')
} else {
// not the end yet, sleep half a second for clarity,
// click the button, and recursively check again
cy.wrap($button).wait(500).click().then(maybeClickNext)
}
})
}
it('clicks the Next button until we get to the last page', () => {
// the HTML table on the page is paginated
// can you click the "Next" button until
// we get to the very last page?
// button selector "[value=next]"
maybeClickNext()
cy.log('**confirm we are on the last page**')
cy.get('[value=next]').should('be.disabled')
cy.get('[value=last]').should('be.disabled')
})
2. Using cypress-recurse plugin
My fav solution for writing recursive Cypress code is to try the cypress-recurse plugin
import { recurse } from 'cypress-recurse'
beforeEach(() => {
cy.visit('public/index.html')
})
it('clicks the Next button until we get to the last page', () => {
// the HTML table on the page is paginated
// can you click the "Next" button until
// we get to the very last page?
// button selector "[value=next]"
// click on the "next" button
// until the button becomes disabled
recurse(
() => cy.get('[value=next]'),
// check if the button is disabled using jQuery ".attr()" method
// https://api.jquery.com/attr/
($button) => $button.attr('disabled') === 'disabled',
{
log: 'The last page',
delay: 1000, // wait a second between clicks
timeout: 10_000, // max recursion for 10 seconds
post() {
// if the button is not disabled, click it
cy.get('[value=next]').click()
},
},
)
cy.log('**confirm we are on the last page**')
cy.get('[value=next]').should('be.disabled')
cy.get('[value=last]').should('be.disabled')
})
Can we do better?
3. Cypress-if plugin
Because we have `if / else` logic in the solution, we can apply the cypress-if plugin to make it more elegant.
// https://github.com/bahmutov/cypress-if
import 'cypress-if'
function maybeClickNext() {
cy.get('[value=next]')
.if('enabled')
.wait(1000)
.log('clicking next')
// because we used "cy.log"
// we removed the button subject, so need to query again
.get('[value=next]')
.click()
.then(maybeClickNext)
.else()
.log('last page')
}
beforeEach(() => {
cy.visit('public/index.html')
})
it('clicks the Next button until we get to the last page', () => {
// the HTML table on the page is paginated
// can you click the "Next" button until
// we get to the very last page?
// button selector "[value=next]"
maybeClickNext()
cy.log('**confirm we are on the last page**')
cy.get('[value=next]').should('be.disabled')
cy.get('[value=last]').should('be.disabled')
})
I like it. But we can do better.
4. Using cypress-await
We can maybe simplify the syntax even more by removing the need to chain Cypress commands with my cypress-await plugin. This is the synchronous mode - we donโt even need the await
keyword in front of the cy
commands!
beforeEach(() => {
cy.visit('public/index.html')
})
function maybeClick() {
const disabled = cy.get('[value=next]').invoke('attr', 'disabled')
cy.log(`disabled ${disabled}`)
if (disabled !== 'disabled') {
cy.wait(1000)
cy.get('[value=next]').click()
maybeClick()
}
}
it('clicks the Next button until we get to the last page', () => {
maybeClick()
cy.log('**confirm we are on the last page**')
cy.get('[value=next]').should('be.disabled')
cy.get('[value=last]').should('be.disabled')
})
Note: before you start using the cypress-await plugin, please know that I still need to add support for import
and require
keywords in the specs. Follow issues #9 and #10 for details.
Find full source code with better syntax highlighting in the blog post โCypress Pagination Challengeโ. You can also see me explain the above solutions in the following free lessons from my Cypress Plugins online course ๐:
๐ฃ๏ธ Upcoming Talks
Catch me soon(ish) at the following events:
๐ป online at Continuous Testing Meetup with the talk "Fast Testing Using Cypress For Free" on Nov 16th.
๐ฅ in person at Maine JS meetup in Portland, Maine, USA on Dec 12th. again talking about running specs in parallel.
๐บ New Cypress Videos
Here are links to the latest records.
Have a question about Cypress? Use the search page at https://cypress.tips/search to find out if I have answered it already. If there is nothing, let me know - I need ideas for new videos and blog posts.
And rememberโฆ there is nothing scarier than flaky tests ๐ฑ