GitLab CI: базовые строительные блоки и E2E-пайплайн
04 июля 2026
В данной статье хотел бы поделиться базовыми навыками настройки Gitlab CI. Данные знания полезны как для расширения
кругозора, использовании в пет проектах и более сложены, так и в при общение с devops. Предлагаю рассмотреть базовые
возможности и создать классический пайплайн build -> deploy -> test.

Базовые строительные блоки
После push, создания merge request, ручного запуска или вызова из другого проекта GitLab создаёт pipeline. Pipeline состоит из jobs — отдельных задач, которые выполняются GitLab Runner. Каждая job запускается в изолированном окружении. Чаще всего это Docker-контейнер с указанным образом: Node.js для frontend-сборки, Maven для Java-проекта, Playwright для браузерных тестов и так далее.
job: отдельная задача
Job — это именованный блок YAML. Одна job должна отвечать за одну понятную задачу. Например, build, deploy_staging
или e2e_tests.
build: # имя для отображения job в UI
stage: build # этап pipilene
image: node:24.15.0-slim # Docker-образ, внутри которого будет выполняться job
script:
- npm ci
- npm run build
где:
- имя
buildотображается в интерфейсе GitLab. - поле
stageопределяет этап пайплайна, - а
scriptсодержит команды, которые будет выполнять runner. - Поле
imageуказывает Docker-образ, внутри которого будет выполняться job.
image: окружение для запуска
Для image не стоит использовать тег latest. Он делает пайплайн непредсказуемым: новая версия образа может изменить версию Node.js, браузера или системных библиотек. Лучше фиксировать конкретную версию.
script: команды внутри job
Поле script — это список shell-команд, которые runner выполняет последовательно. Если любая команда завершится с
ненулевым кодом, job будет отмечен как failed, а следующий этап пайплайна обычно не начнётся.
Для сложной логики удобно использовать многострочный shell-скрипт:
e2e-tests:
script:
- |
if [ "$TEST_TYPE" = "all" ]; then
npx playwright test
else
npx playwright test --grep "@$TEST_TYPE"
fi
Но слишком большую бизнес-логику лучше выносить в файлы репозитория: например, ./ci/deploy-staging.sh. Тогда скрипт
можно запустить и локально, а YAML останется читаемым.
cache: ускорение установки зависимостей
Cache нужен для переиспользования зависимостей между job и пайплайнами. Для Node.js это обычно папка npm-кеша, а не node_modules.
cache:
key:
files:
- package-lock.json
paths:
- .npm/
Ключ кеша строится по package-lock.json. Когда зависимости не меняются, следующий пайплайн сможет использовать уже скачанные пакеты. После изменения lock-файла GitLab создаст новый кеш.
Cache не гарантирует наличие файлов и не должен использоваться для передачи результатов сборки. Его задача — ускорять
повторяющиеся операции, например загрузку npm-пакетов. GitLab рекомендует использовать cache для зависимостей, а
artifacts — для передачи результатов работы job между этапами.
Однако может так оказаться, что быстрее выкачать зависимости заново, чем читать их с диска из кеша.
stage: показывают общий порядок
Jobs можно объединять в stages. Стадии выполняются последовательно, а задачи внутри одной стадии — параллельно, если
между ними нет явных зависимостей, например конфигурация может выглядеть так:
stages:
- build
- test
build:
stage: build
image: node:24.15.0-slim
script:
- npm ci
- npm run build
unit-tests:
stage: test
image: node:24.15.0-slim
script:
- npm ci
- npm run test
Здесь определены две стадии. Сначала GitLab выполнит build, а после успешной сборки перейдёт к unit-tests. Причем
результат работы каждой job можно созранять и передавать в другоую через artifacts.
Стадии полезны для визуального порядка, но иногда они создают лишнее ожидание. Если одна job зависит только от
конкретной задачи, лучше указать это через needs.
artifacts: результаты работы job
Artifacts — это файлы, которые GitLab сохраняет после завершения job. Их можно скачать из интерфейса GitLab или передать следующей job.
Для E2E-тестов стоит сохранять отчёт, скриншоты, видео и результаты тестов даже при ошибке:
e2e-tests:
stage: test
script:
- npx playwright test
artifacts:
when: always
expire_in: 7 days
paths:
- playwright-report/
- test-results/
Значение when: always особенно важно для тестов. Без него GitLab не загрузит артефакты, если тест упадёт, а значит не
получится открыть скриншот или видео падения. Пути в artifacts.paths задаются относительно корня проекта.
needs: явные зависимости пайплайна
По умолчанию GitLab ждёт завершения всей предыдущей стадии. needs превращает пайплайн из строго линейной
последовательности в граф зависимостей и позволяет одной job стартовать после выполнения
другой или других.
build:
stage: build
script:
- npm ci --cache .npm
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 day
deploy_staging:
stage: deploy
needs:
- job: build
artifacts: true
script:
- ./ci/deploy-staging.sh dist
Здесь deploy_staging ждёт build и применяет deploy-staging.sh к артефакту dist/.
rules: когда добавлять job в pipeline
rules определяет условия, при которых job появится в пайплайне.
deploy_staging:
stage: deploy
script:
- ./ci/deploy-staging.sh
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Такая job будет запускаться только для default branch, например main или master.
e2e-tests:
rules:
- if: '$CI_PIPELINE_SOURCE == "pipeline"'
- if: '$CI_PIPELINE_SOURCE == "web"'
when: manual
- when: never
В этом примере E2E-тесты автоматически выполняются, когда их вызвал другой pipeline, и доступны вручную при запуске через интерфейс GitLab.
rules проверяются при создании пайплайна: первая подходящая запись определяет поведение job.
variables: env переменные job
В GitLab CI переменные используют для URL стендов, токенов, паролей, версий и параметров запуска.
variables:
API_URL: "https://api-staging.example.com"
В job эта переменная доступна как обычная переменная окружения:
healthcheck:
script:
- curl --fail "$API_URL/health"
Секреты нельзя хранить в .gitlab-ci.yml. Токены, пароли и ключи нужно добавлять в настройках проекта или группы как
CI/CD Variables. Для production-значений обычно включают флаги Masked и Protected или подключают хранилище паролей
Vault.
inputs: параметры запуска пайплайна
Переменные подходят для runtime-настроек и секретов. Для параметров, которые описывают структуру пайплайна и должны
проверяться при его создании, лучше использовать inputs. Inputs позволяют явно описать параметры пайплайна: тип,
значение по умолчанию, описание и допустимые варианты.
spec:
inputs:
test_type:
description: "Тип запускаемых тестов"
options:
- all
- smoke
- gold
- bronze
- silver
default: smoke
--- # обязательно разделять inputs сверху от job ниже
После разделителя --- можно использовать значение input через выражение $[[ inputs.<имя> ]].
variables:
TEST_TYPE: $[[ inputs.test_type ]]
Полная конфигурация может выглядеть так:
spec:
inputs:
base_url:
description: "Адрес стенда для тестирования"
type: string
test_type:
description: "Тип тестов"
options: [all, smoke, gold, bronze, silver]
default: smoke
---
stages:
- test
e2e-tests:
stage: test
image: mcr.microsoft.com/playwright:v1.58.1-jammy
variables:
BASE_URL: $[[ inputs.base_url ]]
TEST_TYPE: $[[ inputs.test_type ]]
script:
- npm ci
- |
if [ "$TEST_TYPE" = "all" ]; then
npx playwright test
else
npx playwright test --grep "@$TEST_TYPE"
fi
Если у input нет default, он становится обязательным. Значения inputs подставляются во время создания pipeline, до
запуска jobs. Адрес стенда, набор тестов или имя окружения — хорошие кандидаты для input. Пароль, токен доступа и API
key — нет.
trigger: запуск пайплайна в другом проекте
В больших командах приложение и E2E-тесты часто находятся в разных репозиториях:
frontend-app
└── build → deploy → trigger
e2e-tests
└── prerequisite → test → report
После деплоя приложение запускает pipeline другого репозитория с тестами.
trigger-e2e:
stage: post-deploy
trigger:
project: my-group/e2e-tests
branch: main
strategy: mirror
inputs:
base_url: "https://staging.example.com"
test_type: smoke
project указывает путь к проекту с автотестами, branch — ветку, из которой нужно взять его конфигурацию, а inputs
передаёт параметры запуска.
Без strategy trigger-job будет считаться успешной сразу после создания downstream pipeline. С strategy: mirror job
ждёт завершения тестов и получает тот же итоговый статус: success, failed, canceled или manual. Для современных
конфигураций GitLab рекомендует mirror; depend остаётся совместимым вариантом, но не рекомендуется для новых пайплайнов.
templates: шаблоны для переиспользования
По мере роста проекта .gitlab-ci.yml становится длинным. Повторяющиеся части стоит выносить в отдельные файлы и
подключать через include. Основной файл при этом становится компактным:
stages:
- build
- deploy
- post-deploy
include:
- local: ".gitlab/ci/build.yml"
- local: ".gitlab/ci/deploy.yml"
- local: ".gitlab/ci/e2e.yml"
Также можно подключать шаблоны из другого проекта:
include:
- project: "my-group/ci-templates"
ref: "v1.4.0"
file: "/templates/playwright.yml"
GitLab поддерживает локальные, удалённые, проектные и встроенные include-файлы. Подключённая конфигурация объединяется с основной, а значения из основного файла могут переопределять значения шаблона. Для формального переиспользования можно применять CI/CD components. Они принимают inputs и позволяют не копировать одинаковые jobs между репозиториями.
include:
- component: gitlab-pipelines/autotests-component@components
inputs:
job-suffix: ":playwright"
test-tags: "ui"
test-profile: "staging"
Такой подход особенно полезен, когда десятки проектов используют одинаковую логику запуска автотестов, публикации отчётов или проверки качества.
Summary
GitLab CI удобно воспринимать как набор простых строительных блоков:
imageзадаёт окружение;scriptвыполняет команды;cacheускоряет повторные установки зависимостей;artifactsпередают результаты между jobs и сохраняют отчёты;stagesпоказывают общий порядок;needsописывает реальные зависимости;rulesопределяет условия запуска;inputsделает pipeline настраиваемым;triggerсвязывает несколько репозиториев;includeиcomponentsпомогают не копировать одинаковую конфигурацию.
Начать можно с двух jobs — build и test. Затем добавить artifacts, deploy на staging и E2E-проверки. Главное —
постепенно усложнять пайплайн и сохранять его структуру понятной для команды.
Пример e2e pipeline: build -> deploy -> test.
Рассмотрим основной проект с frontend-приложением. После сборки он публикует файлы на staging-стенд, а затем запускает E2E-тесты в отдельном проекте.
workflow:
rules:
# Создаём pipeline для merge request.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
# Создаём pipeline после push в default branch.
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
# Для остальных случаев pipeline не нужен.
- when: never
stages:
- build
- deploy
- post-deploy
variables:
BUILD_DIR: "dist"
STAGING_URL: "https://staging.example.com"
cache:
key:
files:
- package-lock.json
paths:
- .npm/
build:
stage: build
image: node:24.15.0-slim
script:
# Используем npm cache из папки проекта.
- npm ci --cache .npm --prefer-offline
# Создаём production-сборку.
- npm run build
artifacts:
# Передаём результат сборки в deploy job.
paths:
- $BUILD_DIR/
expire_in: 1 day
rules:
# Сборка нужна и в merge request, и в default branch.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
deploy-staging:
stage: deploy
image: alpine:3.21
needs:
- job: build
artifacts: true
script:
# Скрипт должен загрузить dist/ на staging.
# Конкретная реализация зависит от инфраструктуры:
# S3, Kubernetes, SSH, Helm, CDN и т.д.
- chmod +x ./ci/deploy-staging.sh
- ./ci/deploy-staging.sh "$BUILD_DIR"
environment:
name: staging
url: $STAGING_URL
rules:
# Не деплоим каждую ветку: staging обновляется только из default branch.
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
trigger-e2e:
stage: post-deploy
needs:
- deploy-staging
trigger:
# Репозиторий с Playwright-тестами.
project: my-group/e2e-tests
branch: main
# Родительский pipeline ждёт результат E2E-тестов.
strategy: mirror
# Передаём параметры в downstream pipeline.
inputs:
base_url: "https://staging.example.com"
test_type: smoke
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Теперь добавим конфигурацию проекта e2e-tests.
spec:
inputs:
base_url:
description: "URL стенда, на котором будут выполняться тесты"
type: string
test_type:
description: "Набор тестов"
options:
- all
- smoke
- gold
- bronze
- silver
default: smoke
---
stages:
- prerequisite
- test
variables:
BASE_URL: $[[ inputs.base_url ]]
TEST_TYPE: $[[ inputs.test_type ]]
cache:
key:
files:
- package-lock.json
paths:
- .npm/
e2e-tests:
stage: test
image: mcr.microsoft.com/playwright:v1.58.1-jammy
needs:
- job: prerequisite
artifacts: true
script:
- npm ci --cache .npm --prefer-offline
- |
if [ "$TEST_TYPE" = "all" ]; then
npx playwright test
else
npx playwright test --grep "@$TEST_TYPE"
fi
artifacts:
# Отчёт и вложения сохраняются даже при падении тестов.
when: always
expire_in: 7 days
paths:
- playwright-report/
- test-results/
reports:
junit: test-results/junit.xml
rules:
# Автоматический запуск из pipeline приложения.
- if: '$CI_PIPELINE_SOURCE == "pipeline"'
# Возможность вручную запустить тесты из GitLab UI.
- if: '$CI_PIPELINE_SOURCE == "web"'
when: manual
- when: never
Получается следующий поток:
- Разработчик делает merge в main.
- GitLab собирает приложение.
- Job deploy-staging публикует dist/ на staging.
- trigger-e2e запускает downstream pipeline.
- Playwright запускает smoke-тесты на staging.
- В случае ошибки GitLab сохраняет видео, скриншоты и отчёт.
- Результат E2E-тестов возвращается в основной pipeline.
Если E2E-тесты завершатся с ошибкой, trigger-e2e также станет failed. Это делает качество деплоя видимым прямо в
основном pipeline.