CommunitySchreiben & Editierengithub.com

meganemura/nukadoko

Typisierte Schrittverträge für Gherkin, sodass ein Szenario bereits vor der Ausführung als falsch erkannt werden kann. Das Vokabular ist maschinenlesbar für Agenten, und jede Ausführung hinterlässt einen Beleg, der vom Tool gemessen und nicht vom Agenten gemeldet wurde.

Was ist nukadoko?

Dieses Skill beschreibt die Migration einer bestehenden Testsuite auf das nukadoko-Framework, das typisierte Schrittverträge für Gherkin-Szenarien einführt. Es richtet sich an Entwickler, die entweder mit @cucumber/cucumber arbeiten oder bereits ein eigenes DSL für typisierte Schritte verwenden. Der Prozess erfolgt strikt in zwei Phasen, um die Rückverfolgbarkeit von Fehlern zu gewährleisten: Zuerst wird nur der Import umgestellt (auf das Kompatibilitätsmodul von nukadoko), danach werden Schritte schrittweise mit Zod-Schemata typisiert. Die Migration erfolgt nie auf einmal, sondern Schritt für Schritt, wobei die Suite während des gesamten Prozesses lauffähig bleiben muss. Nukadoko unterstützt Cursor, Codex und andere agentenbasierte Systeme; die Ausführung hinterlässt einen maschinenlesbaren Beleg (Receipt), der vom Tool gemessen wird. Besondere Merkmale sind die explizite Deklaration von Mutationen, die Prüfung von Abhängigkeiten mit `from` und die Möglichkeit, einzelne Schritte isoliert mit `nuka do` auszuführen – was mit Cucumber allein nicht möglich ist.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add meganemura/nukadoko

Installed? Explore more Schreiben & Editieren skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Migration zu nukadoko

Wofür das gedacht ist

Eine bestehende Suite wird Stück für Stück auf nukadoko umgestellt – kein Neuschreiben. Der Ausgangspunkt ist unterschiedlich:

  • Eine cucumber-js-Suite – Feature-Dateien plus Kleber (Glue), die normalerweise Playwright steuert.
  • Eine Suite, die bereits als typisierte Schritte strukturiert ist – ein eigenes DSL, nicht das von Cucumber, aber nah genug an nukadokos defineStep, dass die Umstellung hauptsächlich eine Übersetzung ist.

In beiden Fällen erfolgt die Umstellung in Phasen, niemals auf einmal; der Rest dieses Skills erklärt warum und geht dann auf jeden Ausgangspunkt der Reihe nach ein.

Alles untenstehende setzt voraus, dass das Projekt bereits initialisiert ist. Falls noch nicht (keine nukadoko.config.ts), führen Sie zuerst nuka init aus.

Zwei Phasen, niemals auf einmal

Ändern Sie eine Sache, dann die nächste – niemals beides gleichzeitig. Wenn ein Schritt nach zwei gleichzeitigen Änderungen bricht, gibt es keine Möglichkeit zu sagen, welche Änderung ihn gebrochen hat: Die Ursachen sind in einem einzigen Fehler verwickelt. Die Aufteilung der Arbeit in Phasen hält jeden Fehler auf genau eine Änderung zurückführbar. Das ist der ganze Punkt – die Phasen selbst sind dem untergeordnet.

Wenn die untenstehenden Phasen nicht zu Ihrem Ausgangspunkt passen, leiten Sie Ihre eigenen zwei Phasen daraus ab. Entscheidend ist die Rückverfolgbarkeit, nicht diese speziellen Phasennamen.

Zwei Anwendungen folgen: Eine cucumber-js-Suite teilt sich auf in „Import umstellen“ und „Typisierung hinzufügen“ (unten); eine bereits als typisierte Schritte strukturierte Suite teilt sich anders auf, weiter unten beschrieben.

Ausgehend von einer cucumber-js-Suite

Dies ist der Fall, nach dem die beiden obigen Phasen benannt sind.

Phase 1 – Lauffähig machen

Ändern Sie den einen Import, den jede Glue-Datei verwendet:

// vorher: import { Given, When, Then } from "@cucumber/cucumber";
import { Given, When, Then } from "nukadoko/compat";

