CI/CD Testing

CI/CD testing is the part of quality assurance that runs automatically every time code changes: a pipeline builds the change, runs checks in a deliberate order, publishes the results and decides whether the change may move on towards production. For a tester, the pipeline is where most regression checking now happens, where test failures are first seen, and where a release decision is either backed by evidence or not.

This guide is the entry point of the topic. It explains continuous integration, delivery and deployment, the test stages of a pipeline and why their order matters, quality gates, reports and artifacts, environments and secrets, parallel execution, flaky-test policy and how to triage a red pipeline. Two narrower guides go deeper: one compares smoke, sanity and regression testing and shows how to select regression tests; the other covers verification after deployment — post-deploy smoke tests, canary releases and rollback.

Continuous integration, continuous delivery and continuous deployment

Continuous integration (CI) is the practice of merging every developer’s work into a shared mainline frequently — at least daily — with each merge verified by an automated, self-testing build. Martin Fowler’s widely referenced description adds the rules that make it work: keep the build fast, fix a broken build immediately, and treat a red mainline as everyone’s problem. CI is a team habit supported by a tool, not the tool itself; a CI server that runs once a week on a long-lived branch is not continuous integration.

Continuous delivery extends CI so that every change that passes the pipeline is releasable: it is built once, tested in production-like environments and could be deployed to production at the push of a button. The decision to deploy remains a human or business decision. Continuous deployment removes that decision: every change that passes all automated stages goes to production automatically. The difference matters to testers because under continuous deployment there is no manual checkpoint left — whatever the pipeline does not check, nobody checks before users see it.

  • Continuous integration — frequent merges to mainline, each verified by an automated build and tests.
  • Continuous delivery — every passing change is deployable; a person chooses when to release.
  • Continuous deployment — every passing change is deployed automatically; post-deployment verification becomes the last safety net.
  • In all three, the automated tests are the evidence. A pipeline is only as trustworthy as the tests it runs and the gates it enforces.

Test stages in a pipeline and why the order matters

A pipeline arranges checks so that the cheapest, fastest and most likely to fail run first, and the slow, expensive and environment-dependent ones run later, only on changes that survived the earlier stages. This is the test pyramid applied to time: a developer should learn about a typo in minutes, not after a forty-minute browser suite. Fast feedback is not a convenience; when feedback is slow, people batch changes, and a failure in a large batch is harder to attribute to one cause.

The exact stages differ by team, but most pipelines converge on a similar order. Each stage should have a time budget; when a stage grows past it, the team either parallelises it, moves slow tests to a later stage, or removes duplicated coverage.

  • Commit stage (minutes) — static checks (lint, type check, formatting, dependency and secret scanning), unit tests and component tests with dependencies replaced by test doubles. Runs on every push and pull request.
  • Integration and API stage — the application against real databases, queues and service containers; API tests checked against the contract (OpenAPI). This is where most business rules are verified at reasonable cost.
  • End-to-end / UI stage — a small set of critical user journeys through a real browser or device; the slowest and the most prone to flakiness, so kept deliberately small.
  • Non-functional stage — a short performance smoke (a few minutes of load against key endpoints with thresholds on latency and error rate), security scans (static analysis, dependency vulnerabilities, a baseline dynamic scan), accessibility checks on key pages.
  • Post-deployment stage — smoke tests and health checks against the environment just deployed, then synthetic monitoring. Covered in depth in the deployment verification guide.
  • Scheduled stages — the full regression suite, long load tests and full security scans run nightly or weekly instead of on every change, and report to an owner who acts on failures.
Stages for a change, in order: the cheapest and fastest checks run first, and each later stage sees only changes that passed the earlier ones.
  1. 1Commit stageMinutes. Static checks, unit and component tests; every push and pull request

    Gate: only changes that passed go on

  2. 2Integration and APIReal databases, queues and services; API tests against the contract

    Gate: only changes that passed go on

  3. 3End-to-end / UIA small set of critical journeys in a real browser or device

    Gate: only changes that passed go on

  4. 4Non-functionalPerformance smoke, security scans, accessibility checks

    Gate: Deploy — automatic under continuous deployment; a person decides under continuous delivery

  5. 5Post-deploymentSmoke tests and health checks on the new environment, then synthetic monitoring

