Resource library

QA How-To

Test Automation CI/CD Complete Guide (2026)

Test automation CI CD complete guide for building secure, fast pipelines with Playwright, GitHub Actions, GitLab CI, quality gates, artifacts, and more.

20 min read | 3,367 words

TL;DR

Build test automation in CI/CD as a layered quality system: deterministic setup, fast required checks, isolated parallel regression, short-lived credentials, controlled flaky-test handling, and evidence that uploads even after failure.

Key Takeaways

  • Layer fast smoke checks and broad regression suites by trigger and release risk.
  • Pin runtimes and lockfile dependencies so local and CI execution remain reproducible.
  • Parallelize only isolated tests and preserve shard-specific evidence.
  • Use OIDC and least privilege instead of long-lived cloud credentials.
  • Treat retries as diagnostics and quarantine as a temporary, owned policy state.
  • Upload redacted reports and metadata after both passing and failing runs.

A reliable test automation CI/CD complete guide must do more than paste a test command into a workflow. It must explain how to create reproducible environments, divide suites, protect credentials, control flaky tests, preserve evidence, and turn results into an enforceable release signal. This guide gives you that complete system using GitHub Actions, GitLab CI, Playwright, and portable design principles.

You will build a reference pipeline that installs dependencies deterministically, runs fast checks before expensive tests, shards browser tests, uploads reports, and blocks a release when required quality gates fail. The examples use Node.js 22 and Playwright, but the architecture also applies to Selenium, Cypress, API suites, and mixed-language test portfolios.

TL;DR: Test Automation CI CD Complete Guide

Treat the pipeline as a product, not a command runner. Pin the runtime, restore only safe caches, install browsers explicitly, separate required tests from optional diagnostics, and always publish evidence even after failure.

Concern Weak pipeline Production-ready pipeline
Environment Uses whatever the runner provides Pins runtime and lockfile dependencies
Credentials Stores long-lived cloud keys Uses short-lived OIDC credentials
Execution One large sequential job Uses stages, tags, and parallel shards
Flaky tests Blind retries hide failures Measures retries and quarantines with ownership
Evidence Logs disappear with the job Publishes reports, traces, screenshots, and metadata
Release decision A human reads the log Required checks enforce an explicit quality gate

What You Will Build

By the end, you will have:

  • A small Playwright suite with fast smoke and broader regression projects.
  • A GitHub Actions pipeline with deterministic installation, sharding, artifacts, and concurrency control.
  • A GitLab CI equivalent that demonstrates stages, parallel execution, and report collection.
  • A quality-gate policy that distinguishes product failures, infrastructure failures, and quarantined tests.
  • A practical operating model for security, observability, ownership, and continuous improvement.

The goal is not one vendor-specific YAML file. It is a reusable mental model you can apply when your organization changes runners, test frameworks, or deployment platforms.

Prerequisites

Use Node.js 22 LTS or a currently supported later LTS release, npm with a committed package-lock.json, Git, and a repository on GitHub or GitLab. The commands below assume a clean project directory.

node --version
npm --version
git --version
npm init -y
npm install --save-dev @playwright/test
npx playwright install --with-deps chromium

You also need permission to add CI configuration and repository settings. For deployment tests, use a nonproduction environment with synthetic accounts. Never run destructive automation against production unless the suite and data controls were explicitly designed for it.

Before continuing, run npx playwright --version and npx playwright install chromium. Both commands must finish successfully. Commit the lockfile so CI installs the same dependency graph that you tested locally.

Step 1: Define the Test Contract Before Writing YAML

A pipeline becomes predictable when every suite has a purpose, trigger, maximum duration, owner, and failure policy. Start by classifying tests instead of sending every test into every workflow.

Suite Trigger Expected scope Gate policy
Static checks Every pull request Types, lint, configuration Required
Smoke Every pull request and deploy Critical user journeys Required
Regression Merge, schedule, release candidate Broad functional coverage Required for release
Destructive or load Manual or isolated schedule Controlled environment Separately approved
Quarantined Schedule and diagnostic jobs Known unstable tests Visible, not silently ignored

