Decision tables
Equivalence class tables: fields, their valid and invalid classes, and one column per test case marking which class applies. The table is both the specification and the test plan.
Nanook
Combine the power of equivalence class tables and data generators.
npm install @xhubio/nanook-table
# Node.js >= 22. One ESM package, types included.
# Everything is imported from that one module:
# TestcaseProcessor, FileProcessor, ImporterXlsx,
# ParserDecision, ParserMatrix, ParserSpecification,
# DataGeneratorRegistry, DataGeneratorBase,
# GeneratorFaker, LoggerMemory, TableDecision,
# TableMatrix, …
From the README of @xhubio/nanook-table 3.0.1.
$ claude
> /createEquivalenceClassTable Login Form
17.5 minutes, 56 turns, no further input
resources/login-form-tests.xlsx
User 7 columns, 15 combinations, 100 %
Login 7 columns, 48 combinations, 100 %
scripts/create-login-form-table.ts
scripts/check-login-form-table.ts
scripts/generate-login-form-fixtures.ts
$ node scripts/generate-login-form-fixtures.ts
11 fixtures written to fixtures/login-form
Summary of the run behind the Quickstart with Claude Code (2 September 2026). The skill has shipped with the package since 2.1.0.
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.xlsx'])
const registry = new DataGeneratorRegistry()
registry.registerGenerator(
'faker',
new GeneratorFaker({ logger })
)
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(JSON.stringify(collected[0], null, 2))
Adapted from the script that generated the login example for the blog: package
imports instead of source imports and without its custom len generator, otherwise the
same calls. Runs on @xhubio/nanook-table 3.0.1.
{
"tableName": "Login",
"name": "OK_login",
"data": {
"Login": {
"4679e9a0-6d2d-4fea-8351-9677ed8573b6": {
"account": "active",
"Expected reaction": [
{
"key": "signed in",
"comment": "session cookie is set"
}
],
"Expected effect": [
{
"key": "failed-attempt counter unchanged",
"comment": "success or unknown user"
}
],
"email": "Jennifer_Tremblay@hotmail.com",
"password": "VuZGJBushpsbY_b"
}
}
},
"instanceId": "4679e9a0-6d2d-4fea-8351-9677ed8573b6",
"callTree": {
"instanceId": "218e4109-1b41-4269-8cac-d10c92551e39",
"tableName": "Login",
"testcaseName": "OK_login",
"neverExecute": false,
"tags": [],
"children": []
},
"postProcessDirectives": []
}
What the script prints: the first test case of that run, e-mail and password from the Faker generator.
Nanook's default writer would store each one as tdg/<name>/testcaseData.json.
Nanook is an open-source toolkit for defining test cases in spreadsheets and generating test data from them. You describe test scenarios as equivalence class tables, matrix tables or specification tables in standard XLSX files; Nanook reads those sheets, runs every test case through pluggable data generators and hands the result to one or more writers — JSON files by default, or any format you implement. Version 3 is a single ESM package written in TypeScript, with type declarations, for Node.js 22 or newer. Generation is a plain script, so it runs in any CI job. Nanook is used by enterprises including Deutsche Bahn for automated test data generation.
Three table types and one processing pipeline. Each card opens the matching guide.
Equivalence class tables: fields, their valid and invalid classes, and one column per test case marking which class applies. The table is both the specification and the test plan.
Matrix tables describe pairwise or combinatorial relationships between two dimensions of test parameters. Specification tables declare field rules and severities and are converted into a decision table automatically.
A cell directive such as gen:1:faker:internet.email calls the built-in Faker generator or
your own; a shared instance ID keeps related fields on one record. Every test case goes to the registered
writers — JSON by default, anything with a custom writer. Tag-based filters choose which test cases are
processed.
Three steps from a spreadsheet to generated test data.
Write the test cases as a decision table in any editor that saves XLSX — Excel,
LibreOffice Calc, or Google Sheets via download. The sheet starts with the marker
<DECISION_TABLE>, lists the fields with their equivalence classes and has one column per
test case. Matrix and specification tables live in the same workbook. No code at this stage; the Claude
Code skill shipped with the package can draft the table for you; see the Quickstart with Claude Code.
Put a directive into the generator column of each equivalence class: a static value,
gen:<instanceId>:<generator>:<parameter> to call a generator, or
ref: to reuse a value from another test case. The built-in Faker generator covers names,
e-mail addresses, dates and more; a custom generator is a TypeScript class that extends
DataGeneratorBase and overrides doGenerate(). The same instance ID across fields
yields one coherent record.
Run the script: the file processor loads the workbook, TestcaseProcessor resolves
references, calls the generators, applies tag filters and passes every test case to the registered
writers. The
default writer stores one JSON file per test case under tdg/<name>/testcaseData.json;
a custom writer implements before(), write() and after() and can
produce CSV, database rows or API calls. It is a plain Node.js script, so it runs in any CI job.
Eight scenarios where one table replaces hand-written fixtures.
Generate request payloads for every valid and invalid combination of an endpoint's fields from one decision table. A specification table turns field rules — type, mandatory, min, max, format — into those classes automatically.
Registration, login, checkout. Every form has fields with validation rules. One decision table yields test data for every valid and invalid combination, without hand-written fixtures.
Regenerate test data on every spec change. Nanook is an ESM package for Node.js 22 or newer; the generation script runs in any pipeline job — GitHub Actions, GitLab CI, Jenkins, or a cron job.
Stop copying production data into test environments. Generated records match your schema, and the Faker generator produces names, addresses and e-mail addresses that belong to nobody.
Decision tables as the single source of a Playwright suite: in our own SaaS, 16 hand-written spec files became 117 decision tables and 1,981 data-driven cases. Read the field report.
Data that flows through several services stays consistent: ref: directives pull values from
test cases in other tables, and shared instance IDs keep one generated record coherent across fields.
Bootstrap dev and staging environments from one spreadsheet. A custom writer can insert the generated records straight into a database, and a persistent generator store keeps generated values across runs.
Test database migrations with generated data that matches your schema. Change the table when the schema changes and regenerate — no production data involved.
Not sure if Nanook fits your scenario? Open an issue on GitHub or reach out at opensource@xhub.io.
A spreadsheet that lists the input fields of a system, the classes of values each field
can take — valid and invalid — and one column per test case marking which class applies. Nanook calls it a
decision table; the sheet starts with the marker <DECISION_TABLE>.
XLSX. The default file processor registers the XLSX importer for the extensions .xlsx and .xls; there is no ODS or Google Sheets importer. Use any editor that saves XLSX — Excel, LibreOffice Calc, or Google Sheets via download — or register your own importer for another format.
By default one JSON file per test case, written to
tdg/<name>/testcaseData.json. Output is produced by writers: implement
before(), write() and after() of InterfaceWriter to
produce CSV, database rows, API calls or any other format. Several writers can run in one pass.
Nanook 3 is written in TypeScript and ships as one ESM package with type declarations.
It requires Node.js 22 or newer. Version 2.0 (March 2026) was the rewrite from JavaScript; the 1.x names
Processor and TDGServiceRegistry no longer exist — the entry point is
TestcaseProcessor.
Yes. Generation is a Node.js script without service dependencies, so it runs in any pipeline job. Regenerate the test data whenever the tables change.
Yes. Since version 2.1.0 the repository ships a Claude Code skill
(create-equivalence-class-table) and a slash command
(/createEquivalenceClassTable) that draft a formatted decision table for a page, form or
endpoint. The blog post AI-Assisted
Equivalence Class Tables with Claude Code shows a run. The skill also ships inside the npm package: copy
it into your project’s .claude folder and follow the Quickstart with Claude Code.
Yes, Nanook is open source and released under the MIT License. It is free for both personal and commercial use.