Earlier: cheapest, fastest, most likely to failLater: slow, expensive, environment-dependent

Outside the sequence
  • Scheduled: nightly or weeklyFull regression, long load tests and full security scans, reported to an owner who acts on failures

Illustrative order: the exact stages differ by team.

Try it: OpenAPI → Test Cases Try it: Concurrent Request Test Generator Try it: Response Time Analyzer

Example: a pipeline with commit, API, E2E and deploy stages

The workflow below is an example written in GitHub Actions syntax. It runs static checks and unit tests first, then API tests against a throwaway PostgreSQL service container with migrations and synthetic seed data, then the browser suite split into four parallel shards, and on the main branch deploys to staging and runs a smoke script. Every test job uploads its reports and artifacts even when it fails (`if: always()`), because a failure without its evidence is the most expensive kind to investigate. GitLab CI and Jenkins express the same structure with stages and jobs in `.gitlab-ci.yml` or a Jenkinsfile.

  • `needs` makes the order explicit: the E2E shards never start if unit or API tests fail.
  • `concurrency` cancels a pull request’s running workflow when a newer push to the same pull request starts another, saving runner time. On main and the nightly schedule, which share the group of refs/heads/main, a running workflow is never cancelled, so a deployment is not interrupted halfway; but, as in every concurrency group by default, a run still pending in the group is cancelled when a newer one is queued.
  • `timeout-minutes` turns a hung test into a visible failure instead of a job that waits for hours.
  • The deploy token comes from the platform’s secret store; the database password is a literal only because the container lives for one job.
  • The schedule runs on the default branch; cron times in GitHub Actions are in UTC unless the schedule sets a time zone.
# Example pipeline (GitHub Actions syntax). Adapt job names, scripts and versions to your project.
name: ci

on:
  pull_request:
  push:
    branches: [main]
  schedule:
    - cron: "0 1 * * *"   # nightly full regression, 01:00 UTC

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  commit-stage:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm run test:unit          # writes reports/junit-unit.xml
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: unit-reports
          path: reports/

  api-tests:
    needs: commit-stage
    runs-on: ubuntu-latest
    timeout-minutes: 20
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: ci-only-password   # throwaway container, not a real secret
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    env:
      DATABASE_URL: postgres://postgres:ci-only-password@localhost:5432/postgres
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm run db:migrate
      - run: npm run db:seed            # synthetic test data only
      - run: npm run test:api           # writes reports/junit-api.xml
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: api-reports
          path: reports/

  e2e:
    needs: api-tests
    runs-on: ubuntu-latest
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --shard=${{ matrix.shard }}/4   # config starts the app
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: e2e-results-${{ matrix.shard }}
          path: |
            playwright-report/
            test-results/

  deploy-staging:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    needs: e2e
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v7
      - run: ./scripts/deploy.sh staging
        env:
          DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}
      - run: ./scripts/smoke.sh https://staging.example.com

Try it: YAML Formatter Try it: Cron Expression Generator Try it: Cron Expression Explainer

Quality gates: criteria that can stop a change

A quality gate is a check with a pass/fail rule that decides whether a change may proceed to the next stage — merge, deploy to staging, promote to production. It is the automated form of exit criteria. A gate is useful when it is objective, fast to evaluate, tied to a real risk and enforced without exceptions; a gate that is routinely overridden teaches everyone to ignore it.