Tag tests by business meaning, not by their current directory alone. A checkout smoke test can live beside other checkout tests while still being selectable with @smoke. Keep the required pull-request path short enough to provide useful feedback. Move expensive coverage to parallel jobs or later triggers rather than deleting it.

Write the contract in the repository. Include the suite owner, target environment, test-data assumptions, retry policy, artifact retention expectation, and escalation route. This turns a failed job from a mystery into an actionable event.

Verify: Review the table with developers and release owners. Every required job should have a clear answer to: what risk does this detect, who responds, and can a release proceed if it fails?

Step 2: Create a Deterministic Playwright Test Project

Create playwright.config.ts:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: Boolean(process.env.CI),
  retries: process.env.CI ? 1 : 0,
  workers: process.env.CI ? 2 : undefined,
  reporter: [
    ['line'],
    ['html', { outputFolder: 'playwright-report', open: 'never' }],
    ['junit', { outputFile: 'test-results/junit.xml' }]
  ],
  use: {
    baseURL: process.env.BASE_URL || 'https://example.com',
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure'
  },
  projects: [
    { name: 'smoke', grep: /@smoke/, use: { ...devices['Desktop Chrome'] } },
    { name: 'regression', grepInvert: /@smoke/, use: { ...devices['Desktop Chrome'] } }
  ]
});

Create tests/home.spec.ts:

import { test, expect } from '@playwright/test';

test('home page exposes its primary heading @smoke', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
});

test('page has a descriptive title', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveTitle(/Example Domain/i);
});

Add scripts to package.json:

{
  "scripts": {
    "test:e2e": "playwright test",
    "test:smoke": "playwright test --project=smoke",
    "test:regression": "playwright test --project=regression"
  }
}

The configuration makes accidental test.only fail in CI, keeps one retry for diagnostic value, and collects three report formats. A retry does not erase the initial failure: the HTML report and trace retain evidence.

Verify: Run npm run test:smoke and then npm run test:regression. Each command should execute one test and exit with code 0. Open playwright-report/index.html locally with npx playwright show-report.

Step 3: Build a Fast Pull Request Pipeline

Create .github/workflows/test.yml:

name: automated-tests

on:
  pull_request:
  push:
    branches: [main]

concurrency:
  group: tests-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  smoke:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - name: Run smoke tests
        run: npm run test:smoke
        env:
          BASE_URL: https://example.com
      - name: Upload test evidence
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: smoke-evidence
          path: |
            playwright-report/
            test-results/
          if-no-files-found: warn
          retention-days: 14

npm ci honors the lockfile and fails when it disagrees with package.json. The setup action's npm cache stores downloaded package data, not node_modules. Installing the browser with system dependencies avoids relying on undocumented runner state.

Use minimal job permissions. Actions receive a repository token, so broad defaults create needless exposure. Pin third-party actions to a commit SHA in a high-assurance repository, and use dependency update automation to keep those pins current.

The always() condition matters. Without it, the artifact step is skipped after the test command fails, which removes the evidence needed to investigate. A test job should fail because the assertion failed, while its cleanup and publishing steps still run.

Verify: Open a pull request. Confirm the smoke job runs, the summary shows one passing test, and the job contains a smoke-evidence artifact. Temporarily change the heading assertion, push again, and confirm the job fails while the artifact is still uploaded. Revert the intentional failure.

Step 4: Parallelize Regression Tests Without Losing Traceability

Add a regression job to the same GitHub Actions workflow:

  regression:
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - name: Run regression shard
        run: npx playwright test --project=regression --shard=${{ matrix.shard }}/4
      - name: Upload shard evidence
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: regression-${{ matrix.shard }}
          path: |
            playwright-report/
            test-results/
          if-no-files-found: warn

Sharding distributes test files across independent machines. It is useful only when tests are isolated. A test must not depend on another test creating an account, leaving a cart populated, or running first. Create unique test data per worker and clean it through an API or expiration policy.

Set fail-fast: false so one failing shard does not cancel the evidence from the others. Give artifact names the shard identity. For large suites, use Playwright blob reports and merge them in a dependent job. The dedicated GitLab CI child pipelines tutorial shows another way to split ownership and execution graphs.

Do not choose a shard count by fashion. Measure queue time, setup time, execution time, and runner capacity. Too many shards repeat installation overhead and may overload the application or shared test data service.

