Ein agentisches UI-Testsystem für den Peakboard Designer mit Claude Code, MCP und Windows UI Automation

30. September 2026 · aktualisiert 9. Oktober 2026

Agentic AI, Claude Code, MCP, UI Automation, WPF

Im März 2026 begann die Arbeit an einem Testsystem für den Peakboard Designer, eine mit Windows Presentation Foundation (WPF) entwickelte Anwendung. Ein KI-Agent, Claude Code, bedient die Benutzeroberfläche (UI) über Windows UI Automation wie ein menschlicher Tester, beurteilt das Ergebnis und meldet jeden gefundenen Fehler. Alles, was wiederholt werden muss, läuft ohne Modell und wird von einem separaten Runner als JSON-Testskript ausgeführt.

Der Peakboard Designer ist der Editor für Peakboard-Boards und bietet eine Arbeitsfläche, eine Toolbox, Eigenschaftsdialoge sowie ein eigenes Dateiformat für Projekte. Der folgende Screenshot zeigt ein Board mit sechs Stationen auf der Arbeitsfläche sowie rechts die Eigenschaften eines Text-Steuerelements.

Peakboard Designer, ein Board mit sechs Stationen auf der Arbeitsfläche und rechts die Eigenschaften eines Text-Steuerelements

Das Testsystem ist ein internes Werkzeug zum Testen des Designers und nicht Teil des Produkts.

Die Idee

Agentisch heißt, dass das Modell ein Tool aufruft, das Ergebnis liest und den nächsten Schritt selbst festlegt. Das Modell übernimmt alles, was Verständnis erfordert, wie das Erkunden eines unbekannten Dialogs, die Wahl des nächsten Klicks, die Prüfung eines Werts sowie das Schreiben des Fehlerberichts. Für alles, was jedes Mal auf dieselbe Weise wiederholt werden muss (z. B. eine nächtliche Regressionssuite), wird ein Runner ohne Modell eingesetzt.

Der Code des Systems ruft nie ein Modell auf. Claude Code ist das einzige Modell und läuft als eigener Prozess mit einem Prompt und mehreren Tools. Der Rest ist .NET, Markdown und JSON.

Das folgende Diagramm zeigt die Teile und die Verbindungen zwischen ihnen.

Testsystem für den Peakboard Designer, ein Diagramm mit dem Tester in Slack, dem Slack-Bot, dem Kanban-Board, Claude Code, den Notizen, dem MCP-Server, dem Skript-Runner und dem Bericht

Zunächst schreibt ein Tester eine Anfrage in Slack, woraufhin ein Bot Claude Code mit dem Test-Command und der Anfrage als Text startet. Eine Karte auf einem Kanban-Board startet Claude Code ebenfalls. Anschließend startet Claude Code den MCP-Server als Kindprozess, liest die eigenen Notizen und bedient die Anwendung über die Tools des Servers.

Für einen später zu wiederholenden Ablauf schreibt Claude Code ein JSON-Testskript und startet den Runner. Der Bericht sowie die Befunde werden an Slack gesendet und die neuen Erkenntnisse vom Agenten in den Notizen ergänzt.

Der MCP-Server

Das Model Context Protocol (MCP) ist der offene Standard, über den ein Modell Tools in einem anderen Programm aufruft. Der Server ist ein Konsolenprozess auf Basis von .NET 8, der von Claude Code als Kindprozess gestartet wird. Über stdin werden JSON-RPC-Nachrichten an den Server gesendet und die Antworten über stdout mit einer Nachricht je Zeile zurückgegeben.

Die Nachrichtenschleife für die drei Methoden initialize, tools/list und tools/call wurde ohne Software Development Kit (SDK) von Hand geschrieben, da das Protokoll klein ist. Der Server stellt mehr als 30 Tools in vier Gruppen bereit.

Die Lese-Tools liefern den UI-Baum, ein einzelnes Element, dessen Text oder eine Eigenschaft sowie die Liste der geöffneten Fenster. Mit den Aktions-Tools werden Klick, Doppelklick, Rechtsklick, Hover, Texteingabe, Tastendrücke, Listenauswahl sowie Drag-and-drop ausgeführt. Die Assertion-Tools prüfen Existenz, Sichtbarkeit, Bedienbarkeit, Wert und Text eines Elements. Für das Warten, für Screenshots, die Videoaufzeichnung, den Build der Anwendung sowie den Commit in Git stehen weitere Tools zur Verfügung.