Be careful with vanity gates. A rule such as “line coverage must be at least 80%” is easy to measure and easy to satisfy with tests that execute code without asserting anything useful. Coverage is informative as a trend and as a way to find untested code; as a raw threshold without context it rewards the wrong behaviour. A more useful variant is coverage of the changed lines, reviewed together with the tests themselves. The same applies to “number of tests” or “pass rate 100% after retries”: they describe activity, not risk.

  • All required checks green: build, lint, type check, unit, API and E2E tests — with no test skipped outside the quarantine list.
  • No new critical or high vulnerabilities in dependencies or static analysis, or a recorded, time-limited exception.
  • API contract unchanged, or the change is backward compatible (no removed fields, no new required inputs) unless versioned.
  • Performance smoke within thresholds, for example p95 latency under 400 ms and error rate under 1% at the smoke load, compared with the last main-branch baseline.
  • Database migrations applied cleanly to a copy of the current schema and, where the process requires it, a tested rollback or forward-fix plan.
  • For production promotion: post-deployment smoke on staging passed and no open blocker defects on the release.

Test reports and artifacts

A pipeline’s result is only as useful as what it leaves behind. JUnit XML is the common interchange format for test results: most test runners can write it and most CI systems can read it. It is a de facto format rather than a formal standard, so attributes vary slightly between tools, but the core structure — test suites containing test cases, each with an optional failure, error or skipped element and a duration — is widely supported. GitLab, for example, reads it through `artifacts:reports:junit` and shows new and fixed failures in the merge request; Jenkins publishes it as test results with history per test.

Beyond the pass/fail summary, keep the artifacts a person needs to diagnose a failure without re-running it: logs of the application and the test runner, screenshots and videos for UI failures, traces (a Playwright trace records actions, network and DOM snapshots and can be opened after the run), HTTP request/response captures for API failures, and coverage reports. Set a retention period so artifacts are kept long enough to compare with previous runs but do not accumulate forever, and make sure they contain no real personal data or secrets.

<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="3" failures="1" skipped="1" time="4.21">
  <testsuite name="orders-api" tests="3" failures="1" skipped="1" time="4.21">
    <testcase classname="orders.create" name="returns 201 for a valid order" time="0.84"/>
    <testcase classname="orders.create" name="rejects a negative quantity with 400" time="0.31">
      <failure message="expected status 400, received 500">AssertionError: expected 400, received 500
    at tests/orders.spec.ts:42</failure>
    </testcase>
    <testcase classname="orders.refund" name="refunds a captured payment" time="0">
      <skipped message="quarantined: FLAKY-112"/>
    </testcase>
  </testsuite>
</testsuites>

Environments, test data and secrets

Pipeline tests need somewhere to run. Ephemeral environments — created for one pipeline run or one pull request and destroyed afterwards — give each change a clean, isolated stack: service containers for databases and queues, or a short-lived preview deployment per branch. They remove a major cause of false failures: tests interfering with each other through shared data. Long-lived shared environments (a staging that mirrors production) are still needed for integration with real third parties and for release rehearsal, but they drift, so check their versions and configuration as part of the pipeline.

Test data should be created by the pipeline: migrations build the schema, seed scripts load a known synthetic baseline, and each test creates the specific records it needs, with unique identifiers so parallel runs do not collide. Never copy production personal data into CI. Secrets — API keys, deploy tokens, database passwords for shared environments — belong in the CI platform’s secret store or an external vault, scoped to the environments that need them, masked in logs and rotated. On GitHub Actions, workflows triggered by pull requests from forks do not receive repository secrets by default, which is a safety feature to keep, not a problem to work around.

  • One environment per run where possible; shared environments only where they add realism you cannot get otherwise.
  • Schema from migrations, baseline from seed scripts, per-test data with unique ids.
  • Mock or stub third parties at their boundary in early stages; test the real integration in a later, smaller stage.
  • Secrets never in the repository, never printed, never in artifacts.

Try it: Test Data Factory

Parallel execution and sharding

When a suite outgrows its time budget, run it in parallel. Within one machine, runners use several workers; across machines, the suite is split into shards, each running a subset on its own runner, as in the example above. Sharding cuts wall-clock time roughly in proportion to the number of shards if the shards are balanced; splitting by recorded test duration balances better than splitting by file count.

