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:

  1. Start een Docker Compose stack (Django + Postgres) vanuit de PR branch
  2. Voert migraties uit en zaait een testgebruiker
  3. Geeft de lokale URL door aan Claude Code met Playwright MCP (headless Chromium)
  4. Oefent interactieve elementen op basis van de PR diff
  5. Maakt screenshots van elke status bij drie viewports
  6. 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

ApparaatGrootteWaarom
Desktop1440×900Standaard laptop
Tablet1024×768iPad portret
Mobiel640×1136iPhone SE

Elke interactieve status wordt vastgelegd op alle drie. Een sidebar component genereert 12+ screenshots:

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:

GitHub issue aangemaakt door de visuele QA agent

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:

Desktop weergave: klantenlijst en Klant Toevoegen drawer op 1440x900

De agent testte:

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:

Testing Performed checklist met alle items die slagen

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:

  1. Zichtbaar — reviewers zien ze zonder door te klikken naar artefacten
  2. Contextueel — direct naast de diff die ze reviewen
  3. Vergelijkbaar — elke push update het commentaar, dus je ziet voor/na
  4. Controleerbaar — de commentaar geschiedenis toont wat er bij elke commit getest werd

Wat Dit Vangt

Echte bugs gevangen in de eerste week:

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.