Die Tools verwenden System.Windows.Automation, die verwaltete UI-Automation-API von .NET. Als zweites Backend steht über eine Umgebungsvariable die Bibliothek FlaUI zur Verfügung, die mit der neueren, auf dem Component Object Model (COM) basierenden Schnittstelle von UI Automation arbeitet. Bei der Elementsuche wird zunächst die AutomationId, d. h. die vom Entwickler für ein Steuerelement vergebene feste ID, und anschließend die Eigenschaft Name verwendet.

Die zentrale Entscheidung des Entwurfs ist, dass das Modell weder Screenshots noch den vollständigen UI-Baum erhält. Ein Klick kann einen Diff des Baums liefern (die hinzugekommenen, verschwundenen und geänderten Elemente, je Art höchstens 30). Zusätzlich liefert er immer einen Bericht über die geöffneten und geschlossenen Fenster. Das folgende JSON zeigt die Antwort auf einen Klick auf das Menü „File“:

{ "success": true, "automationId": "MainMenu_File", "executionMs": 2840,
  "diff": { "appeared": ["FileMenu_New", "FileMenu_Open"], "disappeared": [], "changed": [] },
  "activeDialog": null, "windows": { "count": 1, "opened": [], "closed": [] } }

Das Feld executionMs enthält die Antwortzeit in Millisekunden. Der Wert ist hoch, da für diesen Klick der Diff eingeschaltet war. Das Feld activeDialog ist null, da der Klick keinen Dialog geöffnet hat.

Die zwei für den Diff benötigten Momentaufnahmen des Baums erhöhen die Antwortzeit von etwa 200 auf etwa 3000 Millisekunden. Standardmäßig ist der Diff ausgeschaltet und wird vom Modell über einen Parameter aktiviert, wenn die Änderungen eines Klicks benötigt werden. Beide Werte stehen in der Toolbeschreibung, die das Modell liest.

Der Fensterbericht ist Teil jeder Antwort, damit dem Modell kein neuer Dialog entgeht. Vollständige Bäume und Screenshots speichert das Tool auf der Festplatte und gibt nur den Dateipfad und nie den Inhalt zurück.

Der Agent besteht aus Markdown-Dateien

Das Repository enthält keinen KI-Code, da der Agent aus Claude Code und Prompts in Markdown besteht. Eine Rolle ist eine Markdown-Datei mit Anweisungen. Claude Code lädt sie als Command, wenn der Prompt mit ihrem Namen beginnt, z. B. /test für die Testrolle. Weitere Rollen schreiben ein Konzept, setzen eine Aufgabe um oder schreiben Dokumentation.

Der Test-Prompt hat etwa 250 Zeilen und enthält zwei Grundsätze, fünf Regeln sowie das Format der Ergebniszeile, mit der jeder Lauf endet. Dabei gehen die Grundsätze jeder anderen Regel vor. Die Details stehen in Referenzdateien, eine je Thema (z. B. Memory-Protokoll, Fehlersuche, Live-Testen und Schreiben von Skripten), die Claude Code nur bei Bedarf liest.

Nach dem ersten Grundsatz „live first“ wird die Anwendung standardmäßig über die MCP-Tools bedient und das Ergebnis nach jedem Schritt geprüft. Ein JSON-Testskript wird nur geschrieben, wenn ein Ablauf später erneut laufen muss (z. B. für einen nächtlichen Lauf, eine Regressionssuite oder die dauerhafte Reproduktion eines Fehlers).

Nach dem zweiten Grundsatz „memory first“ liest der Agent vor dem ersten Klick seine Notizen. Bei einem fehlgeschlagenen Schritt durchsucht er die Notizen nach dem Symptom, statt den Aufruf zu wiederholen.

Fünf Regeln bestimmen die Qualität eines Laufs:

  • Ein Round-Trip für jeden Wert (abweichenden Wert setzen, speichern, erneut öffnen, zurücklesen)
  • AutomationIds nur aus dem Live-Baum
  • Bei jedem Fehlschlag ein Blick in die Notizen statt einer Wiederholung
  • Eine Korrektur im Quellcode für jede fehlende AutomationId
  • Ein Befund für jeden Fehler der Anwendung, auch bei bestandenem Test

Die erste Regel geht auf einen Fehler in einem Verbindungsdialog zurück, in dem eine Portnummer nach dem Speichern und erneuten Öffnen der Datei wieder auf dem Standardwert stand. Gefunden wurde der Fehler nur, weil der Test einen abweichenden Wert gesetzt, gespeichert, die Datei erneut geöffnet und den gespeicherten Wert geprüft hat. Ohne einen dieser vier Schritte bleibt der Fehler unsichtbar.

Memory, Feedback und Glossar