Die Erweiterung bestimmt, was die Erkennung liest, nicht die eigene Historie der Suite: .ts, .mts, .js, und .mjs-Glue werden alle gleich gelesen, sodass eine Suite, die immer in einfachem JavaScript war, keinen Umschrieb zu TypeScript benötigt, um diese Phase zu beginnen. Eine .cjs-Datei ist die eine Ausnahme: nukadoko ist nur ESM, daher importiert die Erkennung niemals eine; nuka check nennt sie stattdessen (step-file-unsupported-extension), anstatt sie als unerklärten undefined-step verschwinden zu lassen. Eine Umbenennung in .js hilft nur, wenn der Code der Datei selbst bereits ES-Modul-Syntax ist; wenn er immer noch require(...) aufruft, schlägt dies am selben Import aus demselben ESM-Grund fehl, und nuka check meldet dies als step-file-import-failed.

Dann führen Sie nuka check <feature> und nuka run <feature> aus und lesen Sie, was sie sagen. Beheben Sie, worauf sie hinweisen, und führen Sie sie erneut aus. Wiederholen Sie dies, bis die bestehende Suite grün ist – das ist die Abschlussbedingung von Phase 1, nichts weiter. Diese Bedingung ist, dass nuka check und nuka run grün werden, nicht dass tsc sauber typisiert; die beiden können in beide Richtungen widersprüchlich sein.

nuka steps und nuka describe bleiben während der gesamten Phase 1 nutzbar, selbst wenn einige Glue-Dateien noch nicht importiert werden können: Beide lesen Schrittdateien einzeln, sodass eine Datei, die noch nicht importiert werden kann, benannt wird (import_failures bei --json, sonst stderr), anstatt das gesamte Vokabular zu leeren. Lesen Sie das Vokabular während der Arbeit, anstatt zu warten, bis jede Datei sauber importiert wird; nuka run <feature> und nuka do <step> verweigern die Ausführung bei einer unlesbaren Glue-Datei weiterhin direkt, absichtlich, da sie ausgeführt werden sollen.

Suchen Sie nicht nach einer Liste dessen, was compat nicht unterstützt, bevor Sie beginnen. Was auch immer nicht funktioniert, scheitert lautstark, entweder beim Import oder beim ersten nuka run – der Fehler nennt, was gebrochen ist, und das ist, worauf Sie reagieren. Eine hier geschriebene Liste würde veralten, sobald die Abdeckung von compat wächst; die Ausgabe des CLI selbst tut das nie.

Phase 2 – Verträge vergeben

Sobald die Suite läuft, lohnt es sich, einige Schritte zu typisieren. Dies ändert sich, wenn einer typisiert wird:

compat-Schritttypisierter Schritt
EingabeNur Muster-Erfassung, ungeprüftValidiert gegen ein args-Schema, jedes Feld trägt ein .describe()
AusgabeVerworfen – das Receipts result ist nullValidiert gegen ein returns-Schema und im Receipt gespeichert
AbhängigkeitenSeiteneffekte auf dem World, unsichtbar in der FunktionssignaturDeklariert mit from, benannt in einem Import, von nuka check geprüft, bevor etwas läuft, und als used im Receipt aufgezeichnet
SchlüsselwortDekorativ – ein an Then gebundener Schritt kann trotzdem mutierenmutates ist eine Deklaration, der nukadoko vertraut: mutates: true deklarieren und eine schreibgeschützte Umgebung weigert sich, ihn auszuführen, und nuka check warnt, wenn er an Then gebunden ist; was tatsächlich lief, wird trotzdem in den observed-Zählungen des Receipts aufgezeichnet
Allein ausführenNicht möglich (das World ist außerhalb eines Szenarios leer)nuka do <step> führt ihn direkt aus, Receipt wird auf stdout ausgegeben; ein from-Schlüssel kommt von --args wie jeder andere, oder von --use <receipt-id> für einen aus einer früheren Ausführung bezogenen; ein resultOf-Fixture-Aufruf innerhalb von run findet trotzdem nichts, da es kein Szenario gibt, durch das er gehen kann

