Page check
How the page check works.
What the scanner loads, what it never does, how the score is computed, and how a site owner opts out.
From the terminal or your coding agent: npx @genclass/runtime@beta scan <url> (alias audit,
--json for agents); the MCP tool genclass_scan runs the same. This page check runs in a Cloudflare
browser; the command runs the checks in a browser on your machine, with this page check as its fallback.
What it does
- Normalises the address (https:// is added when missing; only http and https on the standard ports) and refuses IP
addresses, local names, genclass.dev itself and hostnames that resolve to private, loopback, link-local, carrier-grade NAT or
cloud metadata addresses. It reads the site's
robots.txtfor aGenClassScangroup. From our server it also asks for the page's http:// address (to see whether it redirects to https://; only the first 16 KB are read) and/favicon.ico, and asks hstspreload.org whether the hostname is on the browsers' HSTS preload list. - Starts a fresh browser (Cloudflare Browser Rendering) with no cookies or storage, and loads the page three times in parallel: desktop, 1366 × 768, light; phone, 390 × 844, light; phone, 390 × 844, dark. Each load has 16 seconds, then up to 4 more for the network to go quiet (half a second when the page had not finished loading). When a page is still loading after that, the scanner pauses its playing video and audio so the checks can run, and the report says so.
- Runs the visual checks in each view for about 6 seconds: the measured checks the GenClass runtime ships from version 0.3.0-beta.0, the runtime's visual chunk from
commit
6ae209f, in an isolated part of the browser the page's scripts cannot reach. It presses Tab a few times on the desktop and scrolls with the mouse wheel, which some checks need (focus under a sticky header, a scroll lock, a control under a fixed bar). With GenClass on, the checks run in guard mode with their fixes turned on, and a second, report-only set of the same checks then measures the page again; the fixes are undone before the screenshot. The page's fetch and XHR requests in flight are counted and handed to the checks, which do not judge a blank page or a page that ignores clicks while a request runs. - Takes a screenshot of the top of the page in each view and records the box of every finding.
- Asks a model, Clef on Cloudflare Workers AI, about up to 40 single elements across the three views, from a crop of a viewport screenshot: the visual checks' findings, for a second look (with the element's measured facts), and elements the measured checks cannot judge (text over images, covered controls, buttons and links in the dark view). What it finds is labelled as judged by a model, never as measured (the model judge, below).
- Runs axe-core 4.13.0 in each view, unmodified, in the same isolated part of the browser: its WCAG 2.2
A and AA rules (tags
wcag2a,wcag2aa,wcag21a,wcag21aa,wcag22aa) and its best practices (best-practice). Only violations become findings; rules axe-core cannot decide by itself ("needs review") are counted and shown as a number, never scored. Three of its rules are turned off because the scanner measures the same thing itself and would otherwise count it twice:target-size,meta-viewport,meta-viewport-large. It does not look inside iframes and fetches nothing. - Audits the light phone view: tap targets, the viewport meta tag, text sizes, the page's head (description, robots, link preview tags, icon) and the main document's security headers and cookies; then narrows the same page to 360 and 320 px wide and widens it to 768 px, without a reload, and measures whether it scrolls sideways. On the desktop and the light phone it compares each image's file with the size it is shown at, and reads its format and the web fonts' font-display. On the desktop, last, it focuses the first keyboard stops and presses Tab to those whose styles did not change, comparing screenshots with and without focus.
- Reads what the browser itself reports (a DevTools protocol session of the scanner's own) while each view loads and its
checks run: uncaught errors,
console.error, the status and size of every response, and mixed content. On the desktop it reads Largest Contentful Paint and layout shifts once the network is quiet, before the scanner presses a key or scrolls. - On the desktop page, with GenClass 0.2.2 loaded in observe mode (telemetry off, model from
genclass.dev/model/), typesa,ab,abcinto the first search box, 80 ms apart, and double-clicks the most prominent submit button of a same-origin form that would POST. Forms with a password or card field are skipped, and so are forms and buttons that would submit into another window. GET responses during the probe get 0 to 1,500 ms of extra latency, so they can arrive out of order. - Opens the page once more with GenClass in guard mode and repeats the probe, to measure what guard changes.
- Scores the findings and keeps the report for 6 hours. The whole scan has a hard budget of 50 seconds; a step that does not fit is skipped and the report says so. When the page sent requests other than GET while loading, findings that may come from the scanner's stand-in replies (template text, broken images, JavaScript errors, and GenClass diagnoses other than a duplicate) are listed but not scored.
What it never does
- It never sends a POST, PUT, PATCH, DELETE or a beacon to the site. Every request other than GET, while the page loads and
during the probe, is answered inside the browser with a stand-in
200JSON reply (or a204for a form navigation, so the page stays put) and never leaves it. Form data that the page's own script sends with GET does reach the site's own origin. - It never follows a link off the page: after the first load, any navigation of the page is cancelled. It opens no new
windows or tabs:
window.opendoes nothing, and forms and links that would open one stay in the page. - While it clicks, requests for data to other origins are cancelled and WebSocket messages are held.
- It never signs in, never types into anything but a search box, and registers no service workers. The double-click probe submits the form empty, with the browser's own form validation turned off.
- It does not load private addresses. IP addresses in private, loopback, link-local, carrier-grade NAT and metadata ranges,
and local names, are blocked for every request. Every hostname the page asks for is looked up in DNS first, and a name that
points to a private address is not loaded; a redirect also has to pass the opt-out list and
robots.txt. WebSocket connections to IP addresses or local names are refused, and a WebSocket host that resolves to a private address throws the scan away. The browser then looks each name up again itself, so a DNS server that gives a different answer the second time is the one case these checks cannot rule out.
User agent
Every request carries the token GenClassScan/1.0 (+https://genclass.dev/scan) at the end of a normal Chrome user agent:
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 GenClassScan/1.0 (+https://genclass.dev/scan) Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36 GenClassScan/1.0 (+https://genclass.dev/scan)
Requests come from Cloudflare's Browser Rendering network, which identifies them as automated.
Limits
- Five new scans per minute per visitor, and one running at a time (cached results do not count). Visitors on IPv6 count by their /64 network.
- A few new scans per minute per site, whoever asks, and a small number of scans at once across everyone.
- Crawlers and link previews can open a report but cannot start a scan.
- One scan per address at a time; a second request for the same address waits for the first result.
How the score works
scan-score/4Strict on purpose, like Lighthouse: every point lost comes from a finding listed in the report. Eight categories are each scored from 100 by their own findings, grouped by rule (one visual check, one axe-core rule, one audit). A rule takes points by its severity, more when it found many elements (up to 3 times its base at 100), and rules multiply, so each further rule takes a share of what is left. The score is the weighted geometric mean of the categories, so one failing category pulls the whole score down instead of being averaged away, squared on a 0 to 1 scale (a mean of 90 gives 81, 70 gives 49), and then capped: three serious rules at 69, a critical finding at 59, two critical rules at 35. A category this scan did not measure is left out, never counted as 100. The same findings always give the same score.
category = 100 × Πrules (1 − points(rule) / 100)
mean = Πmeasured categories (max(category, 10) / 100)weight / Σ weight
score = 100 × mean2, then the lowest cap that applies
base: critical 50, serious 25, moderate 15, minor 12 · caps: three serious rules at 69, a critical finding at 59, two critical rules at 35 · weights: Visual bugs 20, Accessibility 22, Phone 14, Reliability 12, Speed 12, Security 10, Search and sharing 5, Races 5
For example, 30 elements failing axe-core's color-contrast (serious) take 61.9 points and 2
images failing image-alt (critical) take 65.05, so accessibility is 13.3. With every
other category at 100 the curved mean is 41.2, and the critical finding caps the score at 41.
Each top issue says what it costs: the score without that rule minus the score with it.
| Category | Weight |
|---|---|
| Visual bugsText that cannot be read, template or placeholder text, garbled characters, images, stylesheets, fonts and media that failed, stretched images, a blank page, a page that ignores clicks or cannot scroll, controls under fixed bars, covered dialogs (the GenClass visual checks); and elements a model (Clef) judged from the screenshot as visibly broken where the measured checks cannot judge, labelled as judged by a model | 20 |
| Accessibilityaxe-core's WCAG 2.2 A and AA rules and its best practices in all three views, focus hidden under sticky bars (WCAG 2.4.11), elements that show no visible change when focused by keyboard (WCAG 2.4.7), and a long run of links before the main content without a skip link | 22 |
| PhoneTap targets under 24 by 24 px (WCAG 2.5.8), zoom turned off (WCAG 1.4.4), no viewport meta tag, text under 12 px, sideways scrolling at 390, 360 and 320 px (WCAG 1.4.10) and on a 768 px tablet | 14 |
| ReliabilityUncaught JavaScript errors and console.error while the page loads, same-site requests that failed with 4xx or 5xx, mixed content, deprecated browser APIs Chrome reported, a page in quirks mode | 12 |
| SpeedLargest Contentful Paint against the web.dev thresholds, total page weight, images far bigger than they are shown or not in a modern format, images without dimensions that moved the content below them, web fonts without font-display (layout shift itself is listed, not scored) | 12 |
| SecurityThe page over HTTPS, http:// redirecting to https://, and the main document's headers: Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options, protection against framing (X-Frame-Options or frame-ancestors), cookies without Secure, session cookies without HttpOnly | 10 |
| Search and sharingA meta description, a page search engines may index, link previews (og:title, og:image), a favicon, and links whose whole text is "click here" or "learn more" | 5 |
| RacesA double click that sends a request twice, and problems GenClass's model diagnosed while the probe typed and clicked | 5 |
| Severity | Base | 3 instances | 100 or more |
|---|---|---|---|
| critical | 50 | 73.9 | 150 |
| serious | 25 | 36.9 | 75 |
| moderate | 15 | 22.2 | 45 |
| minor | 12 | 17.7 | 36 |
| Finding | Category | Severity | Standard |
|---|---|---|---|
Text that is almost invisible on its backgroundtext-invisible | Visual bugsGenClass visual check | serious | WCAG 2.2 SC 1.4.3 Contrast (Minimum), level AA |
Template or debug text shows on the pagetext-template-leak | Visual bugsGenClass visual check | serious | The measurement itself |
Placeholder content shows on the pagetext-placeholder | Visual bugsGenClass visual check | moderate | The measurement itself |
Characters are garbledtext-encoding | Visual bugsGenClass visual check | moderate | The measurement itself |
A web font failed to loadfont-failed | Visual bugsGenClass visual check | minor | The measurement itself |
An icon font failed to loadicon-font-failed | Visual bugsGenClass visual check | moderate | The measurement itself |
An image failed to loadimage-broken | Visual bugsGenClass visual check | moderate | The measurement itself |
A CSS background image failed to loadimage-css-broken | Visual bugsGenClass visual check | moderate | The measurement itself |
A video or audio element has nothing it can playmedia-failed | Visual bugsGenClass visual check | moderate | The measurement itself |
An image is stretched or squashedimage-distorted | Visual bugsGenClass visual check | moderate | The measurement itself |
A stylesheet failed to loadstylesheet-failed | Visual bugsGenClass visual check | serious | The measurement itself |
The Content-Security-Policy blocked a file the page usescsp-blocked | Visual bugsGenClass visual check | moderate | The measurement itself |
A CSS variable is used but defined nowherecss-var-undefined | Visual bugsGenClass visual check | minor | The measurement itself |
The page scrolls sidewayspage-overflow-x | PhoneGenClass visual check | serious | WCAG 2.2 SC 1.4.10 Reflow, level AA |
Phones show the desktop layout, zoomed outviewport-meta-missing | PhoneGenClass visual check | serious | Lighthouse: Has a <meta name="viewport"> tag with width or initial-scale |
Content jumps while the page loadslayout-shift | SpeedGenClass visual check | moderate | web.dev Cumulative Layout Shift: good at most 0.1, poor above 0.25 |
The main part of the page shows nothingpage-blank | Visual bugsGenClass visual check | critical | The measurement itself |
An invisible layer swallows clicksclick-blockedNot run in the page check: it needs a visitor's clicks | Visual bugsGenClass visual check | serious | The measurement itself |
The whole page ignores clickspointer-events-leak | Visual bugsGenClass visual check | critical | The measurement itself |
The page cannot scrollscroll-lock-stuck | Visual bugsGenClass visual check | critical | The measurement itself |
A control is stuck under a fixed barcontrol-unreachable | Visual bugsGenClass visual check | serious | The measurement itself |
A focused element is hidden under a sticky barfocus-obscured | AccessibilityGenClass visual check | moderate | WCAG 2.2 SC 2.4.11 Focus Not Obscured (Minimum), level AA |
An open dialog is covered, or its buttons are off-screendialog-covered | Visual bugsGenClass visual check | serious | The measurement itself |
An accessibility rule failedaxe:<rule id> | Accessibilityaxe-core | the rule's impact | The WCAG success criteria in the rule's tags, or axe-core best practice |
Tap targets smaller than 24 by 24 px, too close to other targetstap-target-small | PhoneScanner audit | serious | WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA |
Pinch zoom is turned offzoom-disabled | PhoneScanner audit | serious | WCAG 2.2 SC 1.4.4 Resize Text, level AA |
Body text smaller than 12 px on a phonefont-size-small | PhoneScanner audit | minor | Lighthouse: Document uses legible font sizes (12 px; at least 60% of the text) |
The page scrolls sideways on narrower phonesreflow-overflow | PhoneScanner audit | serious | WCAG 2.2 SC 1.4.10 Reflow, level AA |
Images far bigger than they are shownimage-oversized | SpeedScanner audit | minor | Lighthouse: Properly size images |
An image without dimensions moved the content below itimage-unsized-shift | SpeedScanner audit | minor | web.dev Optimize CLS: images without dimensions |
The largest content appears latelcp-slow | SpeedScanner audit | moderate | web.dev Largest Contentful Paint: good at most 2.5 s, poor above 4 s |
The layout shifts while the page loadscls-high | SpeedScanner audit | moderate | web.dev Cumulative Layout Shift: good at most 0.1, poor above 0.25 |
More than 1 MB of JavaScript transferredjs-heavy | SpeedScanner audit | minor | web.dev: Reduce JavaScript payloads with code splitting |
Uncaught JavaScript errors while the page loadsjs-error | ReliabilityScanner audit | moderate | Lighthouse: No browser errors logged to the console |
console.error while the page loadsconsole-error | ReliabilityScanner audit | minor | Lighthouse: No browser errors logged to the console |
Same-site requests that failedrequest-failed | ReliabilityScanner audit | moderate | RFC 9110 HTTP Semantics: 4xx client errors and 5xx server errors |
The secure page asks for files over plain httpmixed-content | ReliabilityScanner audit | moderate | W3C Mixed Content |
Elements show no visible focusfocus-invisible | AccessibilityScanner audit | serious | WCAG 2.2 SC 2.4.7 Focus Visible, level AA |
No skip link before a long run of linksskip-link-missing | AccessibilityScanner audit | minor | WCAG technique G1: a link at the top of each page that goes to the main content (best practice) |
The page scrolls sideways on a tablettablet-overflow | PhoneScanner audit | moderate | The measurement itself |
Uses browser features Chrome is removingdeprecated-api | ReliabilityScanner audit | minor | Lighthouse: Avoids deprecated APIs |
The page renders in quirks modequirks-mode | ReliabilityScanner audit | minor | Lighthouse: Page has the HTML doctype |
The page downloads a lot of datapage-weight | SpeedScanner audit | minor | Lighthouse: Avoid enormous network payloads (scores 90 at 2,667 KiB, 50 at 4,000 KiB) |
Large images in JPEG, PNG, GIF or BMPimage-format | SpeedScanner audit | minor | Lighthouse: Serve images in modern formats |
Web fonts without font-displayfont-display | SpeedScanner audit | minor | Lighthouse: Ensure text remains visible during webfont load |
The page is not served over HTTPShttps-missing | SecurityScanner audit | serious | Lighthouse: Uses HTTPS |
http:// does not redirect to https://http-no-redirect | SecurityScanner audit | moderate | OWASP Transport Layer Security Cheat Sheet: redirect HTTP to HTTPS |
No Strict-Transport-Security headerhsts-missing | SecurityScanner audit | moderate | RFC 6797 HTTP Strict Transport Security |
No Content-Security-Policy limiting scriptscsp-missing | SecurityScanner audit | moderate | W3C Content Security Policy Level 3 |
No X-Content-Type-Options: nosniffnosniff-missing | SecurityScanner audit | minor | OWASP HTTP Headers Cheat Sheet |
Any site can show this page in a frameclickjack-unprotected | SecurityScanner audit | moderate | OWASP Clickjacking Defense Cheat Sheet |
Cookies set without Secure or HttpOnlycookie-insecure | SecurityScanner audit | moderate | OWASP Session Management Cheat Sheet: cookie attributes |
No meta descriptionmeta-description-missing | Search and sharingScanner audit | moderate | Lighthouse: Document has a meta description |
Search engines are told not to list this pagenoindex | Search and sharingScanner audit | serious | Lighthouse: Page isn't blocked from indexing |
No link preview title or imagesocial-preview-missing | Search and sharingScanner audit | minor | The Open Graph protocol: og:title and og:image |
No faviconfavicon-missing | Search and sharingScanner audit | minor | Google Search Central: Define a favicon |
Links that only say "learn more" or "click here"link-text-generic | Search and sharingScanner audit | minor | Lighthouse: Links have descriptive text |
A double click sent the same request twiceduplicate-submit | RacesRace probe | serious | The measurement itself |
Search results for an older query stayed on the pagestale-results | RacesRace probe | serious | The measurement itself |
GenClass flagged a timing problemgenclass-detection | RacesRace probe | serious | The measurement itself |
An element that looks broken in the screenshotvisual-judged | Visual bugsModel judge (Clef), not measured | moderate | None: a model's judgement from a crop of the screenshot (the model judge) |
Where severities come from
- The visual checks measure their own severity (high, medium, low), read as serious, moderate and minor. A blank page, a page that ignores clicks and a page that cannot scroll are critical.
- axe-core rules keep axe-core's impact (critical, serious, moderate, minor). A rule that is only a best practice, with no WCAG A or AA tag, is one level lower, since it is advice rather than a requirement.
- The scanner's audits set theirs from the thresholds below.
- A finding the model judge made is always moderate: it is a judgement, not a measured severity.
The audits and their thresholds
- Tap targets (light phone, WCAG 2.2 SC 2.5.8, serious): a visible link, button, form control or element with a control role smaller than 24 by 24 CSS px, when a 24 px circle on its centre touches another target or another small target's circle. Links inside a sentence, checkboxes and radios at the browser's own size or with a label of at least 24 by 24 px, disabled controls, and targets hidden, clipped or off the page are left out.
- Zoom (WCAG 2.2 SC 1.4.4, serious): the
viewport meta tag has
user-scalable=noormaximum-scalebelow 2. - Viewport meta tag (Lighthouse's viewport audit, serious): none on the page, so a phone lays it out 980 px wide.
- Text size (light phone, Lighthouse's legible font size, 12 px): a block of at least 40 characters of text under 12 px is minor; more than 40% of the page's text under 12 px (Lighthouse's audit fails) is moderate.
- Reflow (WCAG 2.2 SC 1.4.10): the light phone narrowed to 360 and 320 px; the page is wider than the viewport and can scroll sideways. Serious at 360 px, moderate at 320 px only, minor when only tables or preformatted text stick out. Not judged when it already scrolls sideways at 390 px.
- Oversized images (Lighthouse's "Properly size images", minor; moderate above 1 MB in all): more than 4 times the pixels the image covers on a 2x screen (desktop) or 3x screen (phone), and more than 100 KB transferred. SVG is left out.
- Images without dimensions (web.dev, minor): no width and height and no aspect-ratio (as attributes or inline style), arriving after the first paint, and a layout shift from 50 ms before to 500 ms after it arrived moved content below it down. An unsized image that moved nothing is not reported.
- Largest Contentful Paint (web.dev), judged on the faster of two loads in the same browser, the desktop view and the light phone (which loads without GenClass): minor when that is above 2.5 s (needs improvement), moderate above 4 s (poor). One load can be much slower than a visitor's: in calibration a text heading a desktop browser painted at 0.59 s took 2.64 s in the scanner's desktop view and 2.13 s on its phone.
- JavaScript errors (Lighthouse's console errors audit): uncaught errors and unhandled rejections in the page's own
main frame while it loads, moderate (minor when every one came from a script on another site);
console.error, minor. Errors from the scanner's own code are left out. - Failed requests (RFC 9110): same-site scripts,
data requests, frames and other files answered with 4xx or 5xx. A script that answers 404, 410 or 5xx is serious, any other 5xx
or a frame moderate, the rest minor. Images, stylesheets, fonts and media belong to the visual checks; 401, 403, 407 and 429
depend on who is asking and are counted, not scored;
/favicon.icois the browser's own request. - Mixed content (W3C Mixed Content): on an https page, a request the browser blocked for being http is serious; a file referenced over http that the browser upgraded or allowed is minor.
- JavaScript transferred: more than 1 MB compressed on the desktop (the scanner's own GenClass runtime left out) is listed as a note and not scored.
- Keyboard focus (desktop, WCAG 2.2 SC 2.4.7, serious): the first 40 keyboard stops (text fields left out) are focused by script; for up to 6 whose styles did not change at all, the scanner presses Tab to reach each one and compares lossless screenshots of the area around it, focused and not. Only identical screenshots count.
- Skip link (WCAG technique G1, minor, a best practice): 15 or more keyboard stops before the main content, and none of the first three (hidden-until-focused ones included) links to it or says it skips to the content.
- Tablet (moderate): the light phone widened to 768 px without a reload scrolls sideways.
- Page weight (Lighthouse's total byte weight): what the phone downloaded by network quiet, minor above 2,667 KiB, moderate above 4,000 KiB.
- Image formats (Lighthouse, minor): JPEG, PNG, GIF or BMP images over 100 KB, although the browser accepts AVIF and WebP.
- Web fonts (Lighthouse, minor): fonts loaded with font-display auto or block (icon fonts left out).
- Deprecated APIs and quirks mode (Lighthouse, minor): what Chrome's Issues report as deprecated, and a page
without
<!doctype html>. - Security (the main document as the browser received it, 2xx only): not HTTPS (serious); http:// that does not redirect to https:// (moderate, one request from our server; not reported for a host on the HSTS preload list); no Strict-Transport-Security, no Content-Security-Policy that limits scripts, no protection against framing, cookies without Secure or session cookies without HttpOnly (moderate); no X-Content-Type-Options: nosniff (minor). Referrer-Policy and SameSite are not checked: browsers already default to safe values.
- Search and sharing: a robots noindex (serious), no meta description (moderate), no og:title or og:image, no favicon
(no
link rel="icon"and no image at /favicon.ico), links whose whole text is "learn more" or "click here" (minor).
Timings are measured once, in Cloudflare's browser in a data center: a fast network, three views loading at the same time in one browser, and GenClass loading in the desktop view for the race probe. They are indicative; field data such as Chrome's CrUX report is the reference for what visitors see.
Counted once
- Text the visual checks found below 1.5:1 is a visual bug; axe-core's
color-contrastleaves out that same element in that view. - axe-core's
color-contrastleaves out the text WCAG 1.4.3 exempts that a page can show machine-readably: text inside an element markedrole="img"oraria-hidden="true"(part of a picture, or decoration), and text that is only the site's own name (a logotype). The text-size audit leaves out the first kind too. - A layout shift the visual checks reported stands for the Cumulative Layout Shift note.
- Sideways scrolling at 390 px stands for the 360 and 320 px reflow check.
No score
When the scanner's browser is shown a bot check or a blank error page instead of the real page in every view (a known challenge or captcha on a small page, or a status other than 200 with nothing on it), the report gives no score at all and says so. A real blank page answered with 200 is scored. Such a result is kept for 30 minutes.
Listed, not scored
- Findings that may come from the scanner's stand-in answers to the page's POST requests, or from files it refused to load (template text, broken images, a blank main region, JavaScript errors, GenClass diagnoses other than a duplicate), when every view they appeared in had such answers or refusals.
- Findings in a category this scan did not measure, and the JavaScript size note.
- Visual findings a model (Clef) judged as probably fine from a crop of the screenshot, in every view they appeared in: they are listed on their own, under "Listed, not scored (a model judged it as probably fine)", with the crop and the measurement as it was made (the model judge).
- Layout shift: Cumulative Layout Shift on the desktop while the page loads (web.dev, session windows with gaps of at most 1 s and 5 s at most; above 0.1 needs improvement, above 0.25 poor) and the visual checks' layout shift above 0.25. Both are listed with what was measured and not scored. In the scanner's browser every request waits for the scanner's own checks before it continues, so content that scripts add after the first paint moves the page more than it does for most visitors: on the three public pages in the calibration where the scanner measured a shift, a desktop browser loading the same page measured none.
axe-core
axe-core 4.13.0 is Copyright (c) 2015 to 2026 Deque Systems, Inc. and licensed under the Mozilla Public License 2.0. The scanner runs it unmodified; its source code is at github.com/dequelabs/axe-core. axe-core's own guide for each rule is linked from the finding.
The visual checks
The checks are the measured visual checks built for the GenClass runtime (in runtime 0.3.0-beta.0), 23 kinds with fixed
thresholds (Table 2), 22 of which run in the page check (click-blocked needs a visitor's clicks, and the
scanner clicks only the race probe's button): text under 1.5:1
contrast on a background it can measure (text over images, gradients or video is skipped), [object Object],
$NaN or raw {{ }} placeholders in visible text, lorem ipsum, garbled characters, images, background
images, video, stylesheets, fonts and icon fonts that failed, files the Content-Security-Policy blocked, CSS variables defined
nowhere, stretched images, sideways scrolling, a missing viewport meta tag on phones, layout shifts above 0.25, an empty main
region, a page that ignores clicks or cannot scroll, controls under fixed bars, focus hidden under sticky bars, and dialogs that
are covered. Each finding says what was measured and against which threshold.
How often the checks are right, as measured for the runtime on GitHub runners (Chromium 153, 2026-10-13): on its
fixture pages, 35 of 35 planted faults found across the 23 kinds; on its bench, no false reports in 128 page views of
clean local apps, and recall 1.00 on planted faults in real app layouts for 19 of the 23 kinds (bench run 5); on public
home pages (bench run 4: 31 of 40 sites measured, 96 loads), 8 findings, 5 confirmed real, 0 check errors, 3
unconfirmed. The scanner embeds the same checks at a later commit (6ae209f) and runs them in
Cloudflare's browser (Chrome 128 when last measured) for about 6 s per view, with 4 Tab presses and no hover, so these
numbers describe the checks, not this scanner. Details are in the runtime's RESULTS.md, sections 8 and 9.
The with-GenClass score
It is measured, never projected. In the same browser, the visual checks run in guard mode with their
fixes on, as the option GenClass.init({ mode: "guard", visual: { on: true, fix: true } })
runs them, and a second set of the same checks, report-only, measures the page again with the fixes in place. The visual
fixes change three kinds in the page: text colour for invisible text, a leaked inline pointer-events: none and a
leaked inline scroll lock on html or body. A finding counts as fixed only when the fix was applied and the re-check no
longer found it, in every view where it appeared; for a leaked pointer-events: none or scroll lock the re-check
must also have run long enough (about 4.5 s) to see it again. Every other kind is reported for the developer with the
suggested CSS or code, and the report says so on each finding. The with-GenClass score only subtracts verified fixes: a
problem that shows up only once a fix is in place (text that becomes measurable after a leaked
pointer-events: none is removed, for example) is not added to it, and the report says how many there were.
The race probe runs again in a fresh page with GenClass in guard mode with its default settings, holding requests for up to 900 ms while it decides; a double submit counts as fixed only when guard's own action sent fewer requests, and a late response only when guard's own recorded discard or defer was on that same request. When guard flags a problem but its expected gain does not clear the safety margin, the report says it held back.
The model judge (Clef)
judged, not measuredEvery other finding comes from a measured check: a number against a threshold the finding names. The page check also asks a
model, Clef (@cf/cloudflare/clef, Cloudflare's open decision model, which reads images), running on
Cloudflare Workers AI, about single elements. It is always given a crop of a viewport screenshot (the element's box and
24 px around it), where the element is in it, and the view. It answers whether a visitor can see and use the element as
intended, and if not, what kind of problem it is; its score is 1 minus its probability that nothing is wrong.
- A second look at measured findings. For a visual check's finding the model is also given the element's measured
facts in words (its box, colours and contrast, what is behind it, what the hit test found on top of it, how much of it is
clipped; text lengths, never the text), with the instruction to judge by what the screenshot shows and to treat the facts as
hints: the evaluation's
clef-img-p3prompt. Sideways scrolling, images that failed to load and template text stay as measured, as the evaluation recommends (at its threshold the model dropped 13 of 34 real sideways-scrolling findings, which a crop cannot show, and 4 of 15 real broken images; template text was already real 5 times in 6), and so do findings with no element to crop (a failed stylesheet or font, a missing viewport tag, a lock on the whole page) and layout shift (already listed, not scored). A finding the model scores below 0.9777 in every view it appeared in moves to "Listed, not scored (a model judged it as probably fine)", with the crop; it is never dropped. On the evaluation, the rules followed by the model at that threshold had precision 0.56 where the rules alone had 0.225, and found 0.54 of the real problems where the rules alone found 0.81. - Findings of its own, where the measured checks abstain: controls another element covers or that are partly clipped,
every visible button and link in the dark view, and text over images, gradients or backgrounds the checks cannot know, in
that order and the first screen of the page first, leaving out the site's own name (a logotype). Here the model gets the crop,
the view and the tag only (the evaluation's
clef-imgonly-p2prompt): the facts of such an element are the measurements the checks could not make, and in calibration they talked the model out of the fixture's deliberate bugs. An element it scores at 0.9757 or more becomes a Visual bugs finding, moderate, labelled "Judged from the screenshot by a model (Clef)", with the crop and what it answered. That threshold was the lowest with precision at least 0.8 on the part of the evaluation used to choose it (4 of 4); on the rest, 32 of the 36 crops it scored that high were real problems (precision 0.89), 5 of 5 among the crops no rule had flagged. Few public pages give it anything to find: the calibration counts what it found and whether it was right. - How it was measured (
wave2/clef-eval, vision/results/clef/RESULTS.md): 1,856 crops of elements on public pages, 139 of them real problems, labelled by six AI labeller agents working in parallel, not people (208 crops labelled by two agents agreed with Cohen's kappa 0.705). Split by site: 412 crops (25 real) to choose the prompt and thresholds, 1,444 (114 real) to measure them once. The labels come from models too, so these numbers say how often Clef agreed with the labelling agents, on crops cut the way the page check cuts them. - Bounds. At most 40 elements per scan (at least 14, 13, 13 for the desktop, light phone and dark phone views, and a view may use what the views before it left), each asked as soon as its crop is taken and answered while the rest of the scan runs; an answer counts only if it comes within 4 seconds. When Cloudflare Workers AI does not answer, or answers late, the findings stay as measured. About 1,300 input tokens per element or fewer at $0.24 per million (Workers AI pricing, 2026-10-14): at most about $0.0125 for 40 elements.
- What is sent. The crop and the words above go to Cloudflare Workers AI, inside Cloudflare's network, with no account or visitor data. Cloudflare's Workers AI data-usage page says it does not use what is sent to train models, and stores it only when the account adds a storage service; this scanner does not. The crops a report shows are kept with its screenshots and deleted with them after 24 hours.
Signed in: the whole report and an AI review
Signed out, a report shows the score, the category scores, the screenshots and half of the findings. The rest are removed on our server before the report reaches your browser, so a shared link shows everyone the same half. Signed in to genclass.dev, with any account (a free one is enough, no card), it shows every finding, the race probe and an AI review.
The AI review is written by an OpenAI model when you open it: a summary, the fixes to make first, and real sites in the same field to learn from. What is sent: the report's score, categories, findings (what was measured and the suggested fix, cut short) and a few speed numbers, plus the page's title, description, headings and navigation labels, read once from the page. Nothing about you. The model can be wrong; it never changes the score or adds a finding. A suggested site is shown only when its address loads. The review is kept with the report and deleted with it after 24 hours.
The vibecoded factor (0 to 100: how much the page looks like it came straight out of an AI app builder or a starter template) is judged by an OpenAI vision model the first time a report is opened. Everyone sees the number; signed in, you also see the breakdown (fonts, images, layout and copy). What is sent: the desktop screenshot of the page and facts read from its HTML and stylesheets (the fonts it loads, AI-builder fingerprints such as Lovable's or v0's, UI kits such as shadcn/ui). It is an opinion, not a measurement, never part of the score, and is deleted with the report after 24 hours.
GenClass Vision (beta)
a second opinion, not scoredA small GenClass model (4.8 MB, 1.19 M parameters) looks at the same crop of each measured visual finding, inside the scan's own Cloudflare browser, and gives a score from 0 to 1. Each finding it saw shows it ("Vision 0.91"); the findings it flags at 0.752 or more that are not scored are listed on their own. It never changes a finding or the score.
- How it was measured (
wave2/vision-v2-student, vision/results/vision-v2/RESULTS.md): on 1,052 held-out crops it ranks problems about as well as Clef (average precision 0.611 against 0.615). Behind the measured rules, at this threshold, it kept 69 of their 500 flags, and 33 of those were real (48%; the rules alone: 15%). The labels come from AI labeller agents (Claude subagents), not people. - Beta. It was taught by OpenAI's Decisions API (gpt-6-luna, public beta), its family answer averaged with Clef's. None of its problem families passed the 60% precision bar on the test set, so nothing it says is scored.
Opting out
Site owners can stop the page check from loading their pages in two ways:
- Add a group to
robots.txt. The scanner reads only groups that name it, since a person asked for one page:
User-agent: GenClassScan Disallow: /
- Or email mehar@daybot.ca with the hostname. It goes on a block list the scanner checks before every scan, every redirect, every report view and every screenshot, subdomains included, so cached results and screenshots for it stop showing at once.
What is kept, and for how long
- The report (findings, timings and the address) is cached for 6 hours, so a shared link shows the same result without a new scan.
- Screenshots and reports are deleted 24 hours after the scan by an hourly sweep.
- For the model judge, crops of the screenshots (an element and 24 px around it) and the element's measured facts are sent to Cloudflare Workers AI, inside Cloudflare's network, with no account or visitor data. Cloudflare's Workers AI data-usage page says it does not use what is sent to train models, and stores it only when the account adds a storage service to it; this scanner does not. The crops a report shows are kept with its screenshots and deleted with them.
- GenClass Vision looks at the same crops inside the same Cloudflare browser. Nothing more is sent anywhere.
- The visitor's IP address is used only for the per-minute rate limit, as a hash, and is not stored.
- The browser starts empty for every scan and is closed afterwards. No cookies or storage carry over between scans.