One wrong day at the EU border. So I made the European Commission grade my tests.

By devmo · Built for the #TestSprite Hackathon Season 4 · Live: ninety-coral.vercel.app · Code: github.com/mohamedwael201193/ninety One wrong day A traveller is leaving Lisbon after a long summer in Europe. She has counted carefully: 89 days inside the Schengen area in the last 180. One day of margin. Since 10 April 2026, the EU's Entry/Exit System (EES) records every entry and every exit at the border. That record is the one that counts. One missed scan, one exit logged on the wrong day, one stay split in two, and her 89 becomes 91. 91 is an overstay on file. The law gives her a way back: Article 52 of Regulation (EU) 2017/2226 lets her ask for her EES data to be corrected. She just has to know the record is wrong, and prove it. That is Ninety. It counts your Schengen 90/180 days, compares them with your EES record, and when the border's count is wrong, it drafts the Article 52 correction letter for you. Every rule on /proof cites its article (Reg. 2016/399, Reg. 2017/2

One wrong day at the EU border. So I made the European Commission grade my tests.

By devmo · Built for the #TestSprite Hackathon Season 4 · Live: ninety-coral.vercel.app · Code: github.com/mohamedwael201193/ninety

One wrong day

A traveller is leaving Lisbon after a long summer in Europe. She has counted carefully: 89 days inside the Schengen area in the last 180. One day of margin.

Since 10 April 2026, the EU's Entry/Exit System (EES) records every entry and every exit at the border. That record is the one that counts. One missed scan, one exit logged on the wrong day, one stay split in two, and her 89 becomes 91. 91 is an overstay on file.

The law gives her a way back: Article 52 of Regulation (EU) 2017/2226 lets her ask for her EES data to be corrected. She just has to know the record is wrong, and prove it.

That is Ninety. It counts your Schengen 90/180 days, compares them with your EES record, and when the border's count is wrong, it drafts the Article 52 correction letter for you. Every rule on /proof cites its article (Reg. 2016/399, Reg. 2017/2226).

Ninety home page with the verified numbers

Why one day is everything

A calculator that is off by one day is worse than no calculator, because people trust it. So the real product here is not the UI. It is the proof that the number on the screen is the right one, for every traveller, on every pull request.

The idea: my engine never grades itself

No expected value in Ninety's tests comes from Ninety's own engine. They come from outside:

  • The European Commission's own short-stay calculator. 14 public scenarios match it 14/14, live at /parity.
  • The Commission's calculator script, run directly. 400 inputs compared field by field: 399/400 match on every printed field, and the one difference is documented line by line.
  • 5,000 fuzzed travel histories on every PR, 20,000 every night.
  • An independent reference implementation, written straight from Art. 6(1) of Reg. 2016/399: 1,200 cases per PR, 20,000 nightly, half of them on the edges (day 180 and 181, 29 February, the year change, the clock changes).
  • 408 Vitest tests and a 94.47% mutation score.

The European Commission grades my tests. TestSprite proves a real person clicking through the real app sees that same right number.

How TestSprite is wired in

Every pull request gets a Vercel preview, and TestSprite tests it twice:

  1. TestSprite V3 (GitHub App): 54 end-to-end UI tests, each asserting exact values. One test checks all 14 parity counts.
  2. TestSprite backend (CLI in GitHub Actions): 30 API + MCP tests across the 3 API endpoints, which are also 3 MCP tools.

84 TestSprite tests on every PR.

Both sit inside 7 required checks on main. No bypass. Admins included. The 7th is the TestSprite N/N gate: it reads the real counts from both suites on the same commit and accepts nothing short of N/N.

Branch protection: 7 required checks, admins included

It earned its place on the final PR. #71's first commit read 52/54, and the gate held the merge. The next commit read 54/54 + 30/30, the gate went green, and it merged.

PR #71: the N/N gate holds the merge at 52/54

PR #71: 54/54 + 30/30, every check green

Triage on red. A red backend run posts a comment on the PR: each failing test, the likely cause, the run id, and the exact command to rerun just those tests.

Nightly on production. Every night 5 TestSprite tests run against the live site. On a red night the workflow opened issue #66 by itself, and PR #67 closed it. The loop runs even while I sleep.

The N/N gate explained on the live /proof page

The loop: 14 PRs went red, then green

I didn't write the loop story by hand. A script reads the GitHub API (every merged PR, every commit, every TestSprite status) and generates LOOP-ITERATIONS.md.

Across 64 merged PRs and 99 TestSprite V3 results on 60 PRs (as of #70), 14 PRs went red on TestSprite, then green:

PR Red run Cause
#8 9/11 App defect
#14 12/14 App defect
#18 13/14 Test plan / CI
#22 13/14 Test plan / CI
#32 15/17 Test plan / CI
#36 13/14 Test plan / CI
#39 14/15 App defect
#41 14/15, then 16/17 App defect (2 bugs)
#43 16/17 Test plan / CI
#55 34/35 App defect
#57 34/43 → 42/43 → 43/43 App defect
#60 42/43 UX loading state
#68 52/54 Test plan / CI
#69 52/54 Test plan / CI

7 real bugs, 1 UX loading state, 7 sharper test plans. 0 planted. Every one is logged in docs/DEFECTS.md.

The 7 real bugs TestSprite caught

  1. #8: the parity table silently dropped a scenario.
  2. #14: the planner ignored a stay still in progress, the exact error that becomes an overstay.
  3. #39: a loaded scenario was lost between pages, so the comparison and the letter used the wrong history.
  4. #41 (a): ?fresh=1 wiped saved trips again on reload.
  5. #41 (b): the evidence table's difference cell read as blank.
  6. #55: Escape left keyboard focus on the page body instead of "Add trip".
  7. #57: /parity could not confirm the Commission's calculator from production. It now runs a stored, hash-checked copy of the Commission script.

PR #57: red first, on purpose

Code review found 8 bugs. I wrote 8 TestSprite plans for them (TS45–TS52) and pushed them before the fixes. TestSprite read 34/43. The fixes took it to 42/43, and that last red exposed the /parity bug above. Fixed: 43/43. Tests first, real bugs found, merged green.

TestSprite V3: 54/54 on PR #70

GitHub Actions: backend suite and N/N gate on PR #70

Why TestSprite made this possible

  • It tests what users actually see. Unit tests prove arithmetic. TestSprite proves the right number reaches the screen, on a real preview of every PR.
  • It turns a claim into a gate. With the N/N gate as a required check, "all tests pass" is no longer a promise in a README. It is a rule GitHub enforces, on admins too.
  • It closes the loop. Plan written first, red on the PR, fix pushed, green, merged. 14 times, generated straight from the GitHub API.
  • It never sleeps. The nightly production run opened issue #66 on its own, and the fix closed it.
  • Plans in plain English. 54 UI plans that read like a checklist a traveller would write, each asserting exact values.

A traveller standing at a border should never have to wonder whether her count is right. With the European Commission grading the math and TestSprite guarding every pull request, Ninety can tell her: it is.

Try it: ninety-coral.vercel.app · Proof: /proof · Code: github.com/mohamedwael201193/ninety

Built by devmo for the TestSprite Hackathon Season 4. #TestSprite #TestSpriteHackathon

Share