Die letzte Zeile ist eine separate Tatsache von der Zeile „Abhängigkeiten“ darüber, keine Folge davon: Ein compat-Schritt kann nicht allein ausgeführt werden, weil das, was er benötigt, auf einem World lebt, das noch niemand bevölkert hat, und das World ist nicht Teil seiner Signatur – es gibt nichts zu inspizieren, um zu wissen, was zuerst eingerichtet werden muss. Die Abhängigkeiten eines typisierten Schritts werden als from-Einträge benannt, in einem Import sichtbar, sodass ein nuka do-Aufruf sie von Hand bereitstellen kann: --args für einen normalen Schlüssel, oder --use <receipt-id> für einen aus dem Ergebnis einer früheren Ausführung – der Name des vorgelagerten Schritts muss nie in der Befehlszeile erscheinen, da das zitierte Receipt ihn bereits trägt. Ein Schritt, bei dem jeder Schlüssel auf diese Weise ankommt, benötigt überhaupt kein --args: --use allein ist ein vollständiger Aufruf. Was außerhalb eines Szenarios immer noch nichts findet, ist eine über das resultOf-Fixture von innerhalb von run gelesene Abhängigkeit: Dieser Aufruf hat keine Kette, die er durchlaufen kann, wenn es kein Szenario gab, um eine zu bauen, unabhängig von --use.

Fördern Sie zuerst die Schritte, die Sie am häufigsten ausführen, einen nach dem anderen – nicht die gesamte Suite in einem Durchgang. Wie ein bestimmtes Stück Glue umgeschrieben wird, ist eine Ermessensentscheidung im Moment; nukadoko schreibt kein Rezept vor, und dieses Skill wird es auch nicht tun.

Beim Verfassen eines typisierten Schritts run landet man manchmal auf der nicht migrierten Form run(ctx, args), bevor sie vollständig destructured ist. nuka steps --json nennt bereits, welche Fixtures dieser Entwurf berührt, als needs_inferred: eine lexikalische Schätzung, gelesen aus den Member-Zugriffen von ctx selbst, zusammen mit needs: null und einer needs_error, die erklärt, warum der echte Vertrag noch nicht gelesen werden kann. Es erspart das manuelle erneute Lesen des gesamten Körpers, um zu wissen, was zur destructured Signatur hinzugefügt werden muss, aber es ist eine Schätzung, nicht der Vertrag: Es übersieht einen Alias (const c = ctx) und nennt nie needs_browser, also beenden Sie die Destrukturierung, anstatt sie als erledigt zu betrachten.

Wie ein geförderter Schritt aussieht

Ein Beispiel. Vorher, compat-Glue:

const { Given } = require("@cucumber/cucumber");

Given("ein Projekt {string} existiert", async function (name) {
  const res = await this.request.post("/projects", { data: { name } });
  this.projectId = (await res.json()).id;
});

Nachher, ein typisierter Schritt (features/steps/create-project.ts):

import { defineStep } from "nukadoko";
import { z } from "zod";

export default defineStep({
  pattern: "ein Projekt {name:string} existiert",
  description: "Ein Projekt erstellen und seine ID zurückgeben",
  args: z.object({
    name: z.string().describe("Der Anzeigename des Projekts"),
  }),
  returns: z.object({
    id: z.string().describe("Die ID des erstellten Projekts, für spätere Schritte zum Zitieren"),
  }),
  mutates: true,
  async run({ request }, args) {
    const res = await request.post("/projects", { data: args });
    return res.json();
  },
});

Was sich geändert hat:

  • this.projectId ist weg; die ID kommt stattdessen durch returns zurück. Ein späterer Schritt deklariert from: { projectId: [createProjectStep, "id"] }, um sie per Schlüssel zu lesen – nuka check überprüft die Bindungsreihenfolge, bevor etwas läuft, und der Lesevorgang erscheint im eigenen Receipt dieses späteren Schritts (das resultOf-Fixture bleibt für den selteneren Lesevorgang verfügbar, den ein Schlüsselname nicht ausdrücken kann).
  • Das Argument ist eine benannte Erfassung, die an einen Schemaschlüssel gebunden ist ({name:string} → args.name), sodass das Muster allein zeigt, welcher Text welches Feld wird.
  • mutates: true ist jetzt eine Deklaration, nicht nur eine Tatsache darüber, was der Code zufällig tut – nuka check warnt, wenn dieser Schritt jemals an Then gebunden ist, und eine schreibgeschützte Umgebung weigert sich, ihn überhaupt auszuführen. Das Receipt zeichnet trotzdem auf, was der Lauf tatsächlich gesendet hat, zur Überprüfung, aber diese Zählung führt nicht zur Ablehnung des Schritts.
  • this.request wird zum request-Fixture, benannt durch Destrukturierung des ersten Arguments: Nur das, was der Executor tatsächlich injiziert, kann dort benannt werden, nichts Implizites, und nur die tatsächlich benannten Namen werden erstellt. Ein Schritt, der weder page noch context nennt, startet keinen Browser.

