Running Our Tests with GitHub Actions
Running Our Tests with GitHub Actions
Now that we have a few tests written for our Next.js store, let's setup GitHub Actions to run our Cypress tests against our app every time we make a pull request. This way we can be sure that our latest changes have not broken anything within the app.
Configuring GitHub Actions
Now that our repo is up on GitHub, we will need to create a GitHub actions config file.
Create a new file called .github/workflows/main.yml
Next we will give our workflow a name:
name: E2E on Chrome
Then tell GitHub when to run this action:
name: E2E on Chrome
on: [push]
The on:
directive tells GitHub to run this workflow each time a push is made to our repo. This way, each time we push up a new branch and create a new pull request, this GitHub Action Workflow will run.
Next, we will setup the job we want to run:
name: E2E on Chrome
on: [push]
jobs:
install:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
We have a single install:
job which will run on the latest version of Ubuntu. Then underneath the steps:
directive we are using the actions/checkout GitHub Action. "This action checks-out your repository under $GITHUB_WORKSPACE
, so your workflow can access it."
Next, we will have our GitHub Actions Workflow use the official cypress-io/github-action
name: E2E on Chrome
on: [push]
jobs:
install:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Cypress run
uses: cypress-io/github-action@v3
with:
project: ./site
browser: chrome
build: yarn build
start: yarn start
wait-on: "http://localhost:3000"
cypress-io/github-action is the official GitHub Action created by Cypress. Under the with:
directive we are telling Cypress to:
project:
Look for Cypress and our tests inside of thesite/
directory.browser:
run our tests inside of the chrome browser.build:
run the build script in thepackage.json
in the root of the repo which builds the production version of our Next.js application.start:
run the start script in thepackage.json
in the root of the repo which serves the production build of our application with a local dev server.wait-on:
tells Cypress to make sure that[http://localhost:3000](http://localhost:3000)
is up and running before it runs our tests.
You can find the documentation for this action here.
Finally, we need to pass in our env variables we currently live in the .env.local
file.
The entire GitHub Actions Workflow config file should look like this:
name: E2E on Chrome
on: [push]
jobs:
install:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Cypress run
uses: cypress-io/github-action@v3
with:
project: ./site
browser: chrome
build: yarn build
start: yarn start
wait-on: "http://localhost:3000"
env:
COMMERCE_PROVIDER: ${{ secrets.COMMERCE_PROVIDER }}
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN: ${{ secrets.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN }}
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN: ${{ secrets.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN }}
Now we need to add the values for these environment variables in our GitHub repo so that our workflow has access to them.
Adding GitHub Secrets
At the top of your repo in GitHub, click on "Settings"
In the left sidebar, click on "Secrets" then “Actions”
Then click on the "New repository secret" button in the top right.
Now you can just add the key and value of the three env variables within the .env.local
file.
Running our Tests in GitHub Actions
Next, within your terminal, checkout a new branch in git.
git checkout -b github-actions-cypress-tests
Add your changes and push up your branch
git add .
git commit -m "added cypress tests and GitHub action config"
git push origin github-actions-cypress-tests
After your new branch has been pushed up, click on the "Code" link at the top of your repo.
Then click on the "Compare & pull request" button to create a new PR.
Then click "Create pull request"
GitHub Actions in Action
You will now see the GitHub Actions workflow running. If you click on "Details" you can watch all of the steps inside of the workflow run.
You will see that one of our tests has failed.
It looks like Cypress could not find our product on the search results page. Let's run this test locally and make sure it is passing.
We know that this test was passing locally, so why is it failing in CI?
Our error says the following:
warning
AssertionError: Timed out retrying after 4000ms: Expected to find element: [data-test="product-tag"]
, but never found it.
So it seems like the first part of our test is working, but once we get to the search results page it cannot find the product-tag
element. We will add some data-test
attributes to the components/product/ProductCard.tsx
component. Around line 73
{
!noNameTag && (
<div className={s.header} data-test="product-card">
<h3 className={s.name}>
<span data-test="product-name">{product.name}</span>
</h3>
<div className={s.price} data-test="product-price">
{`${price} ${product.price?.currencyCode}`}
</div>
</div>
)
}
The entire component file should look like this:
import { FC } from 'react'
import cn from 'clsx'
import Link from 'next/link'
import type { Product } from '@commerce/types/product'
import s from './ProductCard.module.css'
import Image, { ImageProps } from 'next/image'
import WishlistButton from '@components/wishlist/WishlistButton'
import usePrice from '@framework/product/use-price'
import ProductTag from '../ProductTag'
interface Props {
className?: string
product: Product
noNameTag?: boolean
imgProps?: Omit<ImageProps, 'src' | 'layout' | 'placeholder' | 'blurDataURL'>
variant?: 'default' | 'slim' | 'simple'
}
const placeholderImg = '/product-img-placeholder.svg'
const ProductCard: FC<Props> = ({
product,
imgProps,
className,
noNameTag = false,
variant = 'default',
}) => {
const { price } = usePrice({
amount: product.price.value,
baseAmount: product.price.retailPrice,
currencyCode: product.price.currencyCode!,
})
const rootClassName = cn(
s.root,
{ [s.slim]: variant === 'slim', [s.simple]: variant === 'simple' },
className
)
return (
<Link href={`/product/${product.slug}`}>
<a className={rootClassName} aria-label={product.name}>
{variant === 'slim' && (
<>
<div className={s.header}>
<span>{product.name}</span>
</div>
{product?.images && (
<div>
<Image
quality="85"
src={product.images[0]?.url || placeholderImg}
alt={product.name || 'Product Image'}
height={320}
width={320}
layout="fixed"
{...imgProps}
/>
</div>
)}
</>
)}
{variant === 'simple' && (
<>
{process.env.COMMERCE_WISHLIST_ENABLED && (
<WishlistButton
className={s.wishlistButton}
productId={product.id}
variant={product.variants[0]}
/>
)}
{!noNameTag && (
<div className={s.header} data-test="product-card">
<h3 className={s.name}>
<span data-test="product-name">{product.name}</span>
</h3>
<div className={s.price} data-test="product-price">
{`${price} ${product.price?.currencyCode}`}
</div>
</div>
)}
<div className={s.imageContainer}>
{product?.images && (
<div>
<Image
alt={product.name || 'Product Image'}
className={s.productImage}
src={product.images[0]?.url || placeholderImg}
height={540}
width={540}
quality="85"
layout="responsive"
{...imgProps}
/>
</div>
)}
</div>
</>
)}
{variant === 'default' && (
<>
{process.env.COMMERCE_WISHLIST_ENABLED && (
<WishlistButton
className={s.wishlistButton}
productId={product.id}
variant={product.variants[0] as any}
/>
)}
<ProductTag
name={product.name}
price={`${price} ${product.price?.currencyCode}`}
/>
<div className={s.imageContainer}>
{product?.images && (
<div>
<Image
alt={product.name || 'Product Image'}
className={s.productImage}
src={product.images[0]?.url || placeholderImg}
height={540}
width={540}
quality="85"
layout="responsive"
{...imgProps}
/>
</div>
)}
</div>
</>
)}
</a>
</Link>
)
}
export default ProductCard
And update our test to be the following:
// header.spec.js
it("the search bar returns the correct search results", () => {
cy.getBySel("search-input").eq(0).type("linux{enter}")
cy.get('[data-test="product-card"]').within(() => {
cy.get('[data-test="product-name"]').should("contain", "Linux Shirt")
cy.get('[data-test="product-price"]').should("contain", "$25.00 USD")
})
})
Now let's push up these changes and see if our tests will pass now in CI.
git add .
git commit -m "updated search results test and added data-test attributes to ProductCard component"
git push origin github-actions-cypress-tests
Now all of our tests are passing.
Summary
In this lesson we learned how to integrate Cypress with GitHub Actions so that our tests will run against every PR we make in our repo. We also learned how to fix a broken test by adding some additional data-test
attributes to one of our app’s components.