Visuele QA als CI Pipeline Stadium
Ik mergede een PR vorige maand. De code review zag er goed uit. Tests slaagden. Toen opende ik de site op mijn telefoon en de sidebar was compleet kapot.
De oplossing was triviaal—een ontbrekende media query. De bug was overduidelijk zodra je daadwerkelijk naar de mobiele weergave keek. Niemand deed dat.
Dus voegde ik een pipeline stadium toe dat kijkt.
Ik open een GitHub issue dat zegt:
Implementeer de Handmatige Invoer optie voor het toevoegen van klanten. Bij klikken opent een brede slide-over drawer met een formulier om een nieuwe klant aan te maken.
Eén commit later landt de PR met 30+ screenshots die bewijzen dat elke status werkt bij elke viewport. Nul handmatig testen. De enige inspanning was het schrijven van de feature beschrijving.
Het Concept
Bij elke push naar een PR voert een GitHub Actions workflow uit:
- Start een Docker Compose stack (Django + Postgres) vanuit de PR branch
- Voert migraties uit en zaait een testgebruiker
- Geeft de lokale URL door aan Claude Code met Playwright MCP (headless Chromium)
- Oefent interactieve elementen op basis van de PR diff
- Maakt screenshots van elke status bij drie viewports
- Post de resultaten als een PR commentaar
Claude Code bestuurt de browser. Docker Compose levert de app. GitHub Actions bindt het samen. Reviewers zien bewijs van functionaliteit zonder lokaal iets te draaien.
%%{init: {"flowchart": {"subGraphTitleMargin": {"top": 3, "bottom": 10}}} }%%
graph TD
A["PR Push"] --> B
subgraph GHA["GitHub Actions Runner"]
B["Checkout + Docker Compose"] --> C["Migraties + Seed Data"]
C --> Agent
subgraph Agent["Claude Code + Playwright"]
direction LR
D["Lees PR Diff"] --> E["Map Bestanden → URLs"]
E --> F["Login + Navigeer"]
F --> G["Screenshot ×3 Viewports"]
end
end
Agent --> H["PR Commentaar"]
style GHA fill:transparent,stroke:#888
style Agent fill:transparent,stroke:#888
De workflow zelf is eenvoudig:
name: UI Screenshots
on:
pull_request:
types: [opened, synchronize]
jobs:
screenshots:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
# ... Docker Compose setup, migraties, testgebruiker aanmaken ...
- name: Start services
run: |
docker compose -f docker-compose.local.yml up -d --wait
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Run Claude for Screenshots
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: >-
--allowed-tools
"mcp__playwright__*,
Bash(gh pr diff:*),
Bash(gh pr comment:*),
Read,Glob,Grep"
prompt: |
You are a visual QA agent. Your job is to visually
verify a UI deployment.
PR: ${{ github.event.pull_request.number }}
The Django app is running at http://localhost:8001
1. Run `gh pr diff` to find changed templates/views
2. Map changed files to URLs — only test pages
affected by this push
3. Login, then screenshot each page at three viewports:
1440x900, 1024x768, 640x1136
4. Save screenshots with descriptive filenames
5. Post a PR comment with:
- Each test performed with pass/fail
- Any visual issues found
- Total screenshots captured
De Viewport Matrix
| Apparaat | Grootte | Waarom |
|---|---|---|
| Desktop | 1440×900 | Standaard laptop |
| Tablet | 1024×768 | iPad portret |
| Mobiel | 640×1136 | iPhone SE |
Elke interactieve status wordt vastgelegd op alle drie. Een sidebar component genereert 12+ screenshots:
- Pagina laden (gesloten) — 3 viewports
- Trigger klik — 3 viewports
- Volledig geopend — 3 viewports
- Sluit animatie — 3 viewports
Vermenigvuldig met licht/donker thema’s en je kijkt naar 24 screenshots voor één component. Dat is het punt. Uitputtend visueel bewijs als een CI artefact.
Wat Het Daadwerkelijk Oplevert
Hier is echte output van een PR die een handmatige invoer pagina toevoegde. De agent post een PR commentaar met een gestructureerde samenvatting, screenshots bij elke viewport, en een test checklist:

