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.

What it does

  1. 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.txt for a GenClassScan group.
  2. 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 20 seconds, then up to 4 more for the network to go quiet.
  3. Runs the visual checks in each view for about 6 seconds: the measured checks built for the GenClass runtime (in testing; not in runtime 0.2.1), 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.
  4. Takes a screenshot of the top of the page in each view and records the box of every finding.
  5. On the desktop page, with GenClass 0.2.1 loaded in observe mode (telemetry off, model from genclass.dev/model/), types a, ab, abc into 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.
  6. Opens the page once more with GenClass in guard mode and repeats the probe, to measure what guard changes.
  7. Scores the findings and keeps the report for 6 hours. The whole scan has a hard budget of 45 seconds. 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, and GenClass diagnoses other than a duplicate) are listed but not scored.

What it never does

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

How the score works

scan-score/2

The score starts at 100. Each finding takes points by its severity; each category can lose at most its cap, so one noisy category cannot take the page to zero. The same page with the same findings always gets the same score.

score = 100 − Σcategory min(cap, Σfindings in it points(severity))
points: high 10, medium 5, low 2 · cap: 25 per category · four categories
CategoryCap
ReadabilityText that cannot be read, template or placeholder text, garbled characters, fonts and icon fonts that failed to load25
Images and mediaImages, background images, video and audio that failed to load, stretched images25
LayoutStylesheets that failed or were blocked, CSS variables defined nowhere, sideways scrolling, no viewport meta tag, layout shifts, an empty page25
Interaction and racesA page that ignores clicks or cannot scroll, controls under fixed bars, hidden focus, covered dialogs, a double click that sends a request twice, problems GenClass's model diagnosed25
Table 1. Categories and the most each can take.
FindingCategorySeverityPoints
Text that is almost invisible on its background
text-invisible
Readabilityhigh10
Template or debug text shows on the page
text-template-leak
Readabilityhigh10
Placeholder content shows on the page
text-placeholder
Readabilitymedium5
Characters are garbled
text-encoding
Readabilitymedium5
A web font failed to load
font-failed
Readabilitylow2
An icon font failed to load
icon-font-failed
Readabilitymedium5
An image failed to load
image-broken
Images and mediamedium5
A CSS background image failed to load
image-css-broken
Images and mediamedium5
A video or audio element has nothing it can play
media-failed
Images and mediamedium5
An image is stretched or squashed
image-distorted
Images and mediamedium5
A stylesheet failed to load
stylesheet-failed
Layouthigh10
The Content-Security-Policy blocked a file the page uses
csp-blocked
Layoutmedium5
A CSS variable is used but defined nowhere
css-var-undefined
Layoutlow2
The page scrolls sideways
page-overflow-x
Layouthigh10
Phones show the desktop layout, zoomed out
viewport-meta-missing
Layouthigh10
Content jumps while the page loads
layout-shift
Layoutmedium5
The main part of the page shows nothing
page-blank
Layouthigh10
An invisible layer swallows clicks
click-blockedNot run in the page check: it needs a visitor's clicks
Interaction and raceshigh10
The whole page ignores clicks
pointer-events-leak
Interaction and raceshigh10
The page cannot scroll
scroll-lock-stuck
Interaction and raceshigh10
A control is stuck under a fixed bar
control-unreachable
Interaction and raceshigh10
A focused element is hidden under a sticky bar
focus-obscured
Interaction and racesmedium5
An open dialog is covered, or its buttons are off-screen
dialog-covered
Interaction and raceshigh10
A double click sent the same request twice
duplicate-submit
Interaction and raceshigh10
GenClass flagged a timing problem
genclass-detection
Interaction and raceshigh10
Table 2. Every kind of finding, its category and weight. The same element with the same problem in two views counts once.

The checks are the measured visual checks built for the GenClass runtime (in testing, not in runtime 0.2.1), 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, and its visual half uses code that is not released yet: the visual fixes are in testing for a release after runtime 0.2.1, and runtime 0.2.1, which the install line on the page check loads, does not have them. In the same browser, the visual checks run in guard mode with their fixes on, as the coming 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.

Opting out

Site owners can stop the page check from loading their pages in two ways:

User-agent: GenClassScan
Disallow: /

What is kept, and for how long