Parallelism exposes hidden dependencies between tests: two tests that edit the same user, a test that relies on another having run first, a fixed port or file path. These are test design defects, and they show up as intermittent failures. Isolate data per worker, avoid order dependencies and merge the per-shard reports into one result so the gate evaluates the whole suite.

Handling flaky tests in the pipeline

A flaky test passes and fails on the same code. In a pipeline it does double damage: it blocks good changes, and it teaches people to press “re-run” until green, after which real failures are ignored too. Martin Fowler’s article on non-determinism describes quarantine as a response: move the flaky test out of the gating suite into a separate, still-running set, so the pipeline stays trustworthy while the test is fixed. The causes and fixes are covered in the flaky tests guide; the pipeline needs a policy.

  • Retries: allow at most one automatic retry, report a test that passed only on retry as flaky rather than passed, and never retry a whole job silently.
  • Quarantine: a quarantined test still runs and reports, does not gate, has an owner and a ticket, and has a deadline to be fixed or deleted.
  • Tracking: record every flaky occurrence (test id, commit, error, link to the run) so the team can rank the worst offenders; flake rate per test is the number to watch.
  • Limits: cap the quarantine list size; a growing quarantine is a signal that test design needs attention, not a place to park problems.

Pipeline failure analysis: a triage flow

A red pipeline has four common explanations: a product defect introduced by the change, a test that is wrong or outdated, an environment or infrastructure problem, or a flaky test. Treating every red build as a product bug wastes developer time; treating every red build as flakiness lets defects through. A short, shared triage flow gets the classification right in minutes. The first question is always the same: what exactly failed, and what did the artifacts record?

Five questions in order sort a red pipeline: infrastructure and environment first, then flakiness and breaks already on main, and only then a product defect or an outdated test.
Question 1: Did the job fail before any test ran?Dependency install, build, image pull, runner lost
  • Answer: Yes

    Outcome: Pipeline or infrastructure problemOwner: the platform team. Fix it, then re-run.
  • Answer: No

    Question 2: Did it fail during setup?DB not ready, migration error, service unhealthy, expired secret
    • Answer: Yes

      Outcome: Environment problemFix the environment; do not touch the tests.
    • Answer: No

      Question 3: Re-run the failed tests once on the same commit. Do they pass now?
      • Answer: Yes

        Outcome: Flaky candidateLog it, open or update its ticket, quarantine it if it repeats. The green re-run does not prove the change safe.
      • Answer: No: a deterministic failure

        Question 4: Does the same test fail on main without this change?
        • Answer: Yes

          Outcome: Pre-existing break or environment driftNotify whoever owns main.
        • Answer: No: caused by this change

          Question 5: Is the product behaviour wrong, or is the test’s expectation out of date?
          • Answer: The product is wrong

            Outcome: Product defectBug report; the merge stays blocked.
          • Answer: The new behaviour is intended

            Outcome: Test update in the same changeReviewed like code.
  • Read the first error, not the last; later failures are often consequences.
  • Compare with the last green run of the same job: changed dependencies, runner image, data or configuration.
  • Keep a failure log with the classification of each red build; over a month it shows whether the pipeline suffers mainly from defects, flakiness or environments.
  • Product defects found by the pipeline get a normal bug report with the failing test, the commit and the artifacts attached.
