Liczbę już widzieliście.

W serii o toolkicie WCAG puściłem audyt po sitemapie na całej opublikowanej powierzchni tego portfolio. Trzydzieści pięć stron. 5 816 znalezisk. Cztery commity na poziomie CSS później: siedem, wszystkie false positive z podsystemu, który sam napisałem. Zero SERIOUS. Zero błędów AA.

Tamten wpis był o tym, co się stało. Potem padło lepsze pytanie: jak audyt znajduje strony, których audyt strony głównej nie miał jak zobaczyć? Jak jedno narzędzie zamienia 5 808 wystąpień tego samego buga w jedną linijkę w raporcie zamiast 5 808 ticketów?

To jest ta maszyna. Bez reveal, bez zwrotu akcji - wiecie, jak kończy się ta historia. Część 1 to architektura.

Audyt jednej strony to wtyczka do Lighthouse

Najpierw powiem niewygodną rzecz, bo to cały powód, dla którego ta funkcja istnieje.

Audyt dostępności jednego URL-a - wskazujesz stronę, dostajesz ocenę - to problem rozwiązany. Robi to Lighthouse. Robi to axe. Robił to mój własny toolkit w v0.3. Jest przydatny i nie mówi prawie nic o tym, czy twój serwis jest dostępny.

Runda 3 audytu portfolio osiągnęła zbieżność na stronie głównej. Trzy przebiegi, zero nowych znalezisk. “Skończone.” Potem skierowałem discovery na router zamiast na URL i znalazłem dziewięć znalezisk SERIOUS na trzech stronach, których audyt strony głównej nie miał jak zobaczyć. Strona główna była czysta. Strony artykułów nie. Listingi odcinków nie. Archiwum nie.

Zbieżność na jednym URL-u nie znaczy, że serwis osiągnął zbieżność. Znaczy, że ten URL ją osiągnął. Ta przepaść - między “ta strona przechodzi” a “ten serwis przechodzi” - to cała klasa problemu, do której obsługi istnieje audyt multi-page.

Flaga, nie przepisywanie

Cała funkcjonalność multi-page wisi na jednej fladze: --multi-page.

Bez niej toolkit zachowuje się dokładnie tak jak w v0.3. Ten sam audyt jednej strony, output bajt w bajt identyczny. To było twarde ograniczenie projektowe, nie miły dodatek. W momencie, w którym narzędzie po cichu zmienia to, co robi, między wersjami, masz zepsuty każdy pipeline CI, który mu ufał. Więc multi-page jest ściśle opt-in, a stara ścieżka jest zamrożona.

To, co flaga podpina, jest proste do opisania i jest powodem, dla którego reszta działa: warstwa discovery przed dynamicznym testerem i deduper za nim.

single-page (v0.3):   --url  ->  audyt  ->  raport

multi-page (v0.4):     --url  ->  znajdź trasy  ->  audyt każdej  ->  dedup  ->  raport
                                  [warstwa discovery]               [deduper]

Krok audytu w środku to ten sam silnik, który już miałem. Multi-page nie robi audytu mądrzejszym. Sprawia, że audyt leci po właściwym zbiorze stron, i sprawia, że output jest czytelny, gdy jeden bug pojawia się na czterdziestu z nich.

Discovery: domyślnie trzy strategie, AI na żądanie

To jest decyzja, której broniłbym najmocniej, bo to ta, która wygląda na błędną, dopóki nie zapłacisz rachunku za API.

Dyspozytor discovery leci łańcuchem fallbacku. Domyślna kolejność:

sitemap  ->  router-scan  ->  json-config

Próbuje pierwszej. Jeśli ta wróci pusta, spada do następnej. Możesz przypiąć jedną jawnie przez --strategy=<nazwa> i pominąć łańcuch.

Zauważcie, czego nie ma w domyślnym łańcuchu: agenta AI. Strategie są cztery, ale automatycznie lecą tylko trzy.

Sitemap to najtańsza dostępna prawda. Jeśli serwis wystawia sitemap.xml, to jest rzeczywistość po buildzie - co faktycznie jest opublikowane. 35 tras dla tego portfolio. Jeden fetch HTTP, parsowanie, odfiltrowanie szumu (/og/, /api/, feedy). Pewność 1.0, bo to nie zgadywanka, to output buildu.

Router-scan to deterministyczny fallback, gdy nie ma sitemapy albo lecisz po lokalnym dev serverze. Czyta package.json, identyfikuje framework i chodzi po źródłach: src/pages/**/*.astro, App Router i Pages Router dla Nexta, konfig vite-plugin-pages dla Vue i tak dalej. Bez sieci, bez modelu, bez tokenów. Znalazł 11 tras ze szkieletu źródeł tego portfolio.

JSON config to wyjście awaryjne. wcag.config.json z jawną listą stron i opcjonalnymi hookami auth, na wypadek gdy discovery nie umie wywnioskować, czego chcesz - trasy za logowaniem, podzbiór ze stagingu, ręcznie wybrana ścieżka krytyczna.

Agent AI to strategia czwarta i jest świadomie poza domyślnym łańcuchem. Dispatchuje agenta route-discovery przez sesję Claude Code, czyta konfigi frameworka i zwraca ustrukturyzowaną listę tras z oceną pewności. To najbardziej elastyczna strategia i jedyna, która kosztuje przy uruchomieniu. Więc jest opt-in: --strategy=ai, albo aktywuje się, jeśli masz już AI włączone. Nikt nie dostaje niespodziewanego rachunku za tokeny, bo rutynowy audyt postanowił pomyśleć.

