One way to have Cypress automation run is through a series of GitHub Actions workflows. The one I’m going to show you runs through a defined matrix and runs test in parallel.
Take for example this Matrix. Think of this as an array of test you want running in your suite. Note that we can filter this array in our workflows to our advantage. Also note that all the properties you define here will be able to be passed to each matrix job in our workflows as well.
{
"include": [
{
"name": "vtr store",
"app": "misc",
"group": "compare",
"tag": "COMPARE vtr store",
"country": "USA",
"countryHeader": "us",
"filePath": "cypress/e2e/misc/compare/vtr.store.spec.ts",
"storeId": "1",
"backupStoreId": "20001"
},
{
"name": "create deal",
"app": "sales",
"group": "desking",
"tag": "DESKING create deal",
"country": "USA",
"countryHeader": "us",
"filePath": "cypress/e2e/sales/desking/create.deal.spec.ts",
"storeId": "2",
"backupStoreId": "20002"
}
]
}
I also created this quick iife to generate a matrix like above quickly
const fs = require('fs');
const path = require('path');
let filePaths = [];
function recFindByExt(startPath, filter) {
if (!fs.existsSync(startPath)) {
console.log('no dir ', startPath);
return;
}
let files = fs.readdirSync(startPath);
for (var i = 0; i < files.length; i++) {
let filename = path.join(startPath, files[i]);
let stat = fs.lstatSync(filename);
if (stat.isDirectory()) {
recFindByExt(filename, filter); //recurse
} else if (filename.indexOf(filter) >= 0) {
console.log('-- found: ', filename);
filePaths.push(filename);
}
}
}
(async () => {
try {
let includes = [];
let specpath = path.join(__dirname, '../cypress/e2e');
recFindByExt(specpath, '.spec.ts');
const canadaSpecFiles = filePaths.filter((filePath) =>
filePath.includes('canada')
);
const usaSpecFiles = filePaths.filter(
(filePath) => !filePath.includes('canada')
);
usaSpecFiles.map((filepath, index) => {
const splitPath = filepath.split('/');
const name = splitPath[splitPath.length - 1]
.replace('.spec.ts', '')
.replace(/\./g, ' ');
const integrationIndex = splitPath.indexOf('e2e');
const group = splitPath[integrationIndex + 2].replace(/[\W_]+/g, '-');
const appName = splitPath[integrationIndex + 1].replace(/[\W_]+/g, '-');
includes.push({
name: name,
app: appName,
group: group,
tag: `${group.toUpperCase()} ${name}`,
country: 'USA',
countryHeader: 'us',
filePath: filepath.substring(filepath.indexOf('cypress')),
storeId: String(index + 1),
backupStoreId: String(index + 20001),
});
});
canadaSpecFiles.map((filepath, index) => {
const splitPath = filepath.split('/');
const name = splitPath[splitPath.length - 1]
.replace('.spec.ts', '')
.replace(/\./g, ' ');
const integrationIndex = splitPath.indexOf('e2e');
const group = splitPath[integrationIndex + 2].replace(/[\W_]+/g, '-');
const appName = splitPath[integrationIndex + 1].replace(/[\W_]+/g, '-');
includes.push({
name: name,
group: group,
app: appName,
tag: `${group.toUpperCase()} ${name}`,
country: 'CAN',
countryHeader: 'ca',
filePath: filepath.substring(filepath.indexOf('cypress')),
storeId: String(index + 10000),
backupStoreId: String(index + 25001),
});
});
const allIncludes = {
include: includes,
};
const writePath = path.join(__dirname, `matrix.json`);
fs.writeFileSync(writePath, JSON.stringify(allIncludes));
} catch (error) {
console.error(error);
}
})();
Defining the GitHub Actions workflows and filtering your include matrix as seen above.
name: Misc Cypress Suite
on:
# schedule:
# - cron: '0 17-22/4 * * 1-5' # 7am -> 3pm
# - cron: '59 23 * * 1-5' # 5pm
workflow_dispatch:
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
# Recommended: pass the GitHub token lets this action correctly
# determine the unique run id necessary to re-run the checks
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CYPRESS_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
HOST_BASE: ${{ secrets.HOST_BASE }}
# https://github.com/cypress-io/cypress/issues/2777
BASE_URL: ${{ secrets.BASE_URL }}
CYPRESS_ADMIN_PASSWORD: ${{ secrets.CYPRESS_ADMIN_PASSWORD }}
CYPRESS_ADMIN_PORTAL_URL: ${{ secrets.CYPRESS_ADMIN_PORTAL_URL }}
CYPRESS_ADMIN_USERNAME: ${{ secrets.CYPRESS_ADMIN_USERNAME }}
CYPRESS_CORE_API_URL: ${{ secrets.CYPRESS_CORE_API_URL }}
CYPRESS_COUNTRY_HEADER: 'us'
CYPRESS_COUNTRY_SHORT_CODE: 'USA'
CYPRESS_HOST_BASE: ${{ secrets.CYPRESS_HOST_BASE }}
CYPRESS_INVENTORY_API_URL: ${{ secrets.CYPRESS_INVENTORY_API_URL }}
CYPRESS_VEHICLE_API_URL: ${{ secrets.CYPRESS_VEHICLE_API_URL }}
CYPRESS_SALES_PORTAL_URL: ${{ secrets.CYPRESS_SALES_PORTAL_URL }}
CYPRESS_TEST_ENV: 'staging'
CYPRESS_PGHOST_STAGING: ${{ secrets.CYPRESS_PGHOST_STAGING }}
CYPRESS_PGPORT_STAGING: ${{ secrets.CYPRESS_PGPORT_STAGING }}
CYPRESS_PGDATABASE_CORE_STAGING: ${{ secrets.CYPRESS_PGDATABASE_CORE_STAGING }}
CYPRESS_PGDATABASE_LEADS_STAGING: ${{ secrets.CYPRESS_PGDATABASE_LEADS_STAGING }}
CYPRESS_PGDATABASE_USERS_STAGING: ${{ secrets.CYPRESS_PGDATABASE_USERS_STAGING }}
CYPRESS_PGDATABASE_NOTIFICATIONS_STAGING: ${{ secrets.CYPRESS_PGDATABASE_NOTIFICATIONS_STAGING }}
CYPRESS_PGPASSWORD_STAGING: ${{ secrets.CYPRESS_PGPASSWORD_STAGING }}
CYPRESS_PGUSER_STAGING: ${{ secrets.CYPRESS_PGUSER_STAGING }}
CYPRESS_TWILIO_ACCOUNTSID: ${{ secrets.CYPRESS_TWILIO_ACCOUNTSID }}
CYPRESS_TWILIO_AUTH_HEADER: ${{ secrets.CYPRESS_TWILIO_AUTH_HEADER }}
CYPRESS_TWILIO_AUTHTOKEN: ${{ secrets.CYPRESS_TWILIO_AUTHTOKEN }}
CYPRESS_TWILIO_PHONE_ONE: ${{ secrets.CYPRESS_TWILIO_PHONE_ONE }}
CYPRESS_TWILIO_PHONE_TWO: ${{ secrets.CYPRESS_TWILIO_PHONE_TWO }}
CYPRESS_TWILIO_PHONE_THREE: ${{ secrets.CYPRESS_TWILIO_PHONE_THREE }}
CYPRESS_USERS_API_URL: ${{ secrets.CYPRESS_USERS_API_URL }}
CYPRESS_DT_DEALER_ID: ${{ secrets.CYPRESS_DT_DEALER_ID }}
CYPRESS_DT_LENDER_ID: ${{ secrets.CYPRESS_DT_LENDER_ID }}
CYPRESS_R1_SENDER_NAME_CODE: ${{ secrets.CYPRESS_R1_SENDER_NAME_CODE }}
CYPRESS_R1_SENDER_ID: ${{ secrets.CYPRESS_R1_SENDER_ID }}
CYPRESS_R1_TARGET_ID: ${{ secrets.CYPRESS_R1_TARGET_ID }}
CYPRESS_R1_MESSAGE_TYPE: ${{ secrets.CYPRESS_R1_MESSAGE_TYPE }}
CYPRESS_R1_DEALER_ID: ${{ secrets.CYPRESS_R1_DEALER_ID }}
CYPRESS_CUDL_DEALER_ID: ${{ secrets.CYPRESS_CUDL_DEALER_ID }}
CYPRESS_CUDL_LENDER_ID: ${{ secrets.CYPRESS_CUDL_LENDER_ID }}
CYPRESS_R1_LENDER_ID: ${{ secrets.CYPRESS_R1_LENDER_ID }}
CYPRESS_R1_SIMULATION_MESSAGE_TYPE: ${{ secrets.CYPRESS_R1_SIMULATION_MESSAGE_TYPE }}
CYPRESS_R1_TARGET_ID_SIMULATION: ${{ secrets.CYPRESS_R1_TARGET_ID_SIMULATION }}
CYPRESS_R1_SENDER_ID_SIMULATION: ${{ secrets.CYPRESS_R1_SENDER_ID_SIMULATION }}
CYPRESS_R1_WHITE_LIST: ${{ secrets.CYPRESS_R1_WHITE_LIST }}
CYPRESS_WEB_PASS: ${{ secrets.CYPRESS_WEB_PASS }}
CYPRESS_WEB_USER: ${{ secrets.CYPRESS_WEB_USER }}
CYPRESS_WEB_CLIENT_STORE_EMAIL_USER: ${{ secrets.WEB_CLIENT_STORE_EMAIL_USER }}
CYPRESS_WEB_CLIENT_STORE_EMAIL_PASS: ${{ secrets.WEB_CLIENT_STORE_EMAIL_PASS }}
CYPRESS_WEB_CLIENT_LEADS_EMAIL_USER: ${{ secrets.WEB_CLIENT_LEADS_EMAIL_USER }}
CYPRESS_WEB_CLIENT_LEADS_EMAIL_PASS: ${{ secrets.WEB_CLIENT_LEADS_EMAIL_PASS }}
CYPRESS_VTR_STORE_EMAIL_PASS: ${{ secrets.VTR_STORE_EMAIL_PASS }}
CYPRESS_VTR_STORE_EMAIL_USER: ${{ secrets.VTR_STORE_EMAIL_USER }}
CYPRESS_VTR_LEAD_EMAIL_PASS: ${{ secrets.VTR_LEAD_EMAIL_PASS }}
CYPRESS_VTR_LEAD_EMAIL_USER: ${{ secrets.VTR_LEAD_EMAIL_USER }}
CYPRESS_VTR_CAN_EMAIL_USER: ${{ secrets.VTR_CAN_EMAIL_USER }}
CYPRESS_VTR_CAN_EMAIL_PASS: ${{ secrets.VTR_CAN_EMAIL_PASS }}
CYPRESS_IMAP_EMAIL_HOST: ${{ secrets.IMAP_EMAIL_HOST }}
CYPRESS_IMAP_EMAIL_PORT: ${{ secrets.IMAP_EMAIL_PORT }}
INVENTORY_IMPORT_HOST: ${{ secrets.INVENTORY_IMPORT_HOST }}
INVENTORY_IMPORT_PASS: ${{ secrets.INVENTORY_IMPORT_PASS }}
INVENTORY_IMPORT_USER: ${{ secrets.INVENTORY_IMPORT_USER }}
CYPRESS_DB_ENCRYPTION_KEY: ${{ secrets.DB_ENCRYPTION_KEY }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
SLACK_TEST_AUTOMATION_CHANNEL_ID: ${{ secrets.SLACK_TEST_AUTOMATION_CHANNEL_ID }}
SLACK_TRUECAR_BOT_TOKEN: ${{ secrets.SLACK_TRUECAR_BOT_TOKEN }}
CYPRESS_TEST_PHONE: ${{ secrets.CYPRESS_TEST_PHONE }}
CYPRESS_TEST_CODE: ${{ secrets.CYPRESS_TEST_CODE }}
STAGING_USA_MONGO_URI: ${{ secrets.STAGING_USA_MONGO_URI }}
STAGING_CAN_MONGO_URI: ${{ secrets.STAGING_CAN_MONGO_URI }}
jobs:
install:
name: Install
runs-on: ubuntu-latest
timeout-minutes: 80
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install
uses: cypress-io/github-action@v4
with:
runTests: false
set_misc_matrix:
name: Set Misc Matrix
needs: install
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set Matrix
id: set-matrix
run: |
SPECS="`jq -r --arg SPECS "$SPECS" '.include |= map(select([.group] | inside(["misc"])))' workflow-matrix/matrix.json`"
echo "matrix="$SPECS"" >> $GITHUB_OUTPUT
misc-suite:
name: Misc
needs: set_misc_matrix
runs-on: ubuntu-latest
container:
image: cypress/browsers:node16.16.0-chrome105-ff104-edge
options: --user 1001 # ← THIS IS THE IMPORTANT LINE!
strategy:
matrix: ${{fromJson(needs.set_misc_matrix.outputs.matrix)}}
fail-fast: false
timeout-minutes: 50
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Report
run: yarn clean:reports
- name: ${{ matrix.group }} ${{ matrix.name }} Suite
uses: cypress-io/github-action@v4
with:
browser: chrome
record: true
tag: ${{ matrix.group }} ${{ matrix.tag }}
spec: ${{ matrix.filePath }}
env:
# pass the Dashboard record key as an environment variable
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
CYPRESS_PROJECT_ID: ${{ secrets.PROJECT_ID }}
CYPRESS_STORE_ID: ${{ matrix.storeId }}
CYPRESS_BACKUP_STORE_ID: ${{ matrix.backupStoreId }}
CYPRESS_COUNTRY_HEADER: ${{ matrix.countryHeader }}
CYPRESS_COUNTRY_SHORT_CODE: ${{ matrix.country }}
# Recommended: pass the GitHub token lets this action correctly
# determine the unique run id necessary to re-run the checks
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Copy Screenshots
run: yarn copy:screenshots
if: failure()
- name: Send Screenshot
run: yarn alert:slack
if: failure()
- name: Slack Notify
uses: rtCamp/action-slack-notify@v2.2.0
if: failure()
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_TITLE: ${{ matrix.group }} ${{ matrix.name }} ${{ matrix.storeId }}
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: 'Automation Failure!'
SLACK_USERNAME: qaAutomation
- name: Upload Logs
uses: actions/upload-artifact@v2
if: always()
with:
name: API Logs ${{ matrix.name }}
path: logs
- name: generate report
if: always()
run: yarn generate:report
- name: Upload Report
uses: actions/upload-artifact@v2
if: always()
with:
name: ${{ matrix.name }} ${{ matrix.storeId }} Report
path: cypress/reports/mochareports/
I think its important to note the set matrix step:
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set Matrix
id: set-matrix
run: |
SPECS="`jq -r --arg SPECS "$SPECS" '.include |= map(select([.group] | inside(["misc"])))' workflow-matrix/matrix.json`"
echo "matrix="$SPECS"" >> $GITHUB_OUTPUT
This uses jq to filter your matrix and make this job only run test that are in the “group” of “misc”.
Its also important to note that I’m able to reference the properties I defined in my matrix earlier in my workflow files like so:
env:
CYPRESS_STORE_ID: ${{ matrix.storeId }}
CYPRESS_BACKUP_STORE_ID: ${{ matrix.backupStoreId }}
CYPRESS_COUNTRY_HEADER: ${{ matrix.countryHeader }}
CYPRESS_COUNTRY_SHORT_CODE: ${{ matrix.country }}