Why this matters on the job
In most Indian service and product companies today, an automation suite that only runs on the tester's laptop is treated as unfinished work. Interviewers for SDET and Automation QA roles routinely ask: “How are your tests triggered? Where do you see the report? What happens when a test fails at 2 AM?” If your answer is “I run them manually before release”, you lose the offer to someone who says “Every pull request runs API and UI smoke in GitHub Actions, the nightly job runs the full regression in Docker, and a failure posts to Slack with the HTML report link.”
This course turns you into that second candidate. Lesson 1 gives you the mental model and a small project that every later lesson builds on.
Concepts
DevOps, CI and CD in one line each
- DevOps is a way of working where development, QA and operations share ownership of delivering software quickly and safely. Automation and fast feedback are its core tools.
- Continuous Integration (CI): every change is merged frequently into a shared branch, and each merge triggers an automated build plus tests. The goal is to catch breakage within minutes of the commit.
- Continuous Delivery (CD): every change that passes the pipeline is deployable at the push of a button. Continuous Deployment goes one step further and deploys to production automatically.
Where tests run in a pipeline
| Pipeline stage | Typical tests | Target time | Who owns it |
|---|---|---|---|
| Commit / PR build | Lint, unit tests, static analysis | < 10 min | Developers (QA reviews) |
| Build & package | Docker image / artifact build, contract checks | < 10 min | DevOps |
| Deploy to QA / test env | API smoke, UI smoke (@smoke) | < 15 min | QA |
| Nightly / pre-release | Full API + UI regression, cross-browser | 30-90 min | QA |
| Pre-prod / staging | Performance (k6/JMeter), security scan (ZAP), UAT | hours | QA + Perf + Sec |
| Production | Synthetic monitoring, post-deploy smoke | minutes | SRE + QA |
The test pyramid decides pipeline speed
Many unit tests at the bottom, fewer API/integration tests in the middle, and a small number of UI end-to-end tests at the top. If your pipeline is slow, it is usually because the pyramid is upside down (hundreds of UI tests, few API tests). As a tester you push checks down the pyramid: validate business rules through the API, and keep UI tests for critical user journeys such as login, checkout and payment.
Quality gates
A quality gate is an automatic pass/fail rule that decides whether a build may move to the next stage. Examples: all smoke tests pass; zero blocker issues from static analysis; code coverage on new code is at least 80%; no critical vulnerabilities in dependencies. Technically, a gate is just a step whose exit code is non-zero when the rule is broken — CI tools stop the pipeline on any non-zero exit code. Remember: exit 0 means pass, anything else means fail.
Shift-left and shift-right: shift-left means testing earlier (PR checks, API tests on every commit); shift-right means testing in production-like conditions (monitoring, canary checks, post-deploy smoke). A mature QA engineer plans both.
Hands-on Lab: Create the course project and run a local 4-stage pipeline
You will build qa-ci-demo, a small Playwright + TypeScript project with UI tests on SauceDemo and API tests on Restful Booker. Every later lesson uses it. Prerequisites: Node.js 20 or 22 LTS and Git installed (check with node -v and git --version).
- Create the project and install Playwright:
mkdir qa-ci-demo && cd qa-ci-demo npm init -y npm install -D @playwright/test@1.52.0 npx playwright install --with-deps chromium npm pkg set scripts.test="playwright test" mkdir -p tests/ui tests/api - Create
playwright.config.tsin the project root. Note the separateapiproject and the CI-aware settings:// playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ fullyParallel: true, forbidOnly: !!process.env.CI, // a stray test.only fails the CI run retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 2 : undefined, reporter: [ ['list'], ['html', { open: 'never' }], ['junit', { outputFile: 'results/junit.xml' }], ], use: { baseURL: process.env.BASE_URL ?? 'https://www.saucedemo.com', trace: 'on-first-retry', screenshot: 'only-on-failure', }, projects: [ { name: 'api', testDir: './tests/api' }, { name: 'chromium', testDir: './tests/ui', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', testDir: './tests/ui', use: { ...devices['Desktop Firefox'] } }, { name: 'webkit', testDir: './tests/ui', use: { ...devices['Desktop Safari'] } }, ], }); - Create the UI test. The
@smoketag marks tests that must run on every build:// tests/ui/login.spec.ts import { test, expect } from '@playwright/test'; test.describe('Login', () => { test('standard user lands on inventory', { tag: '@smoke' }, async ({ page }) => { await page.goto('/'); await page.locator('[data-test="username"]').fill('standard_user'); await page.locator('[data-test="password"]').fill('secret_sauce'); await page.locator('[data-test="login-button"]').click(); await expect(page).toHaveURL(/inventory\.html/); await expect(page.locator('[data-test="title"]')).toHaveText('Products'); }); test('locked out user sees an error', async ({ page }) => { await page.goto('/'); await page.locator('[data-test="username"]').fill('locked_out_user'); await page.locator('[data-test="password"]').fill('secret_sauce'); await page.locator('[data-test="login-button"]').click(); await expect(page.locator('[data-test="error"]')).toContainText('locked out'); }); }); - Create the API test:
// tests/api/booking.spec.ts import { test, expect } from '@playwright/test'; const API = 'https://restful-booker.herokuapp.com'; test('health check responds 201', { tag: '@smoke' }, async ({ request }) => { const res = await request.get(`${API}/ping`); expect(res.status()).toBe(201); }); test('GET /booking returns a list of ids', async ({ request }) => { const res = await request.get(`${API}/booking`); expect(res.ok()).toBeTruthy(); const body = await res.json(); expect(Array.isArray(body)).toBe(true); expect(body[0]).toHaveProperty('bookingid'); }); - Run both projects once:
npx playwright test --project=api --project=chromium. Expected: 4 passed. Open the report withnpx playwright show-report. - Create
ci.shwith the stages below, then runchmod +x ci.sh && ./ci.sh:#!/usr/bin/env bash # ci.sh - a "pipeline" you can run on your laptop set -euo pipefail echo "== Stage 1: install (clean, from lockfile) ==" npm ci echo "== Stage 2: static check (does every spec compile and load?) ==" npx playwright test --list > /dev/null echo "== Stage 3: API smoke (fast, no browser) ==" npx playwright test --project=api --grep @smoke echo "== Stage 4: UI smoke on chromium ==" npx playwright test --project=chromium --grep @smoke echo "== QUALITY GATE PASSED: build can be promoted ==" - Expected output ends with
QUALITY GATE PASSED. Now prove the gate works: change'Products'to'Product'inlogin.spec.tsand run./ci.sh; echo "exit code: $?". Stage 4 fails, the script stops, and the exit code is1. Revert the change. - Run
ls results— you should seejunit.xml. CI servers (Jenkins, GitHub Actions, Azure DevOps, GitLab) all read this JUnit XML format to show pass/fail trends.
You have just built a pipeline in miniature: ordered stages, fail-fast behaviour, machine-readable results and a gate. GitHub Actions and Jenkins do exactly this on a server, triggered automatically.
Common mistakes
- Running the full UI regression on every commit. The pipeline takes an hour, developers stop waiting for it, and quality gates get bypassed. Run smoke on PRs and the full regression nightly.
- Using
npm installin CI instead ofnpm ci.npm ciinstalls exactly what is inpackage-lock.jsonand fails if it is out of sync — that is what you want for reproducible builds. - Swallowing failures with
|| trueor an empty try/catch so the build stays green. A gate that never fails is not a gate. - Leaving
test.onlyin committed code.forbidOnly: !!process.env.CIprotects you. - Treating CI as “the DevOps team's job”. Testers own the test stages, the reports and the triage of red builds.
Real-world assignment
Your lead says: “We deploy the web app to QA every afternoon. Propose which of our tests should run where.” Take any application you know (or SauceDemo) and write a one-page pipeline proposal: list each stage, which tests run (unit, API smoke, UI smoke, full regression, performance), the trigger (PR, merge to main, nightly, manual), the time budget, and the quality gate rule for each stage. Add a test pyramid with realistic counts (for example 400 unit, 120 API, 35 UI). Commit it as docs/pipeline-plan.md in qa-ci-demo after Lesson 2.
Key takeaways
- CI = every change is built and tested automatically; CD = every green build is deployable.
- Fast, cheap tests run early and often; slow UI and non-functional tests run later or nightly.
- A quality gate is any automated rule whose non-zero exit code stops promotion.
- Tag tests (
@smoke) so the same suite can run as smoke or as full regression. - JUnit XML plus HTML reports are the language CI tools understand — always produce both.