Verify: Push to main in a safe branch or temporarily allow the job on your test branch. Confirm four matrix jobs appear, every job has a distinct shard value, and the union of their reports contains each regression test once.

Step 5: Model Stages and Quality Gates in GitLab CI

The same design transfers to GitLab. Create .gitlab-ci.yml in a GitLab repository:

stages:
  - validate
  - test

default:
  image: mcr.microsoft.com/playwright:v1.52.0-noble
  before_script:
    - npm ci
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - .npm/

variables:
  npm_config_cache: "$CI_PROJECT_DIR/.npm"

validate:
  stage: validate
  script:
    - npm run test:smoke
  artifacts:
    when: always
    paths:
      - playwright-report/
      - test-results/
    reports:
      junit: test-results/junit.xml

regression:
  stage: test
  parallel: 4
  script:
    - npx playwright test --project=regression --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
  artifacts:
    when: always
    paths:
      - playwright-report/
      - test-results/
    reports:
      junit: test-results/junit.xml
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

Use an image version that matches the Playwright package in your lockfile. The illustrative tag above must be updated alongside your dependency rather than copied forever. A mismatch between package and browser image can cause browser executable errors.

GitLab's parallel variables map directly to Playwright's one-based shard syntax. JUnit reports make test cases visible in the pipeline UI, while path artifacts preserve the richer HTML output. Stage ordering ensures validation succeeds before regression consumes more runner capacity.

For independently owned suites, dynamic child pipelines can reduce a monolithic file and start only affected suites. Keep the parent responsible for global policy so child jobs cannot quietly remove release requirements.

Verify: Run the pipeline and inspect the graph. The validate job should precede four regression jobs on the default branch. Confirm the Tests view reads the JUnit file and artifacts remain downloadable after an intentionally failed assertion.

Step 6: Use Secure Test Environments and Short-Lived Credentials

CI tests often need cloud access to create test data or discover a deployed URL. Prefer workload identity federation through OpenID Connect over stored access keys. The runner receives a short-lived identity token, the cloud provider validates repository claims, and a narrowly scoped role issues temporary credentials.

In GitHub Actions, declare only the permissions required by the authentication step:

permissions:
  contents: read
  id-token: write

steps:
  - uses: actions/checkout@v4
  - name: Configure cloud identity
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/qa-ci-readonly
      aws-region: us-east-1
  - name: Read environment URL
    run: aws ssm get-parameter --name /qa/base-url --query Parameter.Value --output text

The trust policy must restrict organization, repository, branch or environment, and expected audience. Do not create a role that any repository can assume. Separate read-only discovery from deployment privileges, and protect production environments with approvals.

Masking is not authorization. A secret hidden in logs can still be stolen by malicious code, an unsafe pull-request trigger, or an overprivileged dependency. Avoid passing secrets to workflows from untrusted forks. Use synthetic accounts with the minimum application role and rotate any credential that cannot yet use federation.

Follow the GitHub Actions OIDC test environments tutorial for the complete trust-policy setup and environment protection workflow.

Verify: Inspect the authentication logs without printing tokens. Confirm the issued identity expires, the role cannot access resources outside the QA scope, and a workflow from an unauthorized branch cannot assume it.

Step 7: Control Flaky Tests Without Hiding Product Risk

A flaky test changes outcome without a meaningful product change. Retries can collect evidence, but a green retry is still a reliability signal. Record the initial failure, retry result, test name, environment, commit, and owner.

Use three states:

  1. Required: failures block the gate.
  2. Quarantined: the test runs in a visible nonblocking job with an owner and expiry date.
  3. Disabled: reserved for tests that cannot execute safely, tracked as missing coverage.

Never implement quarantine as an unreviewed test.skip scattered through code. Maintain a reviewed registry or tag, link it to an issue, and fail policy checks when the expiry passes. A quarantined test must continue running so the team can observe recovery and new failure patterns.

Investigate common sources systematically: shared test data, order dependence, weak locators, missing readiness signals, clock assumptions, network dependencies, and resource starvation. Replace fixed sleeps with observable conditions. Isolate accounts and namespaces by worker. Capture traces on the first retry to balance evidence and cost.