Dies ist das einzige ausgearbeitete Beispiel hier. Es ist kein Katalog jeder Lücke zwischen compat und typisiert – wenn ein anderes Muster oft genug auftritt, um eines zu verdienen, ist das eine separate Ergänzung, nichts, was aus diesem einzelnen Fall improvisiert werden sollte.

Wenn ein globaler After-Hook die Bereinigung war

Eine cucumber-js-Suite verlässt sich oft auf einen globalen After-Hook, um aufzuräumen, was ein Schritt erstellt hat: einen Mandanten, eine gesäte Zeile, eine temporäre Datei. Es lohnt sich, dies zu überdenken, sobald der Schritt, der es erstellt hat, gefördert ist: Deklarieren Sie die Ressource stattdessen als Fixture unter nukadoko.config.ts's eigenem fixtures, mit der Bereinigung direkt nach await use(...) in derselben Funktion, die sie gebaut hat, anstatt in einem Hook, der aus Tags allein erraten muss, welche Szenarien sie tatsächlich benötigen. defineFixtures (aus dem nukadoko-Paket) hält das Fixture vollständig typisiert; jeder typisierte Schritt erreicht es auf dieselbe Weise wie page oder request, durch Destrukturierung des Namens. Dies ist additiv, nicht erforderlich: Ein After-Hook, der etwas aufräumt, das noch kein typisierter Schritt beansprucht hat, ist immer noch genau so gültig wie in Phase 1.

Ausgehend von einem DSL für typisierte Schritte

Wenn es keine Feature-Dateien und keinen Cucumber-Glue gibt, ist die compat-Tür in Phase 1 oben nicht relevant – überspringen Sie sie direkt.

Was dies möglich macht, ist, dass pattern bei defineStep optional ist: Ein Schritt kann ohne Muster definiert werden und ist trotzdem ein vollständiges Stück CLI-only-Vokabular, ausführbar mit nuka do und untersuchbar mit nuka describe. Das ist ein anderes Paar von Phasen, die aus demselben Prinzip oben entstehen: Verschieben Sie zuerst jeden Schritt zu einem typisierten defineStep und bündeln Sie ihn später in ein Gherkin-pattern, wann immer eine Feature-Datei es wert macht.

Wenn das Quell-DSL bereits so etwas wie eine description, args, returns, mutates und eine run-Funktion trägt, ist die Übersetzung zu defineStep direkt – jedes hat ein defineStep-Gegenstück, das es empfängt.

Was nicht zu tun ist

  • Führen Sie Phase 1 und Phase 2 nicht gleichzeitig durch. Das Mischen eines Importwechsels mit hinzugefügter Typisierung in derselben Änderung ist genau das, was einen Fehler unattributierbar macht – siehe „Zwei Phasen, niemals auf einmal“ oben.
  • Lassen Sie die Suite nicht aufhören zu laufen, während sie teilweise migriert ist. Compat- und typisierte Schritte koexistieren in derselben Feature-Datei; eine Suite mit einigen geförderten Schritten und anderen noch im compat-Modus muss während des gesamten Prozesses bestehen bleiben. Machen Sie „vollständig typisiert“ niemals zu einer Vorbedingung für „läuft“.
  • Löschen Sie compat-Glue nicht, bevor sein typisierter Ersatz geschrieben ist und läuft. Schreiben Sie den neuen Schritt, bringen Sie ihn mit nuka do zum Laufen, erst dann entfernen Sie den alten – niemals in der umgekehrten Reihenfolge.
  • Raten Sie keine Reparatur, die nuka check oder nuka run nicht angefordert haben. Deren Ausgabe ist der Beweis dafür, was falsch ist; etwas zu ändern, das sie nicht markiert haben, ist eine Änderung auf eine Ahnung hin, nicht auf das, was tatsächlich gebrochen ist.

Verwandte Skills