Onder die samenvatting voegt de agent screenshots toe van elke status die het heeft uitgeoefend. Hier is de desktop weergave—klantenlijst met data, plus de Klant Toevoegen drawer open:

De agent testte:
- Lege formulier status
- Formulier met validatie fouten (ontbrekend verplicht naamveld)
- Succesvol formulier verzenden
- Klantenlijst rendering met data
- Avatar fallbacks wanneer geen afbeelding
- Modal open/sluit transities
- Alle drie de viewports
Ik sloeg individuele veld focus statussen over voor deze PR—verminderende opbrengsten op een eenvoudig formulier. De agent kan het doen, maar 50 screenshots van tekst inputs die focus krijgen is geen nuttig signaal.
Het PR commentaar wordt een visueel changelog. Elke test run eindigt met een checklist:

Zes maanden vanaf nu kan ik naar elke PR kijken en precies zien hoe de UI eruit zag toen het werd geshipt.
Het Test Manifest
De prompt in de workflow hierboven is generiek — het werkt voor elke PR. De specificiteit komt van de diff zelf. De agent leest gh pr diff, ontdekt welke templates en views veranderden, mapt die naar URLs, en besluit wat te testen.
Voor een formulier component betekent dat het zal oefenen:
component: ClientForm
states:
- empty form
- filled form (valid data)
- validation error (submit without name)
- submitting (loading state)
- success (confirmation)
interactions:
- submit empty form (trigger validation)
- fill all fields, submit
- verify client appears in list after submit
Geen handmatig testplan nodig. De agent leidt het testoppervlak af van de code wijzigingen.
Waarom Screenshots in PR Commentaren
Inline screenshots in het PR commentaar hebben een paar voordelen:
- Zichtbaar — reviewers zien ze zonder door te klikken naar artefacten
- Contextueel — direct naast de diff die ze reviewen
- Vergelijkbaar — elke push update het commentaar, dus je ziet voor/na
- Controleerbaar — de commentaar geschiedenis toont wat er bij elke commit getest werd
Wat Dit Vangt
Echte bugs gevangen in de eerste week:
- Dropdown afgesneden door
overflow: hiddenparent (alleen zichtbaar op tablet) - Button tekst onzichtbaar in dark mode (verkeerde CSS variabele)
- Formulier labels verkeerd uitgelijnd op iPhone (flexbox gap issue)
- Hover status vastgelopen na touch (had
@media (hover: hover)nodig) - Modal backdrop dekt niet de volledige viewport op landscape tablet
Elk van deze passeerde code review. Elk was overduidelijk in de screenshots.
De Kosten
Claude draaien met Playwright MCP in CI duurt 2-4 minuten afhankelijk van hoeveel statussen getest moeten worden. Voor een typische PR die één component raakt, is het ongeveer 90 seconden.
Vergelijk met: deployen naar productie en van gebruikers horen dat de mobiele layout kapot is. Onbetaalbaar.
De Verschuiving
Visuele QA is altijd het knelpunt geweest. Je kunt unit tests automatiseren, integratie tests, zelfs end-to-end flows — maar iemand moet nog steeds naar de UI kijken. Dat is decennia lang waar geweest.
Het is niet meer waar. Agents met browsers draaien niet alleen scripts. Ze interpreteren, navigeren, interacteren en beoordelen. Het testoppervlak is niet hardcoded — het wordt afgeleid van de wijziging. Elke PR krijgt uitputtende visuele dekking die geen handmatig proces kan evenaren.
Dit verandert QE van een poort naar een garantie. Niet “heeft iemand het gecontroleerd” maar “de pipeline heeft het gecontroleerd, hier is het bewijs.” Elke PR, elke push, elke viewport. De QE rol verdwijnt niet. Het verplaatst upstream. In plaats van testplannen uitvoeren, ben je aan het definiëren waar de agent om moet geven. In plaats van bugs vangen, ben je het systeem aan het ontwerpen dat ze vangt.