Open source test management, without the database.
Every other open source TMS in the top ten search results, Kiwi TCMS, TestLink, SquashTM, Tuleap, QaraTMS, unittcms, is a Django or PHP web app with a users table and a projects schema. Assrt is one npm package that writes a Markdown file to /tmp/assrt/scenario.md and uses the scenario UUID v4 as its only access token.
The shape, in one sentence
An open source TMS where test case equals file, project equals folder, and auth equals UUID.
No web tier to deploy. No database to back up. No login screen to keep current with the password rotation policy. The whole system is a CLI plus a directory.
What the other open source TMS pages all assume
Read any of the BrowserStack, Aqua Cloud, Testomat, or CTO Club roundups and you will see the same shape repeated for every entry. A web app you self-host. A relational database. A users table with roles. A projects table with sections. Custom fields on the test case. A REST API gated by a token you provision in a settings page. A dashboard that reports on the database. The unit of testing is the row, the unit of organization is the foreign key, and the unit of access control is the user.
That shape is fine. It also has a fixed cost: someone has to keep the database up, keep the SSO integration working, keep the user list pruned when people leave the org, and keep the ORM migrations applied across upgrades. For a five-person team that just wants to grep their tests and re-run them on a pull request, every line of that overhead is a tax. Assrt removes the tax by removing the database.
The anchor fact: a single comment in the source
If you want to know how Assrt skips the entire users-and-permissions problem in one move, it is documented in eight lines at the top of assrt-mcp/src/core/scenario-store.ts. This is the comment, verbatim.
Read the last line again. No auth required: the UUID v4 IS the access token. That sentence is the whole architecture. The path of every read is /api/public/scenarios/<uuid> and the path of every write is the same with PATCH or POST. There is no /private/ sibling, no token to provision, no cookie to expire. A v4 UUID is 122 bits of entropy. Anyone who knows it can read or update the scenario. Anyone who does not know it cannot.
What replaces the database tables
Every concept a Kiwi TCMS or TestLink schema would store as a row has a one-to-one mapping to something on disk in the Assrt layout. The translation, side by side.
Test case
A #Case heading inside /tmp/assrt/scenario.md. The format is pinned by PLAN_SYSTEM_PROMPT in server.ts: 3 to 5 imperative steps per case.
Project / suite
A directory you choose. Put scenario.md files anywhere on disk and pass them with --plan-file. The TMS is wherever your folders are.
Custom fields
tags, variables, and passCriteria fields on the StoredScenario type in scenario-store.ts. Pass them through MCP or the CLI; they round-trip through the JSON metadata.
Run history
/tmp/assrt/results/<runId>.json for each run, plus latest.json for the most recent. Diff two runIds the same way you diff git commits.
Users / permissions
Removed. The UUID v4 in the URL is the only credential. Share the link, you share the test. Rotate the link, you revoke access.
Web UI
Your text editor. fs.watch picks up edits within 1 second and PATCHes them to the central API automatically. VS Code is the dashboard.
API token provisioning
Removed. The path is /api/public/, not /api/private/. No settings page, no key rotation policy, no leaked-token incidents.
The actual on-disk layout
Here is what a single working session looks like inside /tmp/assrt. Every file is human-readable. None of it requires a tool to view.
The companion JSON sidecar is the entire metadata layer that a traditional TMS would store across half a dozen tables.
Run histories accumulate in /tmp/assrt/results/<runId>.json. They are never overwritten. Diff the latest two and you have the same trend report a TestRail dashboard would show, except it is two jq commands instead of a web view.
How the file becomes the management layer
The reason this design works without a web UI is that fs.watch is doing the job a CRUD page would do. When the file changes, a one-second debounce fires and the new content gets PATCHed to the central capability URL. Edit the Markdown in any editor and the shared scenario updates without you touching a dashboard.
One file in the middle, everything else around it
Three things happen because of this watcher. A human in VS Code can tighten a step and the next test run picks it up. A second agent on a different machine that knows the UUID can fetch the latest plan, modify it, and PATCH it back. CI can read /tmp/assrt/results/latest.json after a run and surface the report wherever it pleases. The file is the API.
Assrt vs. the conventional open source TMS
The honest comparison. Both ship under permissive licenses. The difference is what you operate after the install.
| Feature | Kiwi TCMS / TestLink / SquashTM | Assrt |
|---|---|---|
| What you install | Self-hosted web app + Postgres/MySQL + worker tier | One npm package: npx assrt-mcp |
| Where the test case lives | A row in a database | A Markdown file at /tmp/assrt/scenario.md |
| Access control | Users, roles, project memberships, RBAC tables | UUID v4 in the URL is the access token; no users table |
| Web UI | Required; the only way to author and run tests | Optional; your text editor edits the file directly |
| Test runner | External; you wire it up to JUnit, Playwright, etc. | Built-in: real Playwright via @playwright/mcp |
| Run history | executions table with foreign keys and reports | /tmp/assrt/results/<runId>.json files; never overwritten |
| Auth-and-SSO maintenance burden | Real and ongoing; SSO integrations age | None to maintain; capability URLs do not expire |
| Backup strategy | Database dumps + file uploads | git on the directory containing your .md files |
| AI-generated test cases | Optional add-on if vendor supports it | Native: assrt_plan generates 5-8 #Cases per page |
Try it without giving up your existing TMS
npx assrt-mcp drops the server into your coding agent in under a minute. Generate a few cases, let them live in /tmp/assrt/scenario.md, and diff that file against whatever you would have authored in TestRail.
Install from npm →What this looks like at the terminal
A single end-to-end loop, from install to reading the run history. Every line below is a real command you can type today.
The numbers, all from the source
Not invented. Each one points at a constant or a comment in assrt-mcp on disk.
And one more
0 tables in the schema. There is no schema. The "database" is a folder and a JSON sidecar.
Migrating off a Kiwi TCMS or TestLink instance
You do not have to rip the existing tool out. The path that works in practice is to use Assrt for new tests on new features and let the existing suite age in place. The migration story, step by step.
From a relational TMS to a folder of Markdown
Install the MCP server
npx assrt-mcp inside any project. The server registers with your coding agent (Claude Code, Cursor, etc.) over stdio. No service to run, no port to open.
Generate the first scenario
Point the agent at a local URL with assrt_plan. The plan is written to /tmp/assrt/scenario.md and a UUID is issued.
Move the file into your repo
cp /tmp/assrt/scenario.md tests/regression/checkout.md. From now on the file is versioned with your code. The agent can still edit it; fs.watch and PATCH still work.
Wire it into CI
In CI: npx assrt run --plan-file tests/regression/checkout.md --json. The exit code reflects pass/fail. Results land at /tmp/assrt/results/latest.json for the next CI step to consume.
Decommission the database
Once enough scenarios live as Markdown files in your repo, the Kiwi or TestLink instance no longer needs to be highly available. Eventually it gets archived. Nothing in your test suite breaks.
What you give up, and why it is fine
Capability URLs are not a perfect substitute for RBAC. A user who had a UUID and then left the company still has the UUID. The answer in this model is to rotate the scenario: generate a new scenarioId for the test, share the new UUID with the people who should still have it, and let the old UUID become a key with nothing on the other end.
You also give up the central reporting view a TestRail dashboard would give you. The substitute is jq and git log against the directory of run JSON files. For a small team this is usually a wash. For a large QA org with a dedicated reporting need, this is a real gap and you would keep the heavyweight TMS for that purpose alone.
The trade is intentional. We removed the database to remove the class of problems that comes with operating a database. If you need the database back, the file format is plain enough to import into one in an afternoon.
Questions about open source test management with Assrt
Is Assrt actually a test management tool, or is it just a test runner?
Both, in a different shape. A traditional TMS like Kiwi TCMS, TestLink, or SquashTM gives you a database of test cases, a project hierarchy, a run history, and a web UI. Assrt gives you the same four things, but the database is the filesystem (/tmp/assrt/scenario.md for the plan, scenario.json for metadata, results/<runId>.json for every historical run), the project hierarchy is the tags and variables fields on the StoredScenario type in scenario-store.ts, the run history is whatever JSON files have piled up in /tmp/assrt/results/, and the web UI is your text editor. Anything you can do in TestRail you can do here with grep and a one-line MCP call.
What does the comment 'the UUID v4 IS the access token' actually mean in practice?
It means that to fetch a scenario from the central API you do GET /api/public/scenarios/<uuid> and there is no Authorization header. The path is literally /api/public/, not /api/private/, because the secret is the UUID itself. A v4 UUID has 122 bits of entropy, which is more than a typical bearer token, and the only people who know it are the agent that created it and whoever they pasted it to. This pattern is sometimes called a capability URL. The trade-off: anyone with the link can read the scenario, just like a Google Doc share link. The benefit: there is no users table, no signup flow, and no row-level permission checking to maintain.
Where does the file actually live, and what happens if I delete it?
The plan lives at /tmp/assrt/scenario.md. Metadata at /tmp/assrt/scenario.json. Run history in /tmp/assrt/results/<runId>.json. A local cache mirror lives at ~/.assrt/scenarios/<uuid>.json (the cache survives reboots; /tmp does not on most macOS configs). If you delete /tmp/assrt/scenario.md, the next assrt_test call with a scenarioId will fetch the plan from the central API, write it back to /tmp/assrt/scenario.md, and start a new fs.watch handler on it. Delete the local cache too and you still recover from the API. Pull the network and the local cache is the source of truth.
How is this different from running Kiwi TCMS or TestLink in Docker?
Kiwi and TestLink give you a self-hosted web app with a Postgres or MySQL database, a Django or PHP web tier, a Celery or PHP-FPM worker tier, and a login system. The smallest viable deployment is multiple containers and a database backup strategy. Assrt is a single npm package (npx assrt-mcp) that writes files to disk. There is nothing to deploy. The closest equivalent in shape is git: the test cases are files, the history is the run-id JSON files, the merging strategy is whatever your text editor does. You do not log into git; you do not log into Assrt.
Can I check the test cases into my own repo instead of relying on /tmp?
Yes. Copy /tmp/assrt/scenario.md anywhere you want, including into your repo at tests/regression.md. The PLAN_SYSTEM_PROMPT in assrt-mcp/src/mcp/server.ts (line 219) pins the format to #Case headers with 3-5 imperative steps each, so the file is grep-friendly and diff-friendly forever. When you run assrt_test from CI you can pass plan: readFileSync('tests/regression.md') instead of a scenarioId and skip the central API entirely. The capability URL is one of two storage modes; the plain file is the other.
What about role-based access, audit logs, projects, sections, all the things a real TMS has?
Assrt does not have those, on purpose. RBAC presumes a users table; capability URLs sidestep the question by giving every scenario its own un-guessable URL. Projects and sections are folders on your disk if you want them: tests/checkout/scenario-001.md, tests/auth/scenario-002.md. Audit log is git log on the directory those files live in. The constraint we accept is that there is no central place to ask 'who edited this?' across an org; the trade is that there is no central place that can go down and lock you out either.
Is the test runner part open source too, or just the management layer?
Both. assrt-mcp on npm is the MCP server; the source is browsable on GitHub. The runner is real Playwright via @playwright/mcp, the same MCP server Microsoft maintains. The agent loop and the file watcher are in src/core/agent.ts and src/core/scenario-files.ts. The capability URL store is src/core/scenario-store.ts. There is no closed binary anywhere in the chain. The only optional cloud piece is the Firestore-backed central API at app.assrt.ai, and the local cache fallback in scenario-store.ts means you can ignore it if you want.
Why Markdown for the test case format and not YAML or JSON like everyone else?
Three reasons. First, Markdown is what you would write in a Slack message to a teammate to describe a test, so the friction to author one is zero. Second, the file is a prompt: the AI agent reads it as instructions and executes whatever its Playwright vocabulary supports. YAML would be a schema the agent has to compile; Markdown skips the compile step. Third, diffs are readable. A Markdown #Case file shows up in a pull request and a non-engineer can review it. A YAML test artifact requires the reviewer to know the schema.
Stop running a database for your tests
Your TMS is already on disk. You just have not pointed at it yet.
One npx assrt-mcp and you have a Markdown-backed test management layer with no login screen.
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.