DEV Community

Priyanka Vuddemari
Priyanka Vuddemari

Posted on

Cloud Resume Challenge Week 4— Testing the Serverless Backend with Playwright and Python

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
Enter fullscreen mode Exit fullscreen mode

I also updated the dev container so Playwright is installed automatically on every Codespace rebuild:

"postCreateCommand": "pip install pytest-playwright && playwright install chromium"
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

What Each Test Covers

API Smoke Tests:

  • API returns HTTP 200 — basic health check
  • Response contains a views key — 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ✅
Enter fullscreen mode Exit fullscreen mode

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)