SmartTest

Case study: LeadsIt

LeadsIt was the first real repository migrated to SmartTest (commit d310fbac). It's a good test because it isn't a tidy fixture:

Rule of the exercise #

Only built-in commands. npm install, npx smarttest init --yes, smarttest run --staged in the hook, smarttest benchmark. Whenever SmartTest needed a custom flag or a hand-written config to work, that counted as a SmartTest bug, and it was fixed in SmartTest.

What it forced into the product (0.2.0) #

Problem found on LeadsItFix in SmartTest
No runners found: no root manifest, no workspace configNested projects are discovered without a workspace manifest
vitest.server.config.ts ignored, and the config wasn't passed to the runnerCustom config names and --config in scripts are detected and passed through
node --test not supportedNew @smarttest/adapter-node (JUnit reporter); directory arguments follow Node's semantics exactly
Test-looking files the runner doesn't collect were countedRunner-accurate discovery: each runner lists what it collects (cached)
.wasp/ build output counted as 20 projects / 1,900 modulesFile listing respects .gitignore through git ls-files, and submodules are skipped
Redis suites excluded only by a hook flaginit reads hooks, scripts and CI for --testPathIgnorePatterns / --exclude and carries them over
Two tests ran on every commit (computed import() paths)Static evaluation of constants, new URL(…, import.meta.url), resolve(__dirname…), templates → exact files or bounded globs
SAFE mode fell back to the full suite on unrelated uncertaintySAFE selects uncertain tests individually; full suite only when the change analysis itself is incomplete
SmartTest's own .smarttest/ state showed up as changesOwn state is never a change
.env.*.example files were mandatory triggersOnly real env files trigger
Two auth/ folders → duplicate rule names (invalid config)Unique suggested rule names
No way to prove savings on real historysmarttest benchmark --commits N

Result: parity #

npm run test:all through SmartTest: 706 passed, 0 failed. That's identical to the native runners: Jest 177 + Vitest 489 + MCP 30 + outbound 10, with the Redis suites skipped as before.

Result: history benchmark #

npx smarttest benchmark --commits 12 replays the last 12 commits in a temporary worktree. For each one it compares a full run, a project-gated run (the suites of every touched project, which is what the old hook did) and SmartTest. Every failure that wasn't selected is re-checked on the parent commit.

Full runProject-gatedSmartTest
Total time110.3 s37.2 s13.8 s
Saved by SmartTest88%63%—
Tests run1192—9

Missed failures: 0 · failures already present in the parent commit: 0

CommitChangedSelectedFullGatedSmartTestMissed
ebd4ef85 fix(outbound): a Composio error after Gmail had se21/997.9 s382 ms3.5 s0
56156435 feat(outbound): every cold email thread is filed i41/998.5 s477 ms793 ms0
47606fc6 fix(outbound): Gmail replies thread for the prospe21/998.4 s454 ms1.0 s0
1cfffe0a feat(outbound): samples a web agency can use as de31/999.5 s453 ms957 ms0
a1b6112f feat(seo): titles for the searches Search Console270/997.7 s4.6 s356 ms0
94d41f82 fix(admin): the owner's own purchases are not reve31/997.0 s4.0 s953 ms0
d4338904 fix(ads-monitor): the owner's own test purchases a10/997.8 s0 ms203 ms0
8c3f094e fix(admin): melbdu93@live.fr's January payments (221/9912.5 s5.9 s1.9 s0
0da20f4a feat(admin): Cold outreach (owner, 2026-10-29): ev212/10016.6 s10.5 s2.8 s0
88bdcd48 fix(admin/outreach): every stage at a glance above20/1009.3 s5.7 s423 ms0
e6fbfdee docs(outbound): admin → Outreach and sync_admin.mj10/1008.3 s375 ms189 ms0
42c7810f feat(outbound): a check-in 4 days after a free lis71/1006.9 s4.3 s631 ms0

Times are wall-clock on this machine. SmartTest time includes its analysis overhead.

