DEV Community

Mike Wu
Mike Wu

Posted on

Setting up E2E Tests in Gitlab CI: Laravel + React + Cypress

This is an overview of how we got our e2e cypress tests running for our Laravel backend, and React frontend. πŸš€

Even if your stack is identical to ours, your environment may still slightly differ depending on versions, and providers. This post is only meant to serve as a guide, and not as a copy-paste solution.

Sections

  • .gitlab-ci.yml - Gitlab CI config
  • API Docker Image - Laravel app

TL;DR, Create a single Docker image for your Laravel App + webserver, and use it as a Gitlab service

Motivation

I've spent the last 2 days struggling to make this happen. Through a series of small undocumented wins, we finally reached that sweet βœ… pipeline. Seeing as I've had to piece together information from various sources, and travel back in time, I thought I'd summarize my findings here in case it helps anyone with a similar stack.

.gitlab-ci.yml

E2E Local Tests:
  image: cypress/browsers:node16.13.0-chrome95-ff94
  services:
  - mysql:5.7
  - name: registry.gitlab.com/your_project/api_ci:latest
    alias: api
  variables:
    # Create separate network, required for services to talk to each other
    # Reference: https://docs.gitlab.com/ee/ci/services/#connecting-services
    FF_NETWORK_PER_BUILD: 1
    MYSQL_DATABASE: myapp
    MYSQL_ROOT_PASSWORD: secret
    DB_USERNAME: root
    DB_DATABASE: myapp
    DB_PASSWORD: secret
    DB_HOST: mysql
    REACT_APP_API_URL: http://api:8000
  script:
    # Verify the api is up, and running (optional)
    - curl http://api:8000
    # Install npm packages, and start server in background
    - npm install
    - npm run start&
    - sleep 120 # Wait for server to be up
    - npm run e2e:local # run tests
Enter fullscreen mode Exit fullscreen mode
  • Set FF_NETWORK_PER_BUILD to tell Gitlab to create a netowrk for our job. This is required if you want your services to talk to each other, ie., api, and db, which we do.
  • registry.gitlab.com/your_project/api_ci:latest would be your API Laravel app, bundled in a single docker image with your web-server. In our case it was our app + nginx (with php-fpm).
  • Aliased the API service to api
  • Make sure you set the DB_HOST to the service name as well, in this case mysql.
  • Tell React app the API url is now http://api:8000, where api is the alias we gave our service above.
  • Start react app with & to run in background.

API Docker Image - Laravel app

To get the API up, and running, 2 solutions came to mind:

  1. Use git to pull in the project, and setup volume mounts + nginx service
  2. Bundle everything together in a single Docker image

Option 2. is definitely the simpler option, so that's the one we went with.

Dockerfile

# Build
FROM php:7.4-fpm as build

# Install PHP dependencies to get Laravel up, and running
RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libzip-dev \
    cron \
    openssh-client

# Install xdebug for code coverage
RUN pecl install xdebug \
  && docker-php-ext-enable xdebug

# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/
RUN docker-php-ext-install -j$(nproc) gd pdo_mysql zip bcmath pcntl

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Where php-fpm expects the project files to live
WORKDIR /var/www

# FPM default user (www-data) must own the files,
# or we'll hit a permission error in Laravel
RUN chown -R www-data:www-data /var/www

# Include a docker-init.sh (optional)
COPY ./Docker/dev/docker-init.sh /usr/local/bin/docker-init.sh
RUN chmod +x /usr/local/bin/docker-init.sh

# Bake our entire project into the image
COPY . .


# Copy configs

COPY supervisord.conf /etc/supervisord.conf
COPY nginx-site.conf /etc/nginx/conf.d/default.conf

# Copy start script
COPY start.sh /start.sh
RUN chmod 755 /start.sh

# IMPORTANT - need to tell gitlab which port to check, otherwise it will timeout at 'waiting at services'
EXPOSE 8000

ENTRYPOINT [ "docker-init.sh" ]

CMD ["/start.sh"]
Enter fullscreen mode Exit fullscreen mode
  • Installed nginx & supervisor to run the app.
  • Copied all files into the image.
  • docker-init.sh custom script that does prep stuff like run migrations, and seeders.
  • start.sh is the final docker command, in this case we're starting supervisor

Bonus: docker-init.sh

Here's where we init the app.

#!/bin/sh

# Exit if any fails
set -e

# Install dependencies / upgrade packages
composer install

# Clear cache/config to make sure env is read
php artisan route:clear
php artisan config:clear

# Migrate
php artisan migrate:refresh --seed

php artisan storage:link

# execute default entrypoint
docker-php-entrypoint $@
Enter fullscreen mode Exit fullscreen mode

Next we have our start script, which is really only just starting supervisor.

start.sh

#!/bin/bash
# Start supervisord and services
exec /usr/bin/supervisord -n -c /etc/supervisord.conf
Enter fullscreen mode Exit fullscreen mode

Build & Run

Once you've got your Dockerfile ready, build, and push it to your private Gitlab repository.

Note that the image name has to be the full URL of the repository.

Other Issues

App reading wrong ENV

If you find your API not reading the ENV set in either .gitlab-ci.yaml or the Dockerfile, it's because php-fpm aren't reading those values. Most likely because it was started by supervisor.

The fix is to make sure you use the same values in a .env file.

Top comments (0)