Custom Steps

Die @custom-Konvention — ein dokumentierter Ausweg für Test-Steps, die noch zu keiner bestehenden Control-×-Action-Kombination passen.

← Zurück zur Übersicht · 🇬🇧 English · ← CI-Integration · Step-Description Localization →


Was ist ein Custom Step?

Wer einen Test-Case in einem Test-Management-Tool erstellt, braucht manchmal einen Step, für den es noch keine wiederverwendbare View × Control × Action-Kombination gibt — eine Prüfung oder Aktion, die noch niemand automatisiert hat. Statt den manuellen Testlauf an dieser Lücke zu blockieren, wird der Step als Custom Step festgehalten: Freitext, der beschreibt was passieren soll, plus ein paar optionale strukturierte Hinweise, welches UI-Element gemeint ist.

Ein Custom Step läuft von Anfang an problemlos als Teil eines manuellen Testlaufs — er wird wie jeder andere Step angezeigt, mit eigenem Ergebnis. Was noch fehlt, ist eine automatisierte Implementierung. Diese Seite dokumentiert die Konvention, die solche Steps auffindbar und maschinenlesbar macht, damit eine automatisierte Implementierung — durch dein eigenes Tooling oder durch künftige Framework-Fähigkeiten — auf etwas Stabilem aufbauen kann.

💡 Warum nicht einfach auf Automatisierung für diese Steps verzichten? Weil die Konvention es erlaubt, nachzuverfolgen, welche Steps noch manuell sind, wie viele es sind und worum es geht — ganz ohne vorab eine Zeile Automatisierungs-Code zu schreiben. Siehe Custom Steps entdecken und validieren unten für ein direkt lauffähiges Coverage-Skript.


Woher Custom Steps kommen

Hat dein Test-Management-Tool eine “Custom”-Option für einen Step, der zu keiner bestehenden Kombination passt, schreibt es typischerweise direkt einen Custom Step in die Step-Library-Datei deiner App, gemäß der Konvention auf dieser Seite — die genaue Schreibweise entnimmst du der Dokumentation deines Tools. Du kannst einen auch jederzeit von Hand schreiben; die Konvention setzt kein bestimmtes Authoring-Tool voraus.


Einen Custom Step per Hand schreiben

Ein Custom Step ist eine ganz normale TS_*-exportierte Funktion im 2_Steps-Ordner deiner App, markiert mit einem @custom-JSDoc-Block. Das Template liegt unter 2_Apps/_Skeleton/2_Steps/TS_Custom.ts:

// 2_Apps/<DeineApp>/2_Steps/TS_Custom.ts
import * as Core from '@meintest/cc-testframework';

/**
 * @custom
 * @step s_example
 * @intent check
 * @target { view: Main, control: Textfield, label: 'Email' }
 * @property color
 * @operator equals
 * @expected red
 */
export const TS_Custom_Example = async (_pageLogName: string) => {
    await Core.Step.numberedStep(`Prüfe dass die Farbe des Feldes 'Email' rot ist`, async () => {
        throw new Error('Custom step not yet automated');
    });
};

Kopiere diese Datei, benenne den Export in etwas Beschreibendes um (z.B. TS_Custom_CheckEmailFieldColor), und passe die JSDoc-Tags sowie die numberedStep-Beschreibung an deinen Fall an. Bis er implementiert ist, bleibt der Step-Body genau wie gezeigt — die Zeile throw new Error('Custom step not yet automated') markiert ihn als offen.

💡 Warum steht die Beschreibung im Step-Body, nicht im JSDoc? Der Text, den ein manueller Lauf dem Fach-Tester zeigt, kommt aus dem numberedStep()-Argument, nicht aus einem separaten JSDoc-Tag. Es gibt bewusst kein @description-Tag — den Wortlaut an genau einer Stelle zu halten heißt, dass die Beschreibung, die ein manueller Tester sieht, nie von dem abweichen kann, was Tooling ausliest. Soll diese Beschreibung statt auf Englisch in der Sprache eines Testers erscheinen? Siehe Custom-Steps lokalisieren weiter unten — eine Sibling-Katalog-Datei, kein JSDoc-Tag, ist der Ort für eine Übersetzung.

💡 Custom Step vs. Core.defineExecutionStep. Die beiden lösen unterschiedliche Probleme. Ein Custom Step zielt auf eine konkrete Control — eine Prüfung oder Interaktion, die noch niemand automatisiert hat — und entsteht handgeschrieben oder Agent-generiert zur Autoring-Zeit. Core.defineExecutionStep ist ein Framework-Level-Scaffold für den eigenen Lifecycle einer App (Start/Close/Restart/NavigateTo); es injiziert page/url (Web) bzw. executable/appiumUrl (Desktop) für dich aus GlobalConfig.apps — du zielst dabei nie auf eine konkrete Control. Siehe API-Referenz — Core.defineExecutionStep für die Signatur.

