Reliable automated testing is critical for maintaining a healthy Drupal website, especially when rolling out new features or updates. While manual QA is slow and error-prone, integrating Cypress, a popular JavaScript-based end-to-end testing framework, into your CI/CD pipeline ensures quick, consistent, and repeatable testing.
By running Cypress tests inside GitHub Actions, we can:
- Test user flows like login, form submissions, and page rendering automatically.
- Catch regressions before they reach production.
- Get fast feedback on every pull request.
In this guide, we’ll walk you through setting up Cypress tests for Drupal inside GitHub Actions using a ready-to-use workflow.
Why choose Cypress with GitHub actions for Drupal testing
Cypress combined with GitHub Actions gives you a number of advantages which makes it a natural choice for testing site functionality:
- Real browser testing: Validate real-world user interactions in Chrome.
- Automatic parallelism: Run multiple tests simultaneously (in paid versions).
- Live debugging: View full screenshots and videos of failing tests.
- No flaky Tests: Designed for modern front-end frameworks.
- Cost-effective: GitHub Actions offers free minutes for open-source projects.
Prerequisites
Before you begin, ensure you have:
- A Drupal 10 website.
- Basic understanding of Composer, Drush, and Node.js.
- A GitHub repository with access to GitHub Actions.
- Cypress tests are located in the tests folder.
- A remote database (optional, e.g., Pantheon dev environment).
Step 1: Setup your GitHub action workflow
Create a workflow file at .github/workflows/cypress-tests.yml:
name: Cypress Tests
on:
workflow_dispatch:
jobs:
build:
name: Build for Testing
runs-on: ubuntu-latest
services:
database:
image: mysql:5.7
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
env:
CI: GITHUB_ACTIONS
MYSQL_ROOT_PASSWORD: root
MYSQL_USER_PASSWORD: drupal
MYSQL_USER_NAME: drupal
MYSQL_DATABASE_NAME: drupal
MYSQL_HOST: 127.0.0.1
MYSQL_PORT: 3306
REMOTE_SITE_ENV_ALIAS: ${{ vars.REMOTE_SITE_ENV_ALIAS }}
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
SSH_DEPLOYMENT_KEY: ${{ secrets.PANTHEON_DEPLOYER_SSH_PRIVATE }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: '0'
- name: Setup PHP and tools
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer:v2
extensions: mbstring, gd, intl, yaml, bcmath, curl
- name: Prepare environment variables
run: |
echo "GITHUB_REF_NAME=$(echo ${GITHUB_REF##*/})" >> $GITHUB_ENV
echo "COMPOSER_BIN=$(echo ${GITHUB_WORKSPACE}/vendor/bin)" >> $GITHUB_ENV
echo "SCRIPT_DIR=$(echo ${GITHUB_WORKSPACE}/.github/scripts)" >> $GITHUB_ENV
# Caching based on https://github.com/marketplace/actions/composer-php-actions#caching-dependencies-for-faster-builds
- name: Determine Composer cache directory
shell: bash
run: "echo \"COMPOSER_CACHE_DIR=$(composer config cache-dir)\" >> $GITHUB_ENV"
- name: Cache dependencies installed with Composer
uses: actions/cache@v3
with:
path: |
"${{ env.COMPOSER_CACHE_DIR }}"
$GITHUB_WORKSPACE/vendor
$GITHUB_WORKSPACE/web/core
$GITHUB_WORKSPACE/web/libraries
$GITHUB_WORKSPACE/web/modules/contrib
$GITHUB_WORKSPACE/web/themes/contrib
$GITHUB_WORKSPACE/web/profiles/contrib
key: os-${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
- name: Cache node modules
uses: actions/cache@v3
with:
path: |
~/.npm
~/.nvm
key: ${{ runner.os }}-build-${{ github.ref }}
# Caching END
- name: Install PHP dependencies
run: composer install
- name: Setup environment variables
run: |
echo "$GITHUB_WORKSPACE/vendor/bin" >> $GITHUB_PATH
echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH
- name: Validate app
run: composer validate --no-check-all --ansi
- name: Setup environment
run: bash $SCRIPT_DIR/setup_env.sh
- name: Prepare deployment key
run: |
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
ssh-add - <<< "${{ env.SSH_DEPLOYMENT_KEY }}"
if: ${{ env.SSH_DEPLOYMENT_KEY }}
- name: Setup app
run: bash $SCRIPT_DIR/setup_app.sh
if: ${{ env.SSH_DEPLOYMENT_KEY }}
- name: Start Application in background
run: |
# Enable the devel_php module till config split gets fixed.
drush pm:enable -y devel_php
drush status
# Is anything else listening on 8080? Unlikely.
netstat -an 2>/dev/null
drush config:status
drush watchdog:show
drush -vvv runserver localhost:8080 &
until netstat -an 2>/dev/null | grep '8080.*LISTEN'; do true; done
- name: Cache Cypress binary
uses: actions/cache@v3
with:
path: ~/.cache/Cypress
key: cypress-binary-${{ runner.os }}-${{ hashFiles('tests/package-lock.json') }}
- name: Cache Cypress node_modules
uses: actions/cache@v3
with:
path: tests/node_modules
key: cypress-npm-${{ runner.os }}-${{ hashFiles('tests/package-lock.json') }}
restore-keys: |
cypress-npm-${{ runner.os }}-
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Run Cypress tests
run: |
cd tests
cp cypress.ci.default.json cypress.env.json
if [ ! -d node_modules ]; then npm ci; fi
npx cypress install # Ensures binary is present
npx cypress run
This triggers tests manually via the "Run Workflow" button in GitHub UI.
The build job performs the following steps:
- Spin up a MySQL service container.
- Check out the code and set up PHP (8.3) and Node.js (20).
- Install PHP and JavaScript dependencies.
- Set up environment variables and SSH access.
- Sync and sanitize the database from a remote environment.
- Serve the Drupal site locally on port 8080.
- Run Cypress tests headlessly.
Step 2: Prepare the environment
GitHub runners are blank. We set up everything using two helper shell scripts: setup_env.sh
#!/usr/bin/env bash
set -ev
# Add Composer bin directory to PATH
export PATH=${COMPOSER_BIN}:$PATH
# Prepare SSH config for deployment to Remote Environment.
# Github actions will run steps as root.
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/config
chmod 600 ~/.ssh/config
# Trust all Pantheon git/svn and site hosts.
echo "Host appserver.dev.*.drush.in
StrictHostKeyChecking no" >> ~/.ssh/config
# Print current directory and user ID.
pwd
id
# Set the git configuration.
git config --global user.name "Morpht Automation (GitHub Actions)"
git config --global user.email "xyz@xyz.com"
## Up the PHP Memory Limit
#touch /usr/local/etc/php/conf.d/docker-php-ext-ci.ini
#echo 'memory_limit = -1' >> /usr/local/etc/php/conf.d/docker-php-ext-ci.ini
# Create a MySQL database for Drupal Application to use.
MYSQL_ROOT_COMMAND="mysql --user=root --password=$MYSQL_ROOT_PASSWORD --host=$MYSQL_HOST --port=$MYSQL_PORT --protocol=tcp"
echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE_NAME\`;" | $MYSQL_ROOT_COMMAND
echo "CREATE USER '$MYSQL_USER_NAME'@'%' IDENTIFIED BY '$MYSQL_USER_PASSWORD';" | $MYSQL_ROOT_COMMAND
echo "GRANT ALL ON $MYSQL_DATABASE_NAME.* TO '$MYSQL_USER_NAME'@'%';" | $MYSQL_ROOT_COMMAND
echo "FLUSH PRIVILEGES;" | $MYSQL_ROOT_COMMAND
echo "SET GLOBAL max_allowed_packet=33554432;" | $MYSQL_ROOT_COMMAND
# Print databases which Drupal user can see for debugging.
echo "SHOW DATABASES;" | mysql --user="$MYSQL_USER_NAME" --password="$MYSQL_USER_PASSWORD" --host="$MYSQL_HOST" --port=$MYSQL_PORT --protocol=tcp
set +v
This script:
- Configures SSH for remote deployments.
- Sets Git global configs.
- Creates a MySQL database.
- Tweaks MySQL settings (like max_allowed_packet).
Pro Tip: It ensures Drupal can connect to a fresh MySQL database every time the workflow runs.
setup_app.sh
#!/usr/bin/env bash
set -ev
# Add Composer bin directory to PATH
export PATH=${COMPOSER_BIN}:$PATH
# Print current directory and list contents of web/sites/default
pwd
ls -l web/sites/default
# Create necessary directories with appropriate permissions
mkdir -p web/sites/default/files/private
mkdir -p web/sites/default/files/tmp
chmod -R 777 web/sites/default/files
# Check Drush status
drush status
drush ${REMOTE_SITE_ENV_ALIAS} status
# Sync the database from Remote Environment to CI Environment.
drush sql-sync ${REMOTE_SITE_ENV_ALIAS} @self -y
# Sanitize the database.
drush sql:sanitize -y
# Deploy site.
drush -y deploy
set +v
This script:
- Prepares writable directories (like sites/default/files).
- Pulls a sanitized copy of your Dev database into CI.
- Deploys site configs using drush deploy.
- Make sure the application is ready for Cypress tests.
Step 3: Start the application for testing
Once Drupal is set up:
drush -vvv runserver localhost:8080 &
until netstat -an 2>/dev/null | grep '8080.*LISTEN'; do true; done
This starts a web server on localhost:8080 and waits until it is ready.
Pro Tip: Use devel_php module temporarily during CI to troubleshoot PHP issues.
Step 4: Cache everything!
To speed up builds:
- Cache Composer dependencies.
- Cache Node.js packages.
- Cache Cypress binaries.
This avoids reinstalling large dependencies every time you push a change.
Step 5: Run Cypress tests
Inside the tests folder
cd tests
cp cypress.ci.default.json cypress.env.json
if [ ! -d node_modules ]; then npm ci; fi
npx cypress install
npx cypress run
- cypress.ci.default.json contains environment-specific test configs.
- npm ci ensures clean installs.
- npx cypress run executes all tests in headless mode.
Advanced features and enhancements
Once Cypress CI is running, consider:
- Parallelizing tests: Split test files across multiple runners.
- Slack and Teams notifications: Alert developers when tests fail.
- Visual test recording: Integrate with Cypress Dashboard for deeper insights.
- Scheduled nightly runs: Catch issues early even without code changes.
Real-world use case
Cypress tests can be used in an advanced way so that it is able to check functionality such as roles and access on a site. In one of our client projects, a government website needed to test login workflows, multilingual pages, and form submissions after every update. The tests required user logins to be simulated and then access to various site functions could be tested. As workflows can be quite complex, a full test requires the orchestration of a number of different users (editor, approver, publisher) to complete a test.
Integrating Cypress with GitHub Actions reduced manual QA time by 80% and caught regressions that traditional testing missed. This automation ensured smoother launches and higher user satisfaction.
The use of such tests is able to provide greater reassurance to clients that complex business logic remains functioning after the initial feature has been implemented.
Conclusion
Integrating Cypress tests into your Drupal CI pipeline via GitHub Actions supercharges your development process. You get faster feedback loops, more reliable releases, and happier users. By following this guide, you can set up a robust and scalable testing infrastructure for your Drupal site in less than a day.