Da Claude Code jeden Lauf ohne Kontext beginnt, muss der Agent ohne Notizen jedes bekannte Problem der Anwendung in jedem Lauf erneut finden. Die Notizen sind Markdown-Dateien im Repository, derzeit rund tausend.

Eine Router-Datei listet etwa 30 Themen mit jeweils einer Zeile. Eine Themendatei listet ihre Notizen ebenfalls mit jeweils einer Zeile und verweist auf die benachbarten Themen. Zunächst liest der Agent den Router, öffnet anschließend ein bis drei Themen und folgt höchstens einem Verweis. Bei einem Fehlschlag werden die Notizen nach dem Symptom (z. B. „value reverts“) und nicht nach der Funktion durchsucht.

Es gibt drei Arten von Notizen. Eine Fehlernotiz enthält einen Fehlschlag, seine Ursache und den funktionierenden Schritt. Eine Workflow-Notiz enthält eine geprüfte Folge von Schritten, eine Prüfnotiz das Urteil über eine Funktion oder eine Aufgabe.

In einem Kombinationsfeld (ComboBox) schloss die Eingabetaste laut einer Fehlernotiz den gesamten Dialog, da WPF die Eingabetaste an den Standard-Button des Dialogs sendet. Als Lösung wird die Autovervollständigung mit der Pfeiltaste nach unten übernommen und anschließend auf den Button „OK“ geklickt.

Feedback ist nach den Notizen der zweite Speicher und enthält zwei Dutzend Regeln von Testern und Entwicklern. Ein Tester legt eine Regel in Slack mit einer Nachricht an, die mit „remember“ beginnt. Jede Regel hat einen Geltungsbereich (z. B. Test oder Dokumentation). In jedem Lauf werden die Regeln gelesen und angewendet sowie deren Nummern im Bericht genannt.

Das Glossar ist der dritte Speicher und enthält je Sprache die erlaubten Begriffe mit einer Definition sowie die falschen Begriffe mit ihrem Ersatz. Vor dem Schreiben von Text für Menschen (z. B. ein Fehlerbericht oder ein Hilfeartikel) wird das Glossar von jeder Rolle gelesen.

Der Skript-Runner

Der Runner ist ein Konsolenprogramm, das JSON-Testskripte ausführt. Er verwendet dieselbe Elementsuche und denselben Klick-Code wie der MCP-Server. Ein Klick des Modells und ein Klick aus einem Skript laufen damit durch denselben Code.

Ein Skript besteht aus Setup, Schritten, Teardown, Variablen und Importen. Es gibt zwölf Schritttypen (z. B. action, assert, wait_for, store, if, for_each sowie run für ein Unterskript). Das Format wird durch eine Schemadatei definiert, anhand derer ein unbekannter Schritttyp beim Laden des Skripts abgelehnt wird.

Für eine Aktion können die Elemente angegeben werden, die im Baum erscheinen oder verschwinden müssen. Ohne diese Angabe gilt ein Doppelklick auf ein Symbol der Toolbox auch dann als bestanden, wenn nichts passiert. Mit der Angabe schlägt der Schritt fehl, wenn nichts erscheint oder verschwindet.

Nach jedem Schritt führt der Runner sieben automatische Prüfungen aus, sog. Testorakel (engl. Test Oracles). Unabhängig vom Schritt selbst erkennen die Orakel die folgenden Zustände der Anwendung:

  • Der Prozess der Anwendung wurde beendet
  • Ein Fenster beantwortet eine Nachricht nicht innerhalb eines Timeouts
  • Ein neues Fenster ist erschienen, gemeldet durch einen WinEvent-Hook, auch wenn es bereits wieder geschlossen wurde
  • Ein neues Fenster ist erschienen, gefunden durch Polling, wenn der Hook ausfällt
  • Im Fenster ist ein Fehler-Label sichtbar
  • Die Logdatei der Anwendung enthält neue Fehlerzeilen
  • Das Anwendungsprotokoll der Windows-Ereignisanzeige enthält neue Einträge der Anwendung

Ein Absturzdialog wird an seinem Inhalt und nicht an seinem Titel erkannt, da der WPF-Dialog für eine unbehandelte Ausnahme den normalen Titel der Anwendung trägt. Hierzu liest der Runner den Text eines neuen Fensters und sucht nach einem Ausnahmetyp, einer Zeile des Stacktraces oder den beiden Buttons zum Fortsetzen und Beenden.

Der Runner schreibt einen HTML-Bericht mit einer Tabelle der Schritte und Screenshots, ein GIF aus den Screenshots sowie eine Zeile in eine SQLite-Datenbank. Für jeden Befund wird ein Fingerprint erzeugt (ein Hash über den Namen des Orakels, den normalisierten Titel und die erste Zeile des Stacktraces). Ein von zehn Skripten gefundener Absturz ist damit ein Problem und nicht zehn.