Tag-Referenz

Tag Erforderlich Werte Bedeutung
@custom Ja (nur Vorhandensein, kein Wert) Markiert diesen Export als Custom Step.
@step Ja s_ + mindestens 4 Kleinbuchstaben/Ziffern, z.B. s_ab12 Stabile Step-Identität. Diesen Wert nach Vergabe nicht mehr ändern — er verknüpft Ergebnis und Historie eines manuellen Laufs mit genau diesem Step, auch nachdem er eine automatisierte Implementierung bekommt.
@intent Ja check | action Ob der Step etwas prüft oder etwas ausführt.
@target Nein { view: Main\|Dialog\|Message, section: '...', control: '...', label: '...' } Hinweis, welches UI-Element der Step meint. Anführungszeichen um Werte sind optional. Je mehr Slots du füllst, desto weniger muss eine automatisierte Implementierung raten.
@property Nein Freitext, z.B. color, text, visible Die Eigenschaft, die geprüft oder verändert wird.
@operator Nein equals | contains | not | exists | greater | less Vergleichs-Operator — nur relevant, wenn @intent check ist.
@expected Nein Freitext, z.B. red Der erwartete Wert, sofern zutreffend.

💡 Was, wenn ich den genauen Control-Namen noch nicht kenne? Fülle so viel von @target aus, wie du kannst, und lass den Rest weg — section und label allein reichen oft, damit ein Mensch (oder Tool) das richtige Element später findet. Ein leeres @target ist ebenfalls gültig; es bedeutet nur mehr Rateaufwand weiter unten in der Kette.

Das Gegenstück-Tag zu @custom schreibst du nie von Hand: Sobald der Authoring-Agent einen Step implementiert, ersetzt er @custom durch @implementedAt <Zeitstempel> als Audit-Trail — jedes andere Tag, @step eingeschlossen, bleibt unangetastet.

Custom-Steps lokalisieren

Das ausgelieferte TS_Custom.ts-Template ruft Core.i18n.t(...) direkt auf, statt einen rohen String an numberedStep(...) zu übergeben:

export const TS_Custom_Example = async (_pageLogName: string) => {
    await Core.Step.numberedStep(
        Core.i18n.t('TS_Custom_Example', {}, {
            fallback: `Prüfe dass die Farbe des Feldes 'Email' rot ist`,
        }),
        async () => {
            throw new Error('Custom step not yet automated');
        },
    );
};

Core.i18n.t(key, values, { fallback }) schlägt key im Sibling-Katalog TS_Custom.i18n.json für die aktuelle Locale des Testers nach — automatisch aus dem eigenen Dateipfad des Steps erkannt, du übergibst nie selbst einen Pfad — und fällt auf den fallback-String zurück, wenn kein Katalog, Schlüssel oder Locale-Eintrag existiert.

Custom-Step-Code entsteht im Moment seiner Erstellung — typischerweise durch den Authoring-Agent — ohne dass für den neuen Schlüssel bereits eine Katalog-Datei existiert. Deshalb bleibt fallback hier inline, statt wie bei einem katalog-gestützten Factory-Step wegzufallen: Bis ein Übersetzer einen TS_Custom.i18n.json-Eintrag für diesen Schlüssel ergänzt, ist fallback die Quelle der Wahrheit für den englischen Text dieses Steps zur Laufzeit — es gibt nichts anderes, worauf zurückgefallen werden könnte. Siehe Wann fallback verwenden für die vollständige Begründung, und Step-Description Localization für die vollständige Katalog-Konvention. Übersetzte Einträge fügst du TS_Custom.i18n.json genauso hinzu wie bei jeder anderen Step-Datei; Laufzeit-Lokalisierung beschreibt, wie GlobalConfig.language/CC_TESTFRAMEWORK_LOCALE die Locale bestimmen, gegen die t() auflöst.


Custom Steps entdecken und validieren

Zwei Funktionen aus der Public-API des Frameworks erlauben es, dein Projekt nach Custom Steps zu durchsuchen und sie auf Authoring-Fehler zu prüfen — unabhängig von jedem Test-Management-Tool:

import { discoverCustomSteps, validateCustomStep } from '@meintest/cc-testframework';
import * as path from 'path';

const discoveries = await discoverCustomSteps(path.join(process.cwd(), 'tests'));

