Quickstart with Claude Code
Claude Code drafts the decision table, Nanook generates the data. One form, one script, real test
data at the end. Every step on this page was run as written on 2 September 2026 with
@xhubio/nanook-table 3.0.1, skill version 0.1.0 and Claude Code 2.1.258; the numbers
are from that run.
What you need
- Node.js 22 or newer.
- Claude Code, installed and signed in.
- A project directory. You do not need a clone of the nanook-table repository.
1 · Install Nanook and the skill
The skill and the slash command have shipped inside the npm package since version 2.1.0. Claude
Code reads skills from your project’s .claude folder (or from
~/.claude/skills for all your projects), not from node_modules, so copy
them over after the install:
npm init -y
npm pkg set type=module
npm install @xhubio/nanook-table
npm install -D exceljs
mkdir -p .claude/skills .claude/commands
cp -r node_modules/@xhubio/nanook-table/.claude/skills/create-equivalence-class-table \
.claude/skills/
cp node_modules/@xhubio/nanook-table/.claude/commands/createEquivalenceClassTable.md \
.claude/commands/
exceljs is what the generated script uses to write a formatted workbook with fills and
formulas. It is not a dependency of Nanook itself, so install it once. Two things to know before you
start: the skill text is written in German. Claude reads it either way, but in the run behind this page
the table’s comments and expected results came out German although the prompt was English;
ask for English explicitly if you want it. And the skill assumes a scripts/ and a
resources/ folder; if you want the files elsewhere, say so in the prompt.
2 · Ask for a table
Start Claude Code in the project and run the command with a name or a one-line description of what
you want to test. The command is a thin wrapper around the skill, which can also be invoked directly as
/create-equivalence-class-table; the run behind this page used the command.
claude
/createEquivalenceClassTable Login Form
The skill then works through its steps: analyse the test object, group its fields into one or more
tables, define equivalence classes per field, plan one test case per invalid class plus one happy
path so that the coverage lands on 100 % (the CASCADE pattern), then write and run a TypeScript
script that produces the workbook with exceljs, and verify the result through
Nanook’s ImporterXlsx.
The mechanics are described in AI-Assisted
Equivalence Class Tables with Claude Code.
In the run behind this page, /createEquivalenceClassTable Login Form with no further
input took 17.5 minutes and 56 turns and produced four files. resources/login-form-tests.xlsx
is the workbook. scripts/create-login-form-table.ts builds it with exceljs
and refuses to write a sheet whose coverage is not 100 %. scripts/check-login-form-table.ts
reads the markers back out of the file and recounts, independently of the builder. And
scripts/generate-login-form-fixtures.ts runs Nanook over the workbook and writes one
JSON fixture per test case.
The workbook has two sheets, following the skill’s split into a data table and a test-case
table. User (Execute = F) holds the field email with five classes (valid, empty,
whitespace only, invalid format, too long) and password with three (valid, empty, too long);
5 × 3 gives the 15 combinations. Login
(Execute = T) defines no classes for the form fields itself; it names the base state (logged
out, an existing user, a verified address) and the input, and pulls the email and password classes
in from User by reference. The four
invalid emails arrive as one range reference, ref::User:email:[email_invalid_1-4].
| Sheet | Columns | Combinations | Coverage |
|---|---|---|---|
| User | 7 | 15 | 100 % |
| Login | 7 | 48 | 100 % |
One decision Claude took on its own and reported: the empty, whitespace-only and too-long classes use
a small generator Claude wrote itself (gen::text:empty, gen::text:spaces:3,
gen::text:email:250, gen::text:alpha:200) instead of empty cells or Faker,
because the importer trims cells, a reference to a class without a generator never resolves, and the
built-in Faker generator takes no arguments. The generator is about twenty lines in the fixture
script. And one thing it did not report: the comments and expected results are German, because the
skill is.
The more you say, the less Claude guesses. A bare “Login Form” got Claude’s idea of
a login form, with the assumptions listed at the end of its report: 254 characters for the email,
128 for the password, one INVALID_CREDENTIALS for an unknown address and a wrong
password alike. Name your fields, limits and error codes in the prompt and those assumptions become
yours. Open the workbook in a
spreadsheet before you go on: the fills mark the sections, the formulas count the markers per
field, and the summary row shows the coverage. The full table from a comparable run, column by
column, is in the login example.
3 · Generate the test data
Claude wrote its own generation script, and it registers whatever generators the table uses. Run that first:
node scripts/generate-login-form-fixtures.ts
In the run behind this page it wrote 11 fixtures to fixtures/login-form/: the seven
columns of Login, with the two range references expanded into four and two cases. Node.js
22.18 or newer runs .ts files directly; older 22.x needs
--experimental-strip-types, and npx tsx works everywhere.
If you would rather have one script for every table, the one from the 5 minute Quickstart works too. Save it as
generate.mts, point it at the workbook, and register the generators the table calls
for; this workbook needs text next to faker. The tables are handed to the
processor keyed by name, which is what lets a reference find the other sheet:
import {
LoggerMemory, FileProcessor, ImporterXlsx,
ParserDecision, DataGeneratorRegistry, GeneratorFaker,
TestcaseProcessor, type InterfaceWriter
} from '@xhubio/nanook-table'
const logger = new LoggerMemory()
logger.writeConsole = true
const fileProcessor = new FileProcessor({ logger })
fileProcessor.registerImporter(
'xlsx',
new ImporterXlsx()
)
fileProcessor.registerParser(
'<DECISION_TABLE>',
new ParserDecision({ logger })
)
await fileProcessor.load(['resources/login-form-tests.xlsx'])
const registry = new DataGeneratorRegistry()
registry.registerGenerator(
'faker',
new GeneratorFaker({ logger })
)
// plus the 'text' generator from
// scripts/generate-login-form-fixtures.ts
const collected: unknown[] = []
const writer: InterfaceWriter = {
logger,
async before() {},
async write(tc) {
collected.push(JSON.parse(JSON.stringify(tc)))
},
async after() {},
}
const tables = Object.fromEntries(
fileProcessor.tables.map((t) => [t.tableName, t])
)
const processor = new TestcaseProcessor({
logger,
tables,
generatorRegistry: registry,
writer: [writer],
})
await processor.process()
console.log(collected.length, 'test cases')
console.log(JSON.stringify(collected[0], null, 2))
With the Faker generator alone, this script reported 5 test cases on the run’s workbook and two
errors, There was no generator registered with the name 'text'. With Claude’s
text generator registered as well, it reported 11, with no errors and no warnings.
Check the number. The table has one column per test case, and a range reference adds one case per extra element: 7 columns and two ranges make 11 here. The script must report exactly that number. Fewer means a generator failed on the way: Nanook logs the error and keeps going, and the missing case is easy to overlook. The login example shows the most common cause and the ten-line fix.
What can go wrong
- Fewer cases than columns. A Faker directive with an argument, such as
gen:1:faker:string.alpha:255, fails because the built-in generator takes a Faker path and nothing else. Write a small generator that extendsDataGeneratorBaseand register it under its own name; see Create data generator. Cannot find module 'exceljs'. The generated script needs it in your project:npm install -D exceljs.- Files land in
scripts/andresources/. That is the skill’s default. Name the folders you want in the prompt, or move the files and change the path ingenerate.mts. Method not implementedfrom the default writer. In 3.0.1 the writer returned bycreateDefaultWriterthrows inbefore(). Use an inline writer as above, or your own class.The targetTable 'User' does not exists. You handedfileProcessor.tables, an array in 3.0.1, toTestcaseProcessor. Everyref:then fails with this message and fewer cases come out, 7 instead of 11 in the run. Pass the tables keyed by name, as the script above does.
No terminal?
According to the Claude Code documentation, the desktop app and claude.ai/code read project skills from the same
.claude folder, so a tester could ask for the table there and hand the workbook to
whoever runs the generation. We have not run this page’s steps there, and generating the data
still needs Node.js.
Where to go next
- The login example: the full table, the generated data, and what the skill got wrong.
- AI-Assisted Equivalence Class Tables: how the skill works and what CASCADE coverage is.
- Create an equivalence class table from scratch: the markers by hand, for when you edit what Claude drafted.
- Equivalence class tables in the guide, and the
directives
reference in the repository for
gen:andref:. - Testing a SaaS with Nanook and Writing Tests Got Cheap: what this looks like at 117 tables.
Run record: 2 September 2026, Node.js 24.16.0, @xhubio/nanook-table 3.0.1 with skill version 0.1.0, Claude Code 2.1.258 in headless mode, 56 turns, 17.5 minutes. The workbook, the three scripts and a fixture are kept with the site’s sources.