How to Automate Test Runs with Continuous Integration
Set up continuous integration to run tests on code changes. Compare GitHub Actions and GitLab CI/CD, with starter configurations, troubleshooting, and practical guidance.
Continuous integration (CI) runs your project’s build and tests automatically when code changes, so problems can be caught while the change is being reviewed. To start, put a workflow or pipeline file in your repository, select a trigger such as a push or pull request, prepare the runner environment, install dependencies, and run the same test command developers use locally.
This guide shows starter configurations for GitHub Actions and GitLab CI/CD. The examples use Python and pytest; replace the setup and test command with the ones your project already uses. The configuration patterns apply across languages, but dependency installation and runtime setup are project-specific.
1. Identify the test command CI should run
Before editing a workflow, find the command that runs the relevant tests on a developer’s machine. For example, a Python project might use python -m pytest, a Node.js project might use npm test, and another project might use a build tool’s test task. Use the command already documented by the repository where possible.
Also identify what that command assumes:
- Which language runtime and version are required?
- Which package manager and lockfile should CI use?
- Does setup require a database, service, environment variable, or generated file?
- Should the job run the whole suite, a subset, or tests plus another check?
Keeping CI aligned with local development makes failures easier to reproduce. If a test needs external services or secrets, document those requirements and configure them deliberately in the CI provider rather than relying on a developer’s machine state.
2. Choose a CI provider and trigger
Use the service that fits where the repository is hosted and how the team wants to organize and run jobs. GitHub Actions stores workflow YAML files in .github/workflows. GitLab CI/CD normally reads a YAML pipeline definition from .gitlab-ci.yml at the repository root. GitHub describes CI as a way to run tests and show their results on pull requests; GitLab describes pipelines configured with YAML keywords. GitHub: Continuous integration · GitLab: CI/CD pipelines
Common triggers include pushes, pull or merge requests, schedules, and manual runs. Start with the events where test feedback is useful, often pushes and proposed changes. Add scheduled or manual runs when the project has a reason for them. Provider syntax and event names differ, so check the relevant documentation when adjusting triggers.
3. GitHub Actions example
Create .github/workflows/tests.yml in the repository. This illustrative workflow checks out the code, selects Python, installs dependencies, and runs pytest on pushes and pull requests:
name: Tests
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
- name: Run tests
run: python -m pytest
Adapt the dependency steps to the repository. If the project declares pytest and all test dependencies in its requirements or package configuration, install from that declaration instead of installing pytest separately. If it uses a different lockfile or package manager, use the corresponding reproducible install command. Pin or select a runtime version that the project supports.
A workflow contains triggers and jobs; a job selects a runner and has steps that perform the work. GitHub-hosted and self-hosted runners are available, and jobs can execute sequentially or in parallel. See Understanding GitHub Actions for the workflow model and execution options.
4. GitLab CI/CD example
Create .gitlab-ci.yml at the repository root. This example defines a test stage and one job. The job runs on a runner that can execute the selected container image:
image: python:3.12
stages:
- test
python-tests:
stage: test
script:
- python -m pip install --upgrade pip
- pip install -r requirements.txt
- pip install pytest
- python -m pytest
As with the GitHub example, change the image, dependency installation, and test command to match the project. GitLab jobs are executed by runners; a suitable runner must be available for the job’s environment. Pushes and merge requests are common pipeline triggers, and GitLab also documents scheduled and manual starts. Review GitLab’s pipeline documentation and Get started with GitLab CI/CD when configuring project-specific behavior.
5. Make the first run useful
- Commit the configuration file and push it to the repository.
- Open the provider’s pipeline or Actions interface and inspect the run triggered by the change.
- If a job fails, identify whether setup, dependency installation, environment configuration, or a test failed.
- Reproduce the test command locally when possible, fix the cause, and push another change.
- Confirm that the intended push or pull/merge request event starts the job and that results appear where reviewers can see them.
A failing check is an early signal to investigate, not proof that a particular change caused the problem. Read the logs and examine the change. CI improves feedback, but it cannot guarantee defect-free software.
6. Compare GitHub Actions and GitLab CI/CD
| Decision | GitHub Actions | GitLab CI/CD |
|---|---|---|
| Configuration location | Workflow YAML files in .github/workflows |
Usually .gitlab-ci.yml at the project root |
| Job organization | Workflows contain jobs and steps; jobs may run sequentially or in parallel | Pipelines contain stages and jobs; stages run in sequence and jobs within a stage can run in parallel |
| Triggers | Repository events, schedules, manual triggers, and external events | Pushes, merge requests, schedules, and manual starts |
| Execution environment | GitHub-hosted or self-hosted runners; virtual machine or container execution | Jobs execute on available GitLab runners |
| Useful selection factors | Repository location, needed events, runner control, and preferred workflow configuration | Repository location, needed events, runner control, and preference for stage-based organization |
Neither configuration style is universally best. Consider where the repository lives, which events should run tests, what control the team needs over runners, and how the team wants to organize jobs. GitLab’s stages make sequential pipeline phases explicit. GitHub Actions expresses dependencies between jobs when one must wait for another. Both can support parallel work, subject to runner availability and the nature of the tasks.
7. Add structure only when it helps
Begin with one reliable test job. Once it works, add separation when it improves clarity or gives useful feedback—for example, distinct jobs for unit tests and another independent check, or separate stages for test and build work. Parallel execution can reduce elapsed time when work is independent and runner capacity is available, but it does not make every suite faster automatically.
Keep configuration understandable:
- Use the project’s existing test command and dependency source.
- Make runtime and required services explicit.
- Keep credentials out of committed YAML; use the provider’s secret mechanism for credentials that are truly required.
- Split jobs when their environments, purpose, or failure feedback differ meaningfully.
- Choose timeouts, retries, and coverage requirements based on the project; there is no universal value for every repository.
8. Troubleshooting common failures
| Symptom | Likely cause | What to check |
|---|---|---|
| No pipeline starts | The workflow file is in the wrong location, YAML is invalid, or the configured event does not match the change. | Check the file path, provider’s configuration validation and run history, and trigger filters. |
| Dependency installation fails | The runner uses an incompatible runtime, the install command does not match the package manager, or a dependency requires unavailable system tooling. | Compare runtime and install steps with project documentation; inspect the first installation error in the log. |
| Tests pass locally but fail in CI | Different runtime, environment variables, service dependencies, filesystem assumptions, or timing can change behavior. | Compare the runner environment to local requirements and make required setup explicit in the job. |
| Command or test files are not found | The job runs from a different working directory or the command differs from the repository’s actual test command. | Check the repository layout and run command; set a working directory only if the project requires it. |
| GitLab job stays pending | No eligible runner is available or the job’s runner requirements do not match. | Check runner availability and any tags or environment requirements configured for the job. |
| Parallel jobs fail intermittently | Jobs may depend on shared state or each other, or may compete for constrained resources. | Check for hidden ordering or shared-resource assumptions; serialize dependent work and parallelize only independent jobs. |
| Secrets are missing | The workflow expects a variable that is not configured for that event or project. | Check the provider’s secret and variable settings and whether the event is allowed to access them. Avoid printing secret values in logs. |
9. Performance, reliability, and cost considerations
Pipeline duration depends on the test workload, dependency installation, runner environment, and available capacity. First make the job repeatable and representative. Then inspect which steps consume time and consider dependency caching or independent jobs if the provider and project support them. Verify that any cache key accounts for dependency changes so stale packages do not undermine reliability.
Hosted and self-hosted runners are different operational choices. GitHub documents both options and VM or container execution. A self-hosted runner can provide more control over the environment, while also making runner maintenance part of the team’s work. Choose based on project requirements and available operations capacity; the cited guidance does not establish a universal cost or security advantage for either mode.
Runner usage and pricing depend on the provider, plan, runner type, and usage. Review the current provider terms for the repository before expanding jobs or running workflows more often. Keep an eye on repeated dependency downloads and unnecessary triggers, but do not sacrifice useful feedback solely to minimize run count.
10. Or skip the browser setup
For web projects, CI can also capture a page to review its rendered state. A browser-based setup requires maintaining browser dependencies and capture code. ScreenshotNeo offers a single GET request for a screenshot, and its API parameters use names familiar from other screenshot APIs. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie or consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers say which page verdict and billing status applied. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan. Learn more at ScreenshotNeo and the API docs.
Sign up for 1,000 free screenshots a month, with no card required.
Frequently asked questions
Does CI replace running tests locally?
No. Local runs help developers get fast feedback; CI checks changes in a shared, configured environment.
Do I need a separate CI job for every test file?
No. Start with the project’s normal test command. Split work only when separate jobs improve organization, environment isolation, or feedback.
Will a green pipeline prove the release has no bugs?
No. It means the configured checks passed in that run. The result depends on what the tests cover and the conditions exercised.
Can a scheduled pipeline use the same test command?
Usually. A scheduled run can invoke the same job, while the trigger configuration determines when it starts.
Which provider should a new project choose?
Start with repository hosting, trigger requirements, runner needs, and the configuration model the team can maintain. The documented capabilities do not make one provider the right choice for every project.