for (const discovery of discoveries) {
    const result = validateCustomStep(discovery);
    const status = discovery.isNotImplemented ? 'pending' : 'implemented';
    console.log(`${discovery.spec.stepId} (${discovery.stepName}) in ${discovery.file}:${discovery.line}${status}`);

    if (!result.isValid) {
        console.error('  errors:', result.errors);
    }
    if (result.warnings.length > 0) {
        console.warn('  warnings:', result.warnings);
    }
}

discoverCustomSteps(rootDir) durchläuft <rootDir>/2_Apps/*/2_Steps/*.ts, parst jeden @custom-markierten Export über den TypeScript-Compiler und liefert pro Step eine CustomStepDiscovery — in Datei-Reihenfolge, von oben nach unten. Hat dein Projekt noch keinen 2_Apps-Ordner, kommt ein leeres Array zurück statt eines Fehlers.

validateCustomStep(discovery) prüft eine einzelne Discovery auf gängige Authoring-Fehler und trennt blockierende von informativen Problemen:

Prüfung Schweregrad
stepId fehlt oder passt nicht zu s_[a-z0-9]{4,} Error
intent fehlt oder ist nicht check/action Error
description fehlt oder ist leer Error
operator gesetzt, aber kein erkannter Wert Error
target.view gesetzt, aber nicht Main/Dialog/Message Error
target fehlt komplett Warning
intent ist check, aber weder property noch expected gesetzt Warning
intent ist action, aber operator ist gesetzt Warning

Errors bedeuten, dass der Step so nicht zuverlässig implementiert werden kann und zur Korrektur an den Autor zurück sollte. Warnings sind informativ — der Step lässt sich trotzdem bearbeiten, nur mit weniger Anhaltspunkten.

💡 Doppelte Step-IDs. Teilen sich zwei Custom Steps in unterschiedlichen Apps denselben @step-Wert, loggt discoverCustomSteps eine Warnung in die Konsole, gibt aber trotzdem beide zurück — die Kollision aufzulösen (z.B. eine umbenennen) liegt bei wem auch immer die Discovery-Ergebnisse weiterverarbeitet.

Types

import type { CustomSpec, CustomStepDiscovery, ValidationResult } from '@meintest/cc-testframework';

Siehe API-Referenz — Sektion 12 für die vollständige Feld-für-Feld-Aufschlüsselung dieser drei Types.


Automatisierte Implementierung: der Authoring-Agent

Einen entdeckten Custom Step in eine funktionierende Implementierung zu verwandeln, muss nicht mehr von Hand passieren. runAuthoringAgent(options), aus der Public-API des Frameworks exportiert, nimmt die Custom Steps deines Projekts plus ein Digest des aktuellen UI-Baums deiner Anwendung entgegen und generiert — für jeden Step, bei dem er sich sicher ist — eine echte Implementierung und schreibt sie in die zugehörige Datei.

import { runAuthoringAgent } from '@meintest/cc-testframework';
import * as path from 'path';

const result = await runAuthoringAgent({
    rootDir: path.join(process.cwd(), 'tests'),
    treeDigest: myLiveTreeDigest,   // ein Live-App-UI-Baum-Digest — siehe unten
    dryRun: true,                   // nur Vorschau; es wird noch nichts geschrieben
    onProgress: (event) => console.log(`[${event.phase}] ${event.message}`),
});

console.log(`${result.applied.length} applied, ${result.skipped.length} skipped, ${result.errors.length} errored`);

Setze dryRun: false (oder lass es weg — das ist der Default), sobald du eine Vorschau geprüft hast und bereit bist, den Agenten echte Dateien schreiben zu lassen.

💡 Woher kommt treeDigest? Der Agent braucht einen Schnappschuss des aktuellen UI-Baums deiner Anwendung — im selben Format, das das eigene Inspektions-Tooling des Frameworks intern erzeugt. Diesen Schnappschuss selbst zu erfassen (App starten, Baum erfassen, übergeben) ist nur nötig, wenn du runAuthoringAgent direkt aufrufst. Der Kommandozeilen-Einstiegspunkt weiter unten auf dieser Seite erfasst ihn komplett für dich — siehe Der Agent als Kommandozeilen-Befehl.

Was generiert wird

Jeder angewendete Step bekommt eine von zwei Formen, festgehalten in AppliedStep.implementation:

  • control-action — ein Aufruf einer bestehenden Framework-Methode (Core.Action.click, Core.Check.textContentEquals und ähnliche) auf einem frisch aufgelösten Locator. Das bekommen die meisten @intent: action-Steps und gut spezifizierte @intent: check-Steps.
  • inline-script — ein überprüfbarer Stub für eine @custom-Kombination, für die der Agent noch kein konkretes Template hat. Der generierte Body ist mit einem // TODO(agent)-Kommentar markiert und wirft bewusst einen Fehler, damit für einen menschlichen Reviewer unmissverständlich klar ist, dass diese Stelle noch eine echte Implementierung braucht.

Jeder generierte Locator nutzt das Core.xpath-Tag (siehe Self-Healing Locators), sodass vom Agenten geschriebene Call-Sites ab dem Moment ihrer Erzeugung self-healing-fähig sind — kein zusätzlicher Schritt nötig. Schreibzugriffe laufen über dieselbe Koordinations-Schicht, die auch der Self-Healing-Writeback nutzt (siehe Self-Healing Locators — Multi-User-Koordination), sodass ein Agent-Lauf sicher in einem geteilten Repository aktiviert werden kann.

Sobald ein Step implementiert ist, wird sein @custom-JSDoc-Tag durch @implementedAt <Zeitstempel> ersetzt. Jedes andere Tag — vor allem @step — bleibt vollständig unangetastet, sodass die Identität des Steps in der Historie deines Test-Management-Tools sich nie ändert, auch nachdem er eine automatisierte Implementierung bekommt.

💡 Ein Template generiert aktuell Code, der noch nicht kompiliert. Ein @property color / @operator equals-Check ruft eine Check-Methode auf, die das Framework noch nicht mitliefert. Bis diese Methode landet: prüfe jeden angewendeten Color-Check-Custom-Step und passe die Assertion von Hand an, bevor du dich darauf verlässt, dass der Test durchläuft — der Agent verzeichnet ihn trotzdem als applied, da er ja etwas geschrieben hat, aber lauffähig ist er so nicht.

Wie generierter Code verifiziert wird

Jeder Step, den der Authoring-Agent schreibt, wird gegen den Skeleton-Konventionen-Regelsatz geprüft, bevor er in deiner Source-Datei landet — dieselbe Prüfung, die auch einen handgeschriebenen Step absichert. Findet sich dabei eine fatale Verletzung, versucht der Agent es erneut mit einer fokussierten Fix-Beschreibung, bis zu drei Versuche insgesamt; schlägt es danach immer noch fehl, wird der Step übersprungen, statt Code zu schreiben, der die Validierung nicht bestehen würde.

Was du siehst.

Fall Was passiert
Der generierte Code besteht Nichts — der Step wird geschrieben, und der Lauf läuft still weiter. Das ist der Normalfall.
Die Prüfung findet eine fatale Verletzung Eine Zeile wie retry attempt 2/3 — fixing convention violations erscheint in der Ausgabe des Laufs, während der Authoring-Agent es erneut versucht.
Alle 3 Versuche schlagen weiterhin fehl Der Step wird übersprungen. Er erscheint in AuthoringResult.skipped[] mit reason: 'convention-guard-rejected' — siehe den Abschnitt “Warum ein Step übersprungen wird” weiter unten für die vollständige Tabelle der Skip-Gründe — und das begleitende Detail listet exakt auf, welche Regel(n) der generierte Code verletzt hat.

Ein abgelehnter Step bleibt nicht still kaputt — du hast zwei Optionen: die zugrunde liegende Mehrdeutigkeit von Hand beheben (die @target/@property/@expected-Tags des Custom Steps konkreter machen, oder den Step selbst implementieren) und den Agenten erneut laufen lassen, oder den Skip vorerst akzeptieren und später darauf zurückkommen. So oder so verhält sich der Step in der Zwischenzeit weiter wie ein normaler, nicht automatisierter Custom Step.

💡 Welche Regeln gelten hier? Dieselben sechs aus Skeleton-Konventionen — R2 (Naming), R3 (Signatur) und R4 (XPath-Style) machen hier den Großteil der Arbeit, da LLM-generierter Code am ehesten dort von einer Generierung zur nächsten driftet.

Konfiguration

Env-var Effekt
ANTHROPIC_API_KEY Erforderlich (BYOK) — derselbe Key, der für Self-Healings Vision-Calls genutzt wird. Der Standard-Element-Finder des Agenten wirft sofort einen Fehler, wenn dieser Wert fehlt, keine anthropicApiKey-Option an runAuthoringAgent übergeben wurde, und auch über den OS-Credential-Store nichts auflösbar ist.
AI_VISION_MODEL Optional. Überschreibt das Claude-Modell zur Identifikation der Ziel-Elemente. Default: claude-opus-4-7. Auch setzbar via npx cc-testframework set-ai-vision-model <model> oder das Config-Feld aiVisionModel.
CC_AUTHORING_MAX_STEPS_PER_RUN Optionaler Sicherheits-Circuit-Breaker. Begrenzt, wie viele Custom Steps ein einzelner Lauf bearbeitet; der Rest wird als übersprungen gemeldet (max-steps-reached, siehe unten) statt versucht. Unbegrenzt, wenn nicht gesetzt. Die maxSteps-Option hat, falls übergeben, Vorrang vor der Env-Var.

Warum ein Step übersprungen wird

Einträge in AuthoringResult.skipped[] tragen einen reason, der erklärt, warum der Agent diesen Step nicht implementiert hat:

reason Bedeutung
validation-failed Die @custom-Tags des Steps bestehen validateCustomStep nicht — behebe den Authoring-Fehler und lauf erneut.
duplicate-step-id Dieselbe @step-ID ist in mehr als einer App deklariert. Löse die Kollision (eine umbenennen), bevor du erneut läufst — beide Kopien werden bis dahin übersprungen.
already-implemented Der Body wirft den “not yet automated”-Platzhalter nicht mehr, obwohl @custom noch vorhanden ist — der Agent überschreibt nie eine bestehende Implementierung.
element-not-found Kein Element im übergebenen treeDigest passte zu den Target-Hinweisen dieses Steps.
agent-uncertain Ein Kandidaten-Element wurde gefunden, aber das Vertrauen war zu niedrig, um darauf zu handeln — der Agent überspringt statt gegen eine Vermutung zu implementieren.
agent-uncertain-underspecified @intent: check ist gesetzt, aber weder @property noch @expected — es gibt nicht genug Information, um eine sinnvolle Assertion zu generieren.
max-steps-reached Das Step-Budget des Laufs (CC_AUTHORING_MAX_STEPS_PER_RUN / maxSteps) war bereits aufgebraucht, als dieser Step an der Reihe war.
file-locked-by-human Ein anderer Schreiber (ein Kollege, ein anderer CI-Job oder ein Tool, das am selben Koordinations-Protokoll teilnimmt) hält gerade den Schreib-Lock für diese Datei — siehe Self-Healing Locators — Multi-User-Koordination.
convention-guard-rejected Der generierte Code hat die Skeleton-Konventionen-Prüfung innerhalb von 3 Versuchen nicht bestanden. Siehe Wie generierter Code verifiziert wird oben, was dazu führt.

Das Result-Objekt

runAuthoringAgent gibt ein AuthoringResult zurück — es persistiert von sich aus keine Report-Datei; wenn du es direkt aufrufst und ein JSON-Artefakt für einen CI-Schritt willst (z.B. um einen PR-Kommentar zu posten), schreib es selbst, ähnlich im Geist zu Self-Healings .self-healing-report.json. Der Kommandozeilen-Einstiegspunkt weiter unten persistiert genau diese Form automatisch, am von dir gewählten --report-path (Default .custom-step-authoring-report.json):

{
  "schemaVersion": 1,
  "runTimestamp": "2026-07-14T10:03:21.000Z",
  "mode": "applied",
  "totalDiscovered": 2,
  "applied": [
    {
      "stepId": "s_ab12",
      "file": "2_Apps/<DeineApp>/2_Steps/TS_Custom.ts",
      "stepName": "TS_Custom_CheckEmailFieldColor",
      "implementation": "control-action",
      "elementId": "txtEmail",
      "confidence": "high",
      "reasoning": "Digest lists a Textfield with AutomationId='txtEmail' matching the target label 'Email'.",
      "filesWritten": ["2_Apps/<DeineApp>/2_Steps/TS_Custom.ts"]
    }
  ],
  "skipped": [
    {
      "stepId": "s_cd34",
      "file": "2_Apps/<DeineApp>/2_Steps/TS_Custom.ts",
      "stepName": "TS_Custom_SubmitOrder",
      "reason": "agent-uncertain",
      "reasonDetail": "Two buttons labelled 'Submit' are visible; the target hint doesn't disambiguate them."
    }
  ],
  "errors": [],
  "commitSha": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
}

commitSha und lockedByHumans erscheinen unter denselben Bedingungen wie ihre Self-Healing-Gegenstücke — siehe Self-Healing Locators — Die Report-Datei für die zugrunde liegende Koordinations-Semantik.


Der Agent als Kommandozeilen-Befehl

npx cc-testframework author läuft in einem von zwei sich gegenseitig ausschließenden Modi. Beide sind Teil des Framework-eigenen Pakets — kein zusätzlicher Installationsschritt, verfügbar sobald @meintest/cc-testframework installiert ist.

Modus Flag Was passiert
Runtime (Default) --test <path> Führt einen kompletten Test-Case iterativ aus — bei jedem Fehlschlag klassifiziert er die Ursache und ruft, für die Fehlerarten, für die er eine Strategie hat, den Authoring-Agenten auf, dann läuft der Test erneut — bis er komplett durchläuft oder ein nicht behebbarer Fehler bzw. ein Circuit-Breaker greift.
Batch (Legacy) --batch-mode --app <name> Das ursprüngliche Verhalten: implementiert jeden @custom-markierten Step im 2_Steps-Ordner einer einzelnen App gegen einen einzigen Live-Schnappschuss dieser App.

Werden beide Flags übergeben, oder keines von beiden, endet der Aufruf mit Code 2, bevor irgendein Modus startet.

Runtime-Modus: einen kompletten Test-Case reparieren

Aus pm/ heraus:

export ANTHROPIC_API_KEY="sk-ant-..."
npx cc-testframework author --test 3_Cases/TC_<DeinAblauf>.spec.ts --dry-run

💡 Lieber den Key nicht jede Session neu exportieren? export ANTHROPIC_API_KEY=... funktioniert weiterhin genau wie gezeigt, ist aber nicht mehr die einzige Option — npx cc-testframework config set anthropic-api-key speichert ihn einmalig im Credential-Store deines Betriebssystems, und die CLI fragt beim ersten Gebrauch interaktiv danach, wenn du beides überspringst. Siehe Credential Management für das vollständige Setup.

Der Runtime-Modus startet nie selbst eine Live-App-Session und liest auch keine apps.json — er startet deinen Test-Case als echten Playwright-Subprozess (--workers=1 --reporter=json) und lässt den Control-/Step-Code, den der Test ohnehin nutzt, ganz normal mit deiner Anwendung (oder deinen Anwendungen) sprechen. Genau das macht auch einen Multi-App-Test-Case möglich (z.B. ein Web-App-Step gefolgt von einem Postfach-Check-Step): die Schleife interessiert sich nur für das Bestehen/Scheitern des Tests und, im Fehlerfall, für die Datei:Zeile, auf die der Fehler zeigt.

Bei jedem fehlgeschlagenen Lauf wird der Fehler in eine von drei behebbaren Kategorien eingeordnet — nur für diese wird der Agent überhaupt aktiv:

Klassifizierung Bedeutung
custom-not-implemented Der Platzhalter eines @custom-markierten Steps wirft noch immer — derselbe Auslöser wie im Batch-Modus, nur pro Fehlschlag entdeckt statt vorab.
element-not-found-no-refid Eine Locator-Suche hat ihr Element nicht gefunden, und in der Nähe der fehlgeschlagenen Zeile steht kein Inspector.bindReference-Aufruf — Self-Healing hatte also kein Referenzbild, gegen das es reparieren könnte.
timeout-no-refid Wie oben, nur war der Fehler ein Timeout statt “nicht gefunden”.

Jede andere Fehlerform (eine echte Assertion-Abweichung, ein Code-Fehler, ein Netzwerkfehler, ein abgestürzter Browser, oder ein “Element nicht gefunden”-Fall MIT Referenzbild in der Nähe — dann hatte Self-Healing schon seine Chance) wird einem Menschen überlassen: die Schleife stoppt sofort mit finalStatus: 'failed-non-fixable'.

💡 Was passiert, wenn ein bereits implementierter Step bricht? Genau das decken element-not-found-no-refid und timeout-no-refid ab: ein Step, der gar kein Custom Step ist, aber nach einer Änderung an deiner Anwendung anfängt zu scheitern, ohne Self-Healing-Referenzbild als Rückfallebene. Der Agent diagnostiziert den Fehler und identifiziert das voraussichtlich richtige Element genauso wie bei einem Custom Step — nur das automatische Zurückschreiben dieses Fixes in die Datei ist für diese Kategorie noch nicht unterstützt. Das JSDoc eines bereits implementierten Steps trägt kein @custom-Tag, und der Writer weigert sich bewusst, eine Datei ohne dieses Tag anzufassen — dieselbe Regel, die jeden Custom-Step-Schreibvorgang davor bewahrt, versehentlich handgeschriebenen Code zu überschreiben. In der Praxis wird diese Kategorie bis zu --max-attempts-per-step-mal erneut versucht, danach stoppt der Lauf mit finalStatus: 'stuck-in-loop'. Führe denselben Test-Case erneut mit --dry-run aus — der applied[]-Eintrag für diese Stelle im resultierenden Report zeigt dann das Element und die Begründung, die der Agent ermittelt hat, zum manuellen Übernehmen. Die vollständige Automatisierung dieser Kategorie ist für eine spätere Version vorgesehen.

Optionen

Option Effekt
--test <path> Erforderlich für den Runtime-Modus. Pfad zur Test-Case-Spec-Datei, die ausgeführt und repariert werden soll.
--dry-run Vorschau-Modus — Klassifizierung, Vision und Code-Generierung laufen normal, aber es wird keine Datei geschrieben.
--max-iterations <N> Globaler Circuit-Breaker über den gesamten Lauf. Default 20 (Env-Var CC_AUTHORING_MAX_ITERATIONS).
--max-attempts-per-step <N> Circuit-Breaker pro Stelle — wie oft dieselbe Datei:Zeile erneut versucht werden darf, bevor der Lauf sie aufgibt. Default 3 (Env-Var CC_AUTHORING_MAX_ATTEMPTS_PER_STEP).
--report-path <path> Wohin der JSON-Report geschrieben wird. Default: <cwd>/.custom-step-authoring-runtime-report.json — eine eigene Datei/Schema, getrennt vom Report des Batch-Modus.
--help, -h Gibt die Optionen für beide Modi aus und beendet sich.

Die Konfiguration entspricht ansonsten oben (ANTHROPIC_API_KEY, AI_VISION_MODEL, oder der OS-Credential-Store als Alternative zur Env-Var), plus SELF_HEALING_AGENT_IDENTITY für die Commit-Autoren-Identität des Auto-Commits — siehe Self-Healing Locators (Abschnitt „Commit-Identität“).

💡 Vertippter --test-Pfad? Bestätigt Playwrights eigener JSON-Report, dass der angegebene Pfad/das Pattern null Test-Dateien getroffen hat — meist ein Tippfehler, z.B. ein fehlender Unterstrich —, bleibt die Schleife nicht still: sie druckt [AuthorTestRunner] Warning: Playwright matched 0 test files for pattern '<path>'. Possible typo? Run 'npx playwright test --list' to see all detected test files., sendet ein playwright-0-tests-Progress-Event und trägt einen Eintrag in das credentialIssues-Array des Reports ein (kind: 'playwright-0-tests', mit dem betroffenen pattern). Weder finalStatus noch der Exit-Code ändern sich dadurch allein — null Treffer über einen bewusst gesetzten --grep-Filter sind legitim —, aber weil Playwright selbst bei “0 Tests getroffen” mit Code 0 beendet, kann derselbe Lauf sonst wie ein sauberes finalStatus: 'passed' aussehen. Prüfe credentialIssues daher auch nach einem scheinbaren Erfolg.

Beispiel-Ausgabe

[test-run] iteration 1: Running '3_Cases/TC_<DeinAblauf>.spec.ts' (attempt 1/20)...
[test-fail] iteration 1: Test failed (exitCode=1) — classifying...
[classify] iteration 1: Classified as 'custom-not-implemented' (fixable=true).
[fix-attempt] iteration 1: Dispatching the Authoring Agent for '2_Apps/<DeineApp>/2_Steps/TS_Custom.ts:12' (attempt 1/3)...
[fix-applied] iteration 1: Fix applied (commit a1b2c3d).
[test-run] iteration 2: Running '3_Cases/TC_<DeinAblauf>.spec.ts' (attempt 2/20)...
[test-pass] iteration 2: Test passed.
[done] iteration 2: Runtime-Authoring-Loop finished: passed (2 iteration(s), 1 distinct fix location(s)).

finalStatus, im Report persistiert, ist einer von:

finalStatus Bedeutung
passed Der Test-Case ist grün durchgelaufen.
failed-non-fixable Ein Fehler ohne Fix-Strategie ist aufgetreten — siehe die Klassifizierungs-Tabelle oben.
stuck-in-loop Dieselbe Datei:Zeile-Stelle hat --max-attempts-per-step überschritten.
max-iterations-reached Der gesamte Lauf hat --max-iterations überschritten, ohne zu konvergieren.

Batch-Modus (Legacy): eine App, ein Durchlauf

export ANTHROPIC_API_KEY="sk-ant-..."
npx cc-testframework author --batch-mode --app <DeineApp> --dry-run

Unverändert seit dem ursprünglichen Release: startet eine Live-Session für die genannte App, erfasst ihr UI-Baum-Digest, implementiert jeden @custom-markierten Step im 2_Steps-Ordner dieser App in einem Durchlauf und persistiert einen AuthoringResult-Report (Form siehe Das Result-Objekt und den Abschnitt “Warum ein Step übersprungen wird” oben).

Option Effekt
--batch-mode Erforderlich, um diesen Modus zu wählen.
--app <name> Erforderlich. Die App, für die Custom Steps implementiert werden sollen — muss einem Schlüssel in deiner apps.json entsprechen. Eine App pro Lauf.
--tests-root <path> Pfad zu deinem Test-Root. Default: <cwd>/tests.
--apps-config <path> Pfad zur apps.json. Default: dieselbe Auto-Discovery-Reihenfolge, die die Inspector-CLIs bereits nutzen — expliziter Pfad, dann CCTF_APPS_CONFIG, dann <Projekt-Root>/apps.json, dann <cwd>/apps.json.
--dry-run Vorschau-Modus — Vision und Code-Generierung laufen normal, aber es wird keine Datei geschrieben.
--max-steps <N> Begrenzt, wie viele Custom Steps dieser Lauf versucht. CC_AUTHORING_MAX_STEPS_PER_RUN hat Vorrang, falls beide gesetzt sind.
--report-path <path> Wohin der JSON-Report geschrieben wird. Default: <cwd>/.custom-step-authoring-report.json.

Exit-Codes (beide Modi)

Code Runtime-Modus Batch-Modus
0 finalStatus: 'passed'. Jeder versuchte Step wurde angewendet, oder es wurden null Custom Steps gefunden.
1 Nicht behebbarer Fehler, stuck-in-loop, oder max-iterations erreicht. Der Agent hat mindestens einen Eintrag in errors[] verzeichnet.
2 User-Fehler — fehlendes/widersprüchliches --test/--batch-mode, --test-Pfad nicht gefunden, oder projectRoot ist kein Git-Repository. User-Fehler — fehlendes --app, ein schlicht fehlender Anthropic-API-Key (nirgends in der Priority-Chain auflösbar), ein ungültiges --tests-root, oder ein --app-Wert, der nicht in der apps.json vorkommt.
3 (nicht genutzt) Jeder versuchte Step wurde mit reason: 'file-locked-by-human' übersprungen — ein Kollege (oder ein anderer CI-Job) hält den Koordinations-Lock für jede betroffene Datei. Ein Teil-Lock endet weiterhin mit 0.
4 finalStatus: 'credential-error' — der Anthropic-API-Key konnte aus IRGENDEINEM Grund nicht aufgelöst werden (fehlend, abgelaufen, ungültig, oder ein nicht-interaktiver Kontext, in dem der First-Run-Prompt nicht laufen kann). Siehe AuthorTestResult.credentialIssues und Credential Management. Nur wenn die aufgelöste Zugangsdaten abgelaufen ist — ein schlicht fehlender Key endet weiterhin mit 2 (siehe oben); der Batch-Modus unterscheidet Gründe für einen Credential-Auflösungs-Fehler ansonsten nicht.

💡 Einbindung in CI. Weil die Exit-Codes Agent-/Schleifen-Fehler (1) von einem Lock-Patt (3, nur Batch-Modus) trennen, kann eine Pipeline unterschiedlich reagieren — siehe FAQ — Wie binde ich den Authoring-Agenten in meine CI-Pipeline ein? für ein kurzes Beispiel.

💡 Lieber programmatisch statt über die Shell aufrufen? runAuthorCli(options) deckt beide Modi mit demselben Verhalten ab, plus Test-Injection-Hooks für fortgeschrittenes Tooling. Brauchst du nur die Runtime-Schleife, ohne die argv-förmigen Optionen der CLI? runAuthorTestRunner(options) ist dieselbe Logik, an die runAuthorCli intern delegiert. Siehe API-Referenz — Sektion 12.


Wo es weitergeht

  • Deinen ersten TestCase schreiben — die TS_Custom_<Action>-Namenskonvention und wie der Name eines Custom Steps neben Core.defineExecutionStep-gescaffoldeten Steps steht
  • API-ReferenzdiscoverCustomSteps, validateCustomStep, runAuthoringAgent, runAuthorCli, runAuthorTestRunner, classifyFailure, synthesizeCustomStepFromFailure sowie alle zugehörigen Types
  • Skeleton-Konventionen — die Naming-, Signatur- und XPath-Style-Regeln, gegen die der oben generierte Code geprüft wird
  • Step-Description Localization — die Beschreibung eines Steps in der Sprache eines Testers zeigen, über einen Sibling-.i18n.json-Katalog, und wie Core.i18n.t(...) das zur Laufzeit rendert
  • Credential Management — den Anthropic-API-Key im OS-Credential-Store statt in einer Env-Var speichern, die config-CLI, und was Exit-Code 4 bedeutet
  • FAQ — Custom Steps verfolgen, die noch nicht automatisiert sind, plus Authoring-Agent-Troubleshooting
  • Self-Healing LocatorsCore.xpath, Multi-User-Schreibkoordination und BYOK-Kosten-Details, die sich der Authoring-Agent teilt

📧 Fragen? Kontakt: jens.szelag@itsbusiness.ch

itsbusiness AG · Bern · Schweiz