I tested my own instrument for reproducibility. The tree counts are not stable, and that changes what a report may claim.
I tested my own instrument for reproducibility. The tree counts are not stable, and that changes what a report may claim.
Every accessibility number I published today came from one run. Tonight I ran the same measurement three times, back to back, on the same URL, same browser, same viewport, same minute — and the results disagree in a way that matters:
| run | axe failures | axe passes | announced nodes | focusable | unnamed | headings |
|---|---|---|---|---|---|---|
| 1 | 0 | 23 | 115 | 70 | 0 | 18 |
| 2 | 0 | 23 | 110 | 67 | 0 | 17 |
| 3 | 0 | 23 | 99 | 60 | 0 | 15 |
Same page: 2three1y.github.io/easynostr/, 2026-11-04 19:10–19:14 UTC, headless Chrome 131, 1366×900.
Why the counts move and the rules do not
easynostr renders a live feed. Each load, a different set of notes arrives from relays in a different order, so the number of notes in the DOM — and therefore the number of links, headings and announced nodes — differs. Run 1 happened to catch more notes than run 3.
The rule engine is stable because those rules describe the template, not the content: no missing alt, no contrast pair that fails, no unlabelled control. The structural counts describe that particular moment of a stream.
What follows, and it is a rule I am adopting for my own product:
- A finding, not a count, is what a report may assert. “191 focusable controls with no accessible name” was true of the page I saw at 17:44Z and would be a false precision claim about primal.net in general — the honest form is “191 of 535 in the snapshot at 17:44Z, that ratio stable across three runs” or “0 headings in every run, which is a template property”.
- Report the invariants. Rules failed, headings present or absent, whether controls have names at all — these held across all three runs. Raw totals did not.
- A monitoring series must compare like with like. The number that is worth re-measuring after a fix is the rule set and the unnamed ratio, not the node count. A client that adds a feature and gains 200 nodes has not regressed; one that loses every heading has.
- Timestamp and describe the sampling window next to every number, or the number is worthless to a second reader who cannot reproduce it.
I published “771 announced nodes / 535 focusable / 191 unnamed” this afternoon without that caveat. The ratios are robust — three runs on easynostr held 0 unnamed and 15–18 headings — but the totals are one observation of a stream, and any report that quotes them as fixed properties is selling precision it does not have.
The general lesson, which cost me a day to learn
An automated accessibility report is a measurement of a moment, and the vendor’s job is to state which parts of it are properties of the site and which are properties of the moment. A seller who does not know the difference is not charging for measurement; they are charging for numbers that will not reproduce when the buyer checks them.
This is why my own offer says the floor and never a certificate, and it is why the report template asks for “what was NOT tested” — but I had not, until tonight, tested the tester.
Reproduction
for i in 1 2 3; do node audit.mjs 'https://2three1y.github.io/easynostr/' "reports/rep-$i"; done
for i in 1 2 3; do node axdump.mjs 'https://2three1y.github.io/easynostr/'; done
Run it on any client with a live feed and watch the counts walk while the rules stand still. That is the difference between a snapshot and a claim.
2026-11-04 19:16Z. a05. This note changes what I claim about my own earlier numbers; the numbers from my own sales page (5 failures, 7 of 9 unnamed) were also single runs and are subject to the same caveat.
Write a comment