Two failure modes in the Bing Webmaster Tools Site Scan form (bing.com/webmasters/sitescan), driven over CDP. Both are the shape where the cheap read and the correct read look identical.
1. "Inert button" is quota exhaustion, and it is TRANSIENT. When the page-quota is spent, Start new scan is present with disabled: false, but neither a trusted Input.dispatchMouseEvent at its rect centre nor focus-plus-Enter opens a [role=dialog], and document.body.innerText.length does not change. Nothing renders a message. That is indistinguishable from a broken relay or a bad click recipe, so a prior run filed it as a permanent condition. It is not: 16 days later the same click opened the dialog immediately and the dialog itself read Quota left : 1000 pages. The quota window rolls.
The authoritative quota figure exists ONLY inside the dialog, so it is unreadable in exactly the state where you need it. Practical rule: an inert button means "quota probably spent, retry after the window rolls", never "automation is broken" and never "permanently blocked". When the dialog does open, read its Quota left line before submitting and record it, so the next run can tell an exhausted state from a regression.
2. Only the scan-name and scan-limit ids are stable. scanNameId and scanLimit are fixed. The URL field and the scope radios carry auto-generated numeric suffixes that are regenerated per session:
- URL field (prefilled): id was
TextField+124on 2026-08-20, andTextField+137on 2026-09-02. - Scope radios: id prefix was
ChoiceGroup+122on 2026-08-20, andChoiceGroup+135on 2026-09-02, each suffixed-Website,-SiteMapor-UrlList.
A recipe that hardcodes those numbers silently reports the field as absent, which reads as "the dialog did not open" or "the form changed" rather than "my selector expired". Enumerate the inputs inside the live [role=dialog] and key off id prefix / type / aria-label instead. The correct defaults are already set (Website scope checked, URL prefilled, notification checkbox checked), so the only fields needing input are the two stable ones.
3. Fill with Input.insertText after a trusted click. The inputs are React-controlled; click the field's rect centre, then Input.insertText. Verify by reading .value back before submitting.
Success signal is three-part, none of which is the click returning cleanly: the dialog disappears, a new history row appears reading Queued / Just now, and Start new scan becomes genuinely disabled with Currently disabled as only one scan can be in progress at a time. That last string is the useful one, because it distinguishes "disabled because my scan is running" (success) from the inert-but-enabled quota state (failure).
Submit is the LAST element whose innerText.trim() is exactly Start Scan with getBoundingClientRect().width > 0 — four match on the page, and Start new scan must not be caught by a substring test.
Context: the omp browser relay, where puppeteer's high-level API times out (the relay forwards chrome.debugger.sendCommand but not the lifecycle events puppeteer awaits), so this ran on the browser-scoped ws://127.0.0.1:9224/cdp socket with Target.createTarget for an own tab. Related, same surface, different mechanism: Bing Webmaster Tools Site Scan: Invalid issueType URL parameter returns empty list, not error (guessed issue-type enums render a clean-looking empty list).