Zusätzlich gibt es einen Random Walk, ein Skript aus zufällig gewählten Operationen. Eine Operationsdatei listet vier Operationen, die die Anwendung in ihren vorherigen Zustand zurückbringen (z. B. das Anlegen eines Textblocks mit anschließendem Undo). Ein Generator wählt daraus zufällig 40 Operationen aus, mit einem Seed als Startwert für die Zufallszahlen, und schreibt ein normales Skript. In jeder Nacht werden drei Walks ausgeführt. Ein Absturz aus einem nächtlichen Lauf lässt sich mit dem Seed reproduzieren.

Fehlende AutomationIds

UI Automation findet ein Element über seine AutomationId, die der Entwickler in der Extensible Application Markup Language (XAML) setzt. Ohne AutomationId bleiben nur der Name oder eine Koordinate. Der Test-Prompt verbietet beides und behandelt eine fehlende ID als Fehler der Anwendung.

Die Korrektur übernimmt ein zweiter Agent, den der Hauptagent mit einem fertigen Aufgabentext aus einem Tool des MCP-Servers startet. Der Subagent verfügt über keine UI-Tools, sondern nur über Datei-Tools. Im XAML- oder C#-Quellcode sucht der Subagent das Element, ergänzt die AutomationId mit einem Namen aus Bereich, Steuerelementtyp und Zweck und ändert sonst nichts.

Der Subagent korrigiert höchstens 50 Elemente je Lauf. Nach seiner Rückkehr baut der Hauptagent die Anwendung mit dem Build-Tool, prüft die neuen IDs im Live-Baum, schreibt eine Notiz und committet die Änderung.

Slack und das Kanban-Board

Ein Bot verbindet Slack mit dem Testrechner. Der Bot ist ein .NET-Dienst ohne Modell, der die Bibliothek SlackNet im Socket Mode verwendet und dadurch keinen öffentlichen Endpunkt benötigt. Zunächst prüft der Bot den Absender gegen eine Allowlist von Benutzern und nimmt das erste Wort der Nachricht als Command. Anschließend startet der Bot Claude Code im Headless-Modus mit dem Command und dem Text der Anfrage und liest die Ausgabe als JSON-Stream.

Aus dem Stream liest der Bot die Session-ID und sendet alle vier Sekunden die neuen Tool-Aufrufe und Nachrichten des Agenten an Slack. Die letzte Zeile eines Laufs ist eine Ergebniszeile mit dem Pfad des HTML-Berichts oder einem Fehlertext. Der Bot lädt den Bericht und das GIF hoch und speichert die Session-ID je Kanal.

Eine spätere Nachricht im selben Kanal setzt dieselbe Session mit dem Resume-Flag fort. Dadurch kann ein Tester eine Frage zu Schritt 12 stellen und erhält eine Antwort mit dem vollständigen Kontext des Laufs.

Da auf dem Desktop nur eine Instanz der Anwendung laufen kann, wird jeweils nur ein Lauf ausgeführt und weitere Anfragen werden in eine Warteschlange gestellt. Der Bot läuft als geplante Aufgabe bei der Anmeldung und nicht als Windows-Dienst, da UI Automation einen interaktiven Desktop benötigt.

Im Juli 2026 kam das Kanban-Board als zweiter Weg hinzu, einen Lauf zu starten. Eine Karte ist eine Aufgabe und ein Lauf ein Start von Claude Code für eine Karte in einer Rolle. Entwickeln und Testen sind zwei getrennte Läufe. Der Testlauf setzt nicht voraus, dass der Entwicklungslauf etwas geprüft hat. Im Test-Prompt steht dazu in einer Zeile, dass eine gerade entwickelte Karte ungetestet ist.

Grenzen

Da im Live-Modus kein Orakel läuft, kann nur der Agent einen Fehler bemerken. Der Random Walk umfasst vier Operationen. Sessions werden je Slack-Kanal gehalten, nicht je Thread, und nur im Arbeitsspeicher des Bot-Prozesses. Bei einem Neustart des Bots gehen sie verloren.

Es gibt kein Retrieval über Embeddings, kein Fine-Tuning und keinen separaten Modellaufruf. Die Notizen werden mit einem Router und grep gelesen.

Das war’s.

Bei Fragen gerne melden.

Model Context Protocol →
Claude Code, programmatisch ausführen →
Claude Code, Skills und Commands →
Claude Code, Subagents →
Übersicht zu UI Automation →
FlaUI →
SlackNet →