To jest zasada, na której leci cała seria, zastosowana do jednej funkcji: domyślnie deterministycznie, AI tylko tam, gdzie zarabia na siebie. Parsowanie sitemapy i przejście po źródłach rozwiązują problem discovery dla większości projektów bez ani jednego wywołania modelu. Agent jest dla projektów, które go potrzebują, nie jako drzwi wejściowe.

Frameworki: cztery, które wiedzą, cztery, które ostrzegają

Żywa zajawka tego odcinka mówi “8 frameworków”. To prawda w wąskim sensie, że detektor rozpoznaje osiem, i mylące w sensie, który ma znaczenie, więc oto wersja uczciwa.

Cztery mają realny detektor route-discovery: Astro, Next (zarówno App Router, jak i Pages Router), Vue (vite-plugin-pages) i Nuxt (który jedzie na detektorze Vue). Skieruj router-scan na którykolwiek z nich, a przejdzie po faktycznej strukturze routingu.

Cztery są rozpoznawane, ale niezaimplementowane: SvelteKit, Remix, Gatsby, React Router. Detektor identyfikuje je z package.json, a potem ostrzega “no detector yet”. Nie dostajesz listy tras - dostajesz jasny komunikat mówiący, żeby użyć --strategy=ai albo napisać config.

Zostawiłem sobie notatkę w dokach troubleshootingu na ten temat. Jeśli narzędzie kiedykolwiek powie ci “detected next but no detector implemented yet”, ten komunikat kłamie - Next ma detektor. Jeśli faktycznie go zobaczysz, trafiłeś na jeden z tych czterech, które nie mają. Przyszły ja będzie wiedział, co to znaczy. Teraz wy też.

Osiem rozpoznawanych, cztery w pełni obsługiwane. Jeśli jesteś testerem, wyłapałbyś tę dziurę przy pierwszym uruchomieniu na SvelteKicie, więc wolę powiedzieć to z góry.

Deduper to ta część, która ma znaczenie

Discovery daje ci właściwe strony. Deduper sprawia, że 5 816 znalezisk da się przeżyć.

Puść audyt po 35 stronach, a naiwny output to 35 stron × znaleziska na stronę. Ten sam zepsuty token we współdzielonym komponencie pojawia się na każdej stronie, która go renderuje. Wyciek motywu bloków kodu Shiki w tym portfolio pojawił się na każdej stronie z blokiem kodu - 5 808 wystąpień jednego buga. Jako 5 808 pozycji ten raport jest nieczytelny i niefixowalny. Wygląda na katastrofę. To jedna zmienna CSS.

Więc znaleziska nie agregują się po liczbie. Agregują się po przyczynie. Deduper grupuje po czteroczęściowym kluczu:

(ruleId, sourceFile, line, selector)

Ta sama reguła, ta sama lokalizacja w źródle, ten sam selektor = ten sam bug, niezależnie od tego, na ilu URL-ach wypłynął. Grupa zwija się do jednego kanonicznego znaleziska, a URL-e rolują się w tablicę affectedPages przyczepioną do niego.

5 808 wystąpień staje się jednym znaleziskiem, które mówi “to pojawia się na tych 34 stronach”. Napraw zmienną raz, a następny audyt pokaże wszystkie 34 na zielono. To jest ta linijka w raporcie, która jest warta całej funkcji: jeden fix -> wiele stron na zielono. Graf zależności twoich bugów, nie płaska lista objawów.

To też powód, dla którego “5 816 znalezisk” nigdy nie było katastrofą, na jaką brzmi. Właściwe pytanie nie brzmi ile znalezisk - brzmi ile odrębnych bugów. Odpowiedź to trzy: jeden wyciek konfigu Shiki (5 808 wystąpień), jeden kolor odznaki i siedem cykli klawiatury, które okazały się false positive. Audyt multi-page nie zwielokrotnił roboty. Wydobył strukturę pod nią.

Co weszło, a co nie

Public v0.4.1 (30 kwietnia) wysyła to wszystko: pakiet route-discovery, strategie trzy-plus-jeden, orchestrator multi-page, cross-page dedup i raporty z heat-mapą. AGPL-3.0, tak samo jak reszta toolkitu. Warstwa discovery weszła z 47 nowymi hermetycznymi testami na wierzchu istniejącego zestawu - edge case’y sitemapy, wyczerpanie łańcucha dyspozytora, sama logika dedupu.

Czego nie wysłałem, żeby tego nie przereklamować: niszowi specjaliści Pro tier - modal-specialist i agent ecommerce-journey - są teraz stubami, oznaczonymi “do not dispatch”. Są na roadmapie, nie w release’ie. Funkcje Pro tier, które realne - nagrywanie trace, sekwencje screenshotów, trasy za auth, równoległe wykonanie - to temat późniejszej części. Postawię tę granicę jasno, jak do niej dojdę.

Jutro: ile to jest warte dla osoby, która podpisuje zgodność

Część 1 była inżynierią. Część 2 to druga połowa pytania i ta, która faktycznie obchodzi CTO: jeśli twoja strona główna przechodzi audyt dostępności, co to mówi o twoim serwisie? (Mniej, niż byś chciał.) Ile faktycznie kosztuje cię single-page-green, gdy European Accessibility Act dotyczy całej powierzchni, nie strony lądowania? I co audyt sterowany discovery zmienia w tej matematyce?

To jutro. Bez kodu, tylko ta część, która pokazuje się w budżecie.


Audyt multi-page jest open source: sdet-wcag-toolkit, AGPL-3.0. Część 2 omawia uzasadnienie biznesowe - ile faktycznie warta jest zgodność na całym serwisie i gdzie audyty jednej strony zostawiają cię odsłoniętym.