Chunk 4 is complete — with my background in DevOps and Test automation, this chunk felt most natural.
The goal is straightforward: write automated tests to verify that the serverless backend works correctly. In this post I'll walk through the tests I wrote, why I chose Playwright over Cypress, and how I integrated everything into the CI/CD pipeline.
Why Playwright over Cypress?
The challenge gives you two options — Cypress (JavaScript) or Playwright (Python). I chose Playwright for one simple reason: Python.
The Lambda function is already written in Python, and writing tests in the same language keeps the project consistent. Playwright's Python API is also clean, well-documented, and fits naturally with pytest which is already a standard in the Python testing world.
What to Test?
The book recommends two types of tests:
Unit tests — test Lambda function logic in isolation, mocking DynamoDB calls. Simple but limited since you are mostly testing mocked behavior rather than real infrastructure.
Smoke tests / End-to-end tests which run after deployment against the real live API URL. These verify the full stack is working — Lambda, API Gateway, DynamoDB, and permissions ,all in one test run.
I went with smoke tests and end-to-end browser tests. They are far more valuable and generate much better interview conversation.
Setting Up Playwright
Install Playwright and its pytest plugin:
pip install pytest-playwright requests
playwright install chromium
I also updated the dev container so Playwright is installed automatically on every Codespace rebuild:
"postCreateCommand": "pip install pytest-playwright && playwright install chromium"
The Tests
I created a tests/ folder with a single test file test_resume.py.
import pytest
import requests
from playwright.sync_api import sync_playwright
API_URL = "https://YOUR_API_URL"
RESUME_URL = "https://YOUR-CLOUDFRONT-URL"
# ── API Smoke Tests ──
def test_api_returns_200():
"""API should return a 200 status code"""
response = requests.get(API_URL)
assert response.status_code == 200
def test_api_returns_views_key():
"""API response should contain a views key"""
response = requests.get(API_URL)
data = response.json()
assert "views" in data
def test_api_views_is_integer():
"""Views count should be an integer"""
response = requests.get(API_URL)
data = response.json()
assert isinstance(data["views"], int)
def test_api_views_increments():
"""Views count should increment on each call"""
first = requests.get(API_URL).json()["views"]
second = requests.get(API_URL).json()["views"]
assert second == first + 1
def test_api_views_greater_than_zero():
"""Views count should be greater than zero"""
response = requests.get(API_URL)
data = response.json()
assert data["views"] > 0
# ── End to End Browser Tests ──
def test_resume_page_loads():
"""Resume page should load successfully"""
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
response = page.goto(RESUME_URL)
assert response.status == 200
browser.close()
def test_resume_has_title():
"""Resume page should have a title"""
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto(RESUME_URL)
assert page.title() != ""
browser.close()
def test_visitor_counter_displays():
"""Visitor counter should be visible on the page"""
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto(RESUME_URL)
page.wait_for_selector("#views-count")
count = page.inner_text("#views-count")
assert count.isdigit()
browser.close()
What Each Test Covers
API Smoke Tests:
- API returns HTTP 200 — basic health check
- Response contains a
viewskey — correct response structure - Views count is an integer — correct data type
- Views count increments on each call — counter logic works
- Views count is greater than zero — database has been initialised
End to End Browser Tests:
- Resume page loads successfully — CloudFront + S3 serving correctly
- Resume page has a title — HTML is rendering properly
- Visitor counter displays — JavaScript fetch and DOM update working
Running the Tests
pytest tests/ -v
All 8 tests passed on the first run locally.
Adding Tests to the CI/CD Pipeline
The real value of automated tests is running them automatically. I added two steps to the GitHub Actions workflow:
- name: Install Python test dependencies
run: |
pip install pytest-playwright requests
playwright install chromium
- name: Run Playwright tests
run: pytest tests/ -v
Now every push to main triggers the full pipeline:
Push to GitHub
↓
Sync files to S3
↓
Invalidate CloudFront cache
↓
Terraform apply
↓
Run Playwright tests ← new!
↓
All green ✅
Key Takeaways
- Smoke tests beat unit tests for serverless — testing against the real live API verifies Lambda, API Gateway, DynamoDB, and IAM permissions all at once
- Playwright + pytest is a natural fit — clean API, familiar tooling, easy to extend
- Tests in CI/CD is non-negotiable — running tests locally is good, running them automatically on every push is better
- The visitor counter test is the most valuable — it validates the entire chain from browser to database in a single assertion
What's Next
Chunk 4 wraps up the core challenge. Next up is the final post — a complete retrospective of the entire Cloud Resume Challenge journey.
*My Cloud Resume Challenge series here *
Top comments (0)