The detailed flaky test quarantine CI pipeline guide includes policy checks and expiry automation.

Verify: Introduce a controlled intermittent failure only in a sandbox branch. Confirm the first failure remains visible after a passing retry, and confirm a quarantined test appears in its diagnostic job rather than disappearing from execution.

Step 8: Publish Evidence and Make Failures Actionable

Every failed gate should answer: what failed, where, against which build, with what evidence, and who owns the response? Publish machine-readable JUnit for the CI interface and a human-friendly report for investigation. Retain traces, screenshots, videos, service logs, and deployment metadata only when useful.

Add a small metadata step before uploading artifacts:

mkdir -p test-results
node -e "require('fs').writeFileSync('test-results/run.json', JSON.stringify({commit: process.env.GITHUB_SHA, runId: process.env.GITHUB_RUN_ID, baseUrl: process.env.BASE_URL}, null, 2))"

Do not put passwords, tokens, customer records, or sensitive request bodies in reports. Configure test accounts with synthetic data and redact network logs. Set retention based on investigation and compliance needs, not unlimited convenience.

Separate test status from artifact status. A failed upload should be visible, but it should not rewrite an assertion failure into a misleading infrastructure diagnosis. Conversely, a passing test job with no expected report may indicate misconfiguration and should at least warn.

Use stable artifact names with suite, shard, browser, and attempt identifiers. Link the deployment version to the test run so investigators can reproduce the exact target. See how to publish test evidence as CI artifacts for merging reports and designing retention rules.

Verify: Download evidence from both a passing and failing run. Confirm run.json identifies the commit and target, the report opens without the CI workspace, and a security review finds no secrets or personal data.

Step 9: Operate the Pipeline as an Engineering System

Measure the pipeline at suite and test level. Useful signals include queue duration, setup duration, execution duration, pass rate before retry, retry recovery rate, cancellation rate, infrastructure error rate, and time to a first actionable failure. Do not celebrate a shorter duration if failures become harder to diagnose.

Assign ownership in CODEOWNERS or a test catalog. Route notifications by service and severity instead of sending every failure to one crowded channel. A pull-request smoke failure should return to the author. A scheduled regression finding may route to the owning team with the commit and artifact link.

Review dependencies and runner images on a cadence. Test upgrades in a branch, update action pins, and remove obsolete compatibility workarounds. Keep a small canary workflow that validates runner assumptions before a release-critical suite discovers them.

Control cost through trigger design, concurrency cancellation, affected-test selection, and appropriate parallelism. Do not reduce cost by removing evidence or silently relaxing gates. Use a manual rerun for infrastructure incidents, but require a reason when overriding a release gate.

Verify: Create a dashboard or weekly report using CI metadata. Pick one slow or unreliable area, make a change, and compare the same signals over multiple runs. The improvement should be observable and should not reduce coverage or diagnostic quality.

Test Automation CI CD Complete Guide Architecture Decisions

Choose patterns according to risk and feedback needs.

Decision Choose this when Watch for
Hosted runners You want low operations overhead Queue variability and limited customization
Self-hosted runners Tests need private networks or special hardware Isolation, patching, cleanup, and capacity
Containers You need repeatable OS dependencies Image/package version mismatch
Matrix jobs Dimensions are known at workflow authoring time Job explosion and repeated setup
Dynamic child pipelines Suites are numerous or independently owned Governance spread across generated config
Full regression per merge Risk is high and suite is affordable Slow feedback and runner saturation
Scheduled regression Broad suite is too expensive per change Defects detected later

Most teams need a layered answer: static checks and smoke tests on pull requests, targeted integration tests for affected services, regression on the default branch, and a scheduled cross-system suite. Releases consume the latest valid evidence for the exact candidate, never an unrelated green run.

Service virtualization is appropriate when a dependency is unavailable or costly, but retain contract tests and a smaller set of real integration journeys. Mocks validate your assumptions about a dependency. They do not prove the dependency behaves that way.

Troubleshooting

Browser executable is missing -> Run npx playwright install --with-deps chromium, or use a matching official Playwright container. Keep the package and image versions aligned.