Update: 0.5.0 #

LeadsIt now vendors SmartTest 0.5.0 (tools/smarttest/smarttest-0.5.0.tgz). Parity is unchanged: npm run test:all passes 706 tests. smarttest trace records all 100 test files (Jest, Vitest and both node --test projects) in 23 seconds.

The benchmark window moved by two commits: the last two are the SmartTest migration itself, which change the root package.json and lockfile and therefore run everything.

CommitChangedStaticWith tracesMissed
47606fc6 fix(outbound): Gmail replies thread for the p21/990/990
1cfffe0a feat(outbound): samples a web agency can use31/991/990
a1b6112f feat(seo): titles for the searches Search Con270/990/990
94d41f82 fix(admin): the owner's own purchases are not31/991/990
d4338904 fix(ads-monitor): the owner's own test purcha10/990/990
8c3f094e fix(admin): melbdu93@live.fr's January paymen21/991/990
0da20f4a feat(admin): Cold outreach214/1004/1000
88bdcd48 fix(admin/outreach): every stage at a glance20/1000/1000
e6fbfdee docs(outbound): admin → Outreach and sync_adm10/1000/1000
42c7810f feat(outbound): a check-in 4 days after a fre73/1003/1000
d310fbac chore(tests): run only the tests a commit aff9100/100100/1000
f25b41e6 chore(tests): update vendored SmartTest 0.2.02100/100100/1000

What changed compared with 0.2.0:

A wider run, smarttest benchmark --commits 200 --sample 40 (40 commits spread over the last 200), found 0 missed failures. SmartTest ran 522 of 3,052 test-file runs (17%), 354 of 2,884 on the 38 commits without a lockfile change, and saved 63% of the full-run time. Against the project-gated baseline it was 6% slower: on single-project commits the old hook runs one small suite, and SmartTest's analysis time is counted in full.

Update: 0.6.0 to 0.8.0 #

VersionWhat changed on LeadsItMeasured
0.6.0smarttest hook status recognises the existing husky hook; smarttest ci picks the base by itself; parse in worker threads and parallel runner discoverycold scan 5.5 s → 3.6 s; 12-commit replay at 0 missed failures
0.7.0sync, watch, migrate; Playwright and Cypress adapters (LeadsIt has no end-to-end suite in its hook)706/706 through SmartTest
0.8.0Runner groups (Jest, Vitest, two node --test projects) run in parallel; validated result cachefull suite 7.7 s → 5.2 s; a repeated run of the same state 4.1 s → 1.2 s (93 of 100 files from the cache, labelled "cached, not run"); 12-commit replay 47.2 s → 43.4 s, 0 missed failures, 0 cache-integrity violations

Update: 0.9.0 #

LeadsIt vendors tools/smarttest/smarttest-0.9.0.tgz. hook install --dry-run previews the upgrade and hook install --yes changes the existing invocation to run --staged --hook. Pelloux, i18n and TypeScript checks remain intact. Adoption caught and fixed a shell-text replacement bug in SmartTest before applying the hook.

The packaged CLI passes 714 assertions across 101 files in 4.78 s. The final adoption commit f21d47c1 also runs the executable Git hook: 714 passed, zero cached, in 5.3 s; the dirty apify submodule remains unstaged and untouched. A staged hook run takes 3.67 s including isolation: 314 passed, 400 cached, zero failed. Cached assertions were not executed in that run.

The 12-commit replay records 53.59 s full, 7.18 s project-gated and 34.38 s SmartTest, with zero missed failures and zero cache violations. SmartTest saves 36% against full runs but is 379% slower than project-gated execution on this window. It selects 526 of 1,204 test-file runs. History, cache and machine load differ from older measurements; this is not a controlled version comparison. The raw benchmark retains the per-commit results.

Update: 1.0.0 #

Measured on a fresh clone with the 1.0.0 candidate (all repositories, raw results):

Caveats #

Edit this page on GitHub