Text version
Pipeline failed
|
|-- 1. Did the job fail before any test ran? (dependency install, build, image pull, runner lost)
|      yes -> PIPELINE / INFRASTRUCTURE problem. Owner: platform. Fix, then re-run.
|
|-- 2. Did it fail during setup? (DB not ready, migration error, service unhealthy, expired secret)
|      yes -> ENVIRONMENT problem. Fix the environment; do not touch the tests.
|
|-- 3. Re-run the failed tests once on the SAME commit. Do they pass now?
|      yes -> FLAKY candidate. Log it (test id, commit, error, run link), open or update its ticket,
|             quarantine if it repeats. The change is not proven safe by the green re-run.
|      no  -> deterministic failure, continue.
|
|-- 4. Does the same test fail on main without this change?
|      yes -> PRE-EXISTING break or environment drift. Notify whoever owns main.
|      no  -> caused by this change, continue.
|
`-- 5. Is the product behaviour wrong, or is the test's expectation out of date?
       product wrong       -> PRODUCT DEFECT. Bug report, merge blocked.
       intended new change -> TEST UPDATE in the same change, reviewed like code.

Try it: AI Error Message Analyzer

What a tester does in CI/CD

In teams with a pipeline, testers spend less time executing scripted checks by hand and more time deciding what the pipeline checks, keeping it trustworthy and testing what it cannot. The work below is a practical summary; which tests to automate at which level is covered in the manual versus automated testing guide, and the metrics that show whether delivery is healthy — change fail rate and failed deployment recovery time, among the DORA metrics — are worth reviewing with the team every release.

  • Map risks to stages: which check, at which stage, protects against which failure.
  • Design and review the regression suite, including its selection rules for pull requests versus nightly runs.
  • Define quality gates with the team and challenge vanity gates.
  • Own the flaky-test policy and the failure log; triage red builds with developers.
  • Write post-deployment smoke checks and agree rollback criteria before release.
  • Keep exploratory testing for new features — no pipeline replaces a person looking for what nobody thought to automate.

Try it: Regression Scenario Generator Try it: API Health Checker

Frequently asked questions

What is the difference between continuous delivery and continuous deployment?

In continuous delivery every change that passes the pipeline is ready to deploy, but a person decides when to release it. In continuous deployment every passing change is deployed to production automatically. Continuous deployment therefore needs stronger automated checks and post-deployment verification, because there is no manual checkpoint.

Which tests should run on every commit?

Fast, deterministic checks: static analysis, unit tests, component tests and usually a focused subset of API tests. Together they should finish within a few minutes. Longer suites — full end-to-end, long load tests, full security scans — belong in later stages or scheduled runs.

Is a code coverage threshold a good quality gate?

On its own, rarely. A raw threshold is easy to meet with tests that assert little, and it says nothing about risk. Coverage is useful as a trend and to find untested code; if you gate on it, prefer coverage of changed lines, reviewed together with the tests.

Should the pipeline retry failed tests automatically?

At most once, and a test that passes only on retry should be reported as flaky, not as passed. Unlimited or silent retries hide both flakiness and real intermittent defects such as race conditions.

What is JUnit XML and why does it matter?

It is an XML format for test results — suites, test cases, failures, skips and durations — that most test runners can write and most CI systems can read. Using it lets the pipeline show failures in merge requests, track history per test and merge results from parallel shards.

How do I know whether a failure is a bug or an environment problem?

Check where it failed and whether it repeats. Failures before tests start, in setup or across unrelated tests usually point to the environment; a failure that repeats on the same commit and not on main, with a clear assertion, usually points to the change. The triage flow in this guide walks through the questions in order.

Do testers still do manual testing in a CI/CD team?

Yes, but differently. Scripted regression moves into the pipeline; testers spend their time on exploratory testing of new features, risk analysis, test design, gate definition and failure triage.

References

  • Continuous Integration (Martin Fowler)martinfowler.com · 2024The widely referenced description of continuous integration: integrate often, keep the build self-testing and fast, and fix a broken build immediately.
  • ISTQB Certified Tester Foundation Level (CTFL) v4.0ISTQB · 2023The foundation syllabus of software testing: test levels and types, test design techniques, test management and tools.
  • GitHub Actions documentationGitHubHow workflows, jobs, runners and artifacts are defined in GitHub Actions — where automated tests run on each push or pull request.
  • GitLab CI/CDGitLabHow pipelines, jobs and artifacts are defined — where an automated regression suite runs on every change.
  • Unit test reports — GitLab CI/CDGitLabPublishing JUnit XML results as pipeline artifacts so failed tests appear in the merge request and can be compared between runs.
  • Pipeline — Jenkins User HandbookJenkins ProjectJenkins pipelines defined as code in a Jenkinsfile: stages, steps, parallel execution and test result publishing.
  • Eradicating Non-Determinism in Tests (Martin Fowler)martinfowler.com · 2011Why flaky tests destroy a suite's value, their common causes (isolation, asynchrony, remote services, time, resource leaks) and quarantine as a response.
  • DORA's software delivery performance metricsDORA (Google Cloud)The DORA delivery metrics — change lead time, deployment frequency, failed deployment recovery time, change fail rate and related measures — grouped by throughput and instability.

Practice exercise

Review a draft pipeline and triage its red runs

Your task

You are the only tester on a team of six developers building a cinema ticketing platform (fictional): a web app and a REST API in one repository, built with Node.js and PostgreSQL, with CI on GitHub Actions. This sprint the pipeline is to become the merge gate for every pull request. A developer wrote the draft below — it already runs but does not block merges yet — and asked you to review it before it does. The material also lists the five red runs of last week, which nobody has classified yet.

Review the draft against the guide: the order of the stages and their time budgets, the flaky-test policy, test data and secrets, reports and timeouts, the proposed coverage gate and the nightly schedule. Write the merge gate you would propose, then triage the five red runs with the five-question flow from the guide. Work on paper; a time zone converter helps with one line of the draft. Allow 30–45 minutes.

Material
Cinema ticketing platform (fictional) - web app and REST API, one repository
Team: 6 developers, 1 tester (you). Build: Node.js, PostgreSQL. CI: GitHub Actions.
Goal for this sprint: the pipeline becomes the merge gate for every pull request.

Draft pipeline (written by a developer). It already runs but does not block merges yet.
Jobs in the order they run today; the retries line in J1 is new in this draft.
  J1  e2e            Playwright, 310 browser tests, about 38 min, on every push.
                     New in this draft: retries: 3; the job is green when every test
                     passes on some attempt.
  J2  unit           2,400 unit tests, about 3 min.
  J3  lint-types     eslint and tsc, about 1 min.
  J4  api            140 API tests, about 9 min, against the shared database
                     "qa-shared", restored every night from a production dump.
  J5  coverage-gate  fails when line coverage is below 80%.
  J6  deploy-staging on main only. DEPLOY_TOKEN is read from ci/deploy.env, a file
                     committed in the repository. No check runs after the deploy.
  J7  nightly        full regression and a 20-minute load test; cron "0 2 * * *",
                     meant to run at 02:00 Riyadh time. Results go to a chat channel
                     that nobody is assigned to read.
  Reports            the Playwright HTML report and the traces are uploaded only when
                     the job succeeds. No job has a timeout.

Last week’s red runs (R1 to R5)
  R1  PR 741, job unit: "npm ci" fails after 40 s with ENOTFOUND registry.npmjs.org.
      No test ran.
  R2  PR 744, job api: 1 failure, seats.hold
      "returns 409 when the seat is already held": expected 409, received 200.
      Re-run on the same commit: fails again. Same test on main: passes.
      The PR changes the seat-hold service.
  R3  PR 745, job e2e: 1 failure, checkout "shows the order summary": timeout
      waiting for the summary panel. Re-run on the same commit: passes.
      Fourth time this month for this test, on unrelated PRs.
  R4  main, job api: the setup step "wait for database" times out after 60 s:
      connect ETIMEDOUT qa-shared:5432. No API test ran.
  R5  PR 749, job api: 1 failure, pricing "applies the 10% student discount":
      expected 45.00, received 42.50 (ticket price 50.00 SAR). Re-run: fails again.
      Main: passes. PR 749 implements the approved change TK-311
      "student discount becomes 15%".
Steps
  1. Put the jobs in the order the guide recommends, give each stage a time budget, and say which jobs gate a pull request and which belong to the nightly run.
  2. Go through the draft line by line and list every practice that contradicts the guide, with the fix for each (retries, test data, the coverage gate, the secret, the schedule, reports, timeouts).
  3. Write the merge gate for a pull request as a short list of objective pass/fail rules.
  4. Classify R1 to R5 with the five-question triage flow: the question that decides it, the classification, the owner and the next action.
  5. Fill in the failure log for the week (how many of each class) and name one thing this pipeline still cannot check that you will test yourself.
Show the model answer
Model answer

Order and budgets. The draft runs the slowest, most environment-dependent job first and the cheapest checks last, so a type error that tsc finds in a minute is reported only after 38 minutes of browser tests. Reverse it. Commit stage, on every push and pull request, budget about 5 minutes: J3 lint-types (1 min) then J2 unit (3 min), plus dependency and secret scanning, which the draft lacks. API stage, needs the commit stage, budget about 10 minutes: J4 against a service container (see below). E2E stage, needs the API stage: not 310 tests on every push. Keep a small set of critical journeys as the gating suite — find a showing, hold a seat, pay, receive the ticket — split into shards balanced by recorded duration, with fail-fast: false and the shard reports merged into one result, so it finishes within about 10 minutes; the other browser tests move to the nightly run. J6 deploy-staging runs on main only, after E2E, and is followed by a smoke script and health checks against staging, which the draft does not have. A pull request now gets its first answer in minutes and its full answer in about 25 minutes, instead of waiting 38 minutes before the first unit test even starts.

The new retries: 3 in J1, with a green job when any attempt passes, is the opposite of the guide’s policy: at most one automatic retry, a test that passed only on retry reported as flaky rather than passed, and never a silent retry of a whole job. With it, the checkout test in R3 would most likely have passed on a retry on all four occasions and never been noticed, and so would a real race condition in seat holds — exactly the kind of intermittent defect a ticketing system cannot afford. Set one retry, make the report show pass-on-retry as flaky, and record every occurrence with test id, commit, error and run link.

J4 breaks two rules at once. The shared database "qa-shared" is restored from a production dump: production personal data (customer names, phone numbers, payment references) never belongs in CI, and a shared database makes parallel runs and pull requests interfere with each other through shared rows, which the guide names as a major cause of false failures. Replace it with a PostgreSQL service container per run: schema from migrations, a synthetic seed baseline, and per-test records with unique ids. Report the dump to whoever owns data protection in your company, because copies may exist on runners and in artifacts. J6 keeps DEPLOY_TOKEN in ci/deploy.env inside the repository: secrets never live in the repository, are never printed and never land in artifacts. Move it to the platform’s secret store scoped to the staging environment, and treat the committed token as exposed: revoke it and issue a new one, since the repository history still contains the old value.

J5, line coverage below 80% fails the build, is a vanity gate: easy to satisfy with tests that execute code and assert nothing, and silent about risk. Keep coverage as a trend and a way to find untested code; if the team wants a gate, use coverage of the changed lines, reviewed together with the tests. Reports uploaded only on success invert the guide: upload them with if: always(), because a failure without its evidence is the most expensive to investigate — traces, screenshots, runner logs and HTTP captures — with a retention period and no personal data in them. Every job needs timeout-minutes, so a hung test becomes a visible failure instead of a job that waits for hours. J7 has two problems: cron schedules in GitHub Actions run in UTC unless the schedule sets a time zone, and the draft sets none, so 0 2 * * * fires at 02:00 UTC, which is 05:00 in Riyadh (UTC+3, no daylight saving); for 02:00 Riyadh write 0 23 * * *, 23:00 UTC the previous day. And a scheduled stage must report to an owner who acts on failures: name one, rotate the duty, and turn each nightly failure into a ticket or a triage entry rather than a message nobody reads.

Merge gate for a pull request, as objective pass/fail rules on the latest commit: (1) lint, type check, unit, API and the critical E2E journeys are green, with at most one automatic retry (a pass on retry is reported and logged as flaky) and no test skipped outside the quarantine list; (2) no new critical or high vulnerability in dependencies or static analysis, or a recorded, time-limited exception; (3) the API contract is unchanged or backward compatible — no removed fields, no new required inputs — unless versioned; (4) the pull request’s database migrations apply cleanly to the fresh database of the API stage. Coverage of changed lines is shown to the reviewer but is not a threshold. The nightly suite is not part of the gate: it is the safety net and must be green for a release candidate. Exceptions are recorded with a reason and an expiry; a gate that is overridden routinely teaches everyone to ignore it.

R1 — question 1 decides it: the job failed before any test ran, in npm ci; ENOTFOUND means the runner could not resolve the registry’s host name. Pipeline or infrastructure problem. Owner: whoever maintains the runners and the pipeline (the platform role; in this team probably the developer who wrote the draft). Re-run once name resolution works again; if it repeats, that owner investigates the runner’s network and DNS. Nothing to change in tests or product. R4 — question 1 covers the pipeline’s own steps (install, build, image, runner), and those succeeded; question 2 decides it: the job failed in setup, waiting for the shared database qa-shared, and no test ran. Environment problem: do not touch the tests. Owner: whoever runs qa-shared; find out whether it was down, restarting or still being restored, compare with the last green run of the same job, and re-run when it answers. Because this is main, tell the team that main’s status is unknown until a green run. R4 is one more argument for the per-run service container proposed for J4.

R2 — questions 1 and 2: no, tests ran. Question 3: the re-run on the same commit fails again, so the failure is deterministic. Question 4: the test passes on main, so this change caused it. Question 5: the product behaviour is wrong. A seat that is already held must be refused with 409; a 200 means two customers can hold the same seat, and the pull request changes exactly the seat-hold service. Product defect; owner: the author of PR 744. Write a bug report with the failing test, the commit, the run link and the request and response that returned 200, severity proposed as Critical because it leads to double booking; merge blocked until it is fixed and the test passes. The test is not out of date: no requirement changed.

R3 — question 3: the re-run on the same commit passes, so this is a flaky candidate, and the history settles it: fourth failure this month on unrelated pull requests. Log it (test id, commit, error, link to the run), update its ticket and quarantine it: it keeps running and reporting, stops gating, gets an owner (whoever owns the checkout tests) and a deadline to be fixed or deleted. The green re-run does not prove PR 745 safe, and the failed run left no trace to inspect, because reports and traces are uploaded only on success — one more reason for if: always(). With that fixed, open the trace of the next failure to see whether the summary panel appeared late or the test waited for the wrong element, and whether checkout itself could be slow for users.

R5 — question 3: deterministic; question 4: passes on main, so caused by the change; question 5: the new behaviour is the intended, approved change TK-311, so the test’s expectation is out of date, not the product. Check the arithmetic: 15% off 50.00 SAR is 42.50, which is what the product returned. Owner: the author of PR 749. Update the test in the same pull request — title "applies the 15% student discount", expected 42.50 — and have it reviewed like code; the pull request is not mergeable until that commit is in it. Failure log for the week: 1 infrastructure (R1), 1 environment (R4), 1 flaky (R3), 1 product defect (R2), 1 test update (R5) — over a month this shows whether the pipeline suffers mainly from defects, flakiness or environments. What the pipeline still cannot check: the new discount in the user interface and the seat map as a customer sees them — exploratory testing of new features stays with you.

Your progress on this topic

Part of the QA Roadmap

Tools mentioned in this guide

Check several API health endpoints at once and get status, latency and a pass/fail summary.

API Testing

Generate a structured test-case matrix (positive, negative, auth, boundary) from an OpenAPI / Swagger specification.

Automation

Generate concurrency test scripts (k6, JMeter JMX, Locust, Artillery, Node.js) with safe defaults for a target endpoint.

Automation

Measure an endpoint's response time over several samples: min, max, average, median and p95.

Performance

Format and validate YAML with consistent indentation and quoting.

Formatting

Explain stack traces and error messages, identify likely root causes and next debugging steps with Mutqan AI.

Bug Analysis AI

One-stop factory for synthetic test data: users, customers, companies, addresses, numbers, dates and strings in JSON, CSV, SQL or XML.

Test Data

How Mutqan Soft can help

More guides

More guides