Tests pass locally but time out in CI -> Compare CPU, memory, network, base URL, and worker count. Reduce workers temporarily, inspect traces, and wait for observable readiness instead of increasing every timeout.

A matrix job reports no tests -> Confirm the project filter and shard syntax. An empty shard can occur when shard count exceeds eligible test files, so tune parallelism to the suite.

Artifacts are missing after failure -> Use the platform's unconditional execution rule, such as if: always() in GitHub Actions or when: always in GitLab. Verify the path relative to the job workspace.

Cache restoration creates strange dependency failures -> Cache the package manager download directory, key it from the lockfile, and rebuild dependencies with npm ci. Do not share mutable node_modules across incompatible runners.

OIDC authentication is denied -> Inspect issuer, audience, subject claims, environment conditions, and role trust policy. Keep the denial until the mismatch is understood rather than broadening trust to make the job pass.

Best Practices and Common Mistakes

  • Do pin runtimes, dependency locks, container images, and external actions through a maintained update process.
  • Do keep test data unique, synthetic, disposable, and scoped to the worker.
  • Do publish evidence after success and failure, with deliberate retention and redaction.
  • Do make quarantine temporary, owned, measurable, and continuously executed.
  • Do use least-privilege tokens and short-lived federation.
  • Do not run every suite on every trigger without considering feedback time and cost.
  • Do not use retries to convert unstable coverage into a false green signal.
  • Do not make jobs depend on execution order or shared mutable accounts.
  • Do not expose secrets to untrusted pull-request code.
  • Do not let a manual rerun erase the original failure and its evidence.

A useful review question is: if this job fails at 2 a.m., can the next engineer determine whether the product, test, environment, or runner failed without recreating the run? If not, improve evidence before increasing coverage.

The Complete Series

These focused tutorials extend the reference architecture. Implement evidence publishing and credential hardening early because both reduce risk across every suite you add later.

Where To Go Next

Start with one critical smoke journey and make it deterministic. Add unconditional evidence upload, then enforce the job as a required check. Once that path is trustworthy, split regression coverage by risk and introduce parallel execution based on measured duration.

For broader foundations, review Playwright CI pipeline best practices and API test automation strategy. Apply the same contract to each suite: explicit purpose, controlled environment, stable data, clear gate, owned failure, and preserved evidence.

Do not attempt a wholesale migration in one pull request. Improve one feedback loop, observe it, document it, and repeat. A modest pipeline that engineers trust is more valuable than a large suite they routinely rerun or bypass.

Interview Questions and Answers

Q: How would you design test automation in CI/CD?

I would layer checks by feedback speed and risk. Pull requests run static checks and critical smoke tests, merges run broader integration and regression coverage, and scheduled jobs cover expensive cross-system scenarios. Every required job uses reproducible dependencies, isolated data, explicit timeouts, evidence upload, and an owner.

Q: What is the difference between retry and quarantine?

A retry repeats a failed test to gather evidence about instability. Quarantine changes the gate policy for a known unstable test while continuing to execute and report it. Quarantine requires an owner, issue, reason, and expiry, while retries should never conceal the first failure.

Q: How do you reduce CI duration safely?

Measure queue, setup, and execution time first. Then cancel superseded runs, cache package downloads safely, select affected suites, shard isolated tests, and move noncritical coverage to appropriate triggers. I verify that coverage and failure diagnostics remain intact.

Q: Why should artifacts upload when tests fail?

Failure is when traces, screenshots, JUnit output, service logs, and run metadata are most valuable. Unconditional upload preserves evidence for classification and reproduction. Reports must still be redacted and retained only as long as policy requires.

Q: How do you secure credentials used by tests?

I prefer OIDC federation that grants short-lived, narrowly scoped cloud credentials based on repository and environment claims. I avoid secrets in fork workflows, use synthetic application accounts, minimize token permissions, and prevent reports from recording sensitive values.

Q: When would you use self-hosted runners?

I use them when tests require private network access, special hardware, or tightly controlled images. The organization must own isolation, patching, ephemeral cleanup, capacity, and secret boundaries. Hosted runners remain simpler when those constraints do not exist.

Q: How do you distinguish a product failure from an infrastructure failure?

I combine assertion output with traces, application health, deployment identity, and runner telemetry. Product failures reproduce against a healthy target and violate expected behavior, while infrastructure failures show unavailable dependencies, runner loss, or setup errors. The pipeline should preserve both categories rather than automatically rerunning everything.

Conclusion

A production-grade test automation CI/CD complete guide is ultimately a guide to trustworthy feedback. Build deterministic inputs, layer suites by risk, isolate data, parallelize only independent tests, protect environments with least privilege, and publish evidence regardless of outcome.

Begin with the smallest release-critical journey and make its signal dependable. Then expand coverage while measuring speed, reliability, security, and diagnostic quality. The result is not merely a green pipeline. It is an engineering control that helps the team release with evidence and respond to failures with confidence.

Interview Questions and Answers

How would you design test automation in CI/CD?

I would layer checks by feedback speed and risk. Pull requests run static checks and critical smoke tests, merges run broader integration and regression coverage, and scheduled jobs cover expensive cross-system scenarios. Every required job uses reproducible dependencies, isolated data, explicit timeouts, evidence upload, and an owner.

What is the difference between retry and quarantine?

A retry repeats a failed test to gather evidence about instability. Quarantine changes the gate policy for a known unstable test while continuing to execute and report it. Quarantine requires an owner, issue, reason, and expiry, while retries should never conceal the first failure.

How do you reduce CI duration safely?

Measure queue, setup, and execution time first. Then cancel superseded runs, cache package downloads safely, select affected suites, shard isolated tests, and move noncritical coverage to appropriate triggers. Verify that coverage and failure diagnostics remain intact.

Why should artifacts upload when tests fail?

Failure is when traces, screenshots, JUnit output, service logs, and run metadata are most valuable. Unconditional upload preserves evidence for classification and reproduction. Reports must still be redacted and retained only as long as policy requires.

How do you secure credentials used by tests?

Prefer OIDC federation that grants short-lived, narrowly scoped cloud credentials based on repository and environment claims. Avoid secrets in fork workflows, use synthetic application accounts, minimize token permissions, and prevent reports from recording sensitive values.

When would you use self-hosted runners?

Use them when tests require private network access, special hardware, or tightly controlled images. The organization must own isolation, patching, ephemeral cleanup, capacity, and secret boundaries. Hosted runners remain simpler when those constraints do not exist.

How do you distinguish product and infrastructure failures?

Combine assertion output with traces, application health, deployment identity, and runner telemetry. Product failures reproduce against a healthy target and violate expected behavior. Infrastructure failures show unavailable dependencies, runner loss, or setup errors, and both categories should remain visible.

Frequently Asked Questions

What tests should run in a CI/CD pipeline?

Run static checks and critical smoke tests on every pull request. Run broader integration and regression suites on merges, release candidates, or schedules according to risk and cost. Keep destructive and load tests isolated behind explicit triggers.

Should automated tests run before or after deployment?

Use both positions. Run component and contract checks before deployment, then run smoke and integration tests against the deployed candidate. Promote only the exact candidate whose required evidence passed.

How many retries should CI tests use?

Use zero or a small number chosen for diagnostic value, commonly one for browser tests. Always retain the initial failure and report retry recovery. More retries can increase delay and hide instability instead of improving confidence.

How can a team speed up regression testing in CI?

Measure setup and execution first, then cancel superseded runs, cache package downloads, select affected suites, and shard independent tests. Avoid excessive shards because repeated setup and shared-environment load can erase the benefit.

What artifacts should automated tests publish?

Publish machine-readable results such as JUnit plus human-readable reports, traces, screenshots, videos, relevant logs, and run metadata. Redact secrets and sensitive data, identify the tested deployment, and define a retention period.

How should flaky tests be handled in CI/CD?

Keep required tests blocking unless a reviewed quarantine policy applies. A quarantined test should continue running in a visible job with an owner, issue, reason, and expiry date. Fix root causes and track first-attempt reliability.

Is GitHub Actions or GitLab CI better for test automation?

Both support stages, parallel jobs, caches, reports, artifacts, protected environments, and cloud federation. Choose based on repository platform, governance, runner operations, and team expertise. The core pipeline architecture transfers between them.

Related Guides