Controllable pages to test LCA agentic self-healing. Same URL the whole time — the break is driven by a shared flag, so the agentic test never re-records from scratch.
- Button: repo > Actions > toggle-heal > Run workflow > pick
on/off/gone. - CLI:
./toggle.sh on/./toggle.sh off/./toggle.sh gone. - Pages reflect the change in ~30-60s. Then re-run the SAME saved agentic test URL:
on=> the captured locator is broken => heal fires;off=> healthy;gone=> the checkout action is removed entirely => heals fail until the budget exhausts (unhealable).
Resolution order in flag.js: ?v=2/?break/?gone (manual, per-tab) > localStorage.lcaBreak=1 (same browser) > flag.json (shared). ?gone=0/?break=false count as off.
checkout.html (Checkout drift + decoy; gone removes it), frames.html (iframe+shadow), validation.html (#status-badge drift; ?remove=1/?wrong=1), visual.html, timing.html (?delay), flow.html (mid-step drift), modal.html (random promo popup interrupts mid-flow; close-and-continue, blocked clicks logged; ?popup=1 forces / ?popup=0 suppresses), data.html (#token; ?use=), email.html (verify link/OTP), cookie.html, config.html (run-config inspector: injected headers, cookies, localStorage, basic-auth probe + egress IP/country for the IP-geolocation toggle), download.html (report.txt fixture in files/), upload.html (native OS file dialog; expects report.txt back), api.html (renders api/todo.json live + external httpbin failure legs), login.html (gated sign-in; exactly one credential pair is accepted, stored only as a salted PBKDF2-HMAC-SHA256 digest so the secret is never in the markup — every other pair gets one generic error, and a correct one opens a sessionStorage-backed session panel). signup.html / signin-email.html / inbox.html (the credential-resolution trio — see below). index.html has the 60-case map; 404.html catches dead URLs.
Three pages that make an agent's credential-resolution behaviour observable from the page, so a test never has to inspect a run log to prove it.
- signup.html — registration whose password policy is both stated on screen and enforced from the
same rule object (10–24 chars · upper · lower · digit · one of
!@#$%^&*()-_=+.· no spaces · no angle brackets), each rule exposed as[data-rule]withdata-ok. A verification code is mailed on submit. Fixtures:?strict=1adds an undisclosed rule — the first compliant password is always rejected once, and any password already tried this session is rejected again, so a run must produce a genuinely different value and cannot pass by resubmitting;?nomail=1never delivers;?expire=1mails a dead code;?link=1verifies by link instead. - inbox.html — a mailbox keyed by recipient (
?to=addr). Opening an address nothing was sent to gives an explicit empty state, which is how "did the run read the permanent or the temporary mailbox?" becomes a page-level assertion. A blockedlocalStoragereports its ownstorage-unavailablestate rather than masquerading as an empty mailbox. An expired confirmation link issues a fresh one instead of dead-ending. Break:driftmoves the verify-link and code ids whilearia-label="Verify"stays put — the stable accessible name is the heal anchor, same contract as email.html. - signin-email.html — the identity is an email address, and any well-formed address is accepted
with the shared password, so a run's own permanent address signs in without this page naming it.
Accounts made on signup.html are also accepted with their own password.
?otp=1/?link=1add a mailed challenge. Failed attempts persist in sessionStorage, sodata-attemptssurvives a reload and "exactly one attempt, then stop" stays provable across a navigation.
login.html is the deliberate counterpart and is intentionally left alone: it accepts only the
username lca_admin, so an email identity fails there by construction — that is the fixture for
"try the stored address once and then stop", while signin-email.html is the fixture for the same rule's
success path.
State it writes. lca.mailbox, lca.accounts (localStorage — the mailbox must be readable from a
second tab) and lca.signup.pending / lca.signup.tried / lca.signin-email.pending /
lca.signin-email.attempts (sessionStorage, tab-scoped). Two consequences worth knowing: config.html
enumerates localStorage, so these keys appear in its listing once an auth page has run in that profile;
and parallel runs sharing an origin share one mailbox, so give each run its own recipient address —
per-run temporary addresses do this naturally, a shared permanent address does not. Codes live 60
minutes, long enough that ordinary agent latency never expires one by accident.
Four pages for the TC × LCNC agentic surface — multi-tab, multi-window and the Custom-JS console. Shared plumbing lives in assets/tabkit.js (cross-context channel with per-opener token scoping, window.open wrapper that auto-names every window for exact child↔row binding, live registry, Smart-Tab identity parser + score calculator) and assets/tabtarget.js (the self-describing identity card every opened target renders; channel ids persist in sessionStorage so self-redirecting windows stay one registry row).
Docs alignment: the product records tab actions as "Open a new tab" / "Close a tab" / "Multiple tabs" (docs never name a literal "Switch tab" step), auto-records link-opened tabs and new windows, and ships Smart multi-tab handling — matching by URL/title with a manual Improve tab accuracy → Configure tab attributes dialog (Page title / Page URL / Query parameters / Full path parameter). Multi-window recording is documented as desktop browser tests only, for now. Custom JS is Custom actions (JavaScript): Custom action, Create variable (returns a string), Custom validation (returns true/false); imported variables read as variables.test['name'] etc. Async/Promise returns, thrown-error reporting and JS timeouts are not documented — the console page probes exactly those.
-
tabs.html — six openers, six outcomes: a real
target="_blank" rel="opener"anchor (tab B — Chrome defaults_blankto noopener, and whether script may close such a tab is the browser's call; the page reports what actually happened), a scripted open (tab C), an identical twin of B (same URL and title — only the auto-assigned window name separates them), the framed target (control inside an iframe), a tab that closes itself after 6 s, and a button labelled like an opener that opens nothing (the changeWitnessGate case). Approving anything in a child posts a code to the handoff inbox, so "switch back to tab 1 and validate" asserts a live fact. Break:driftslips a Notifications tab in front of the invoice (index 2);gonedrops the invoice entirely. Two popups in one gesture needs popup blocking off — true on Automate/LCNC launch paths; the registry marks blocked rows honestly. -
windows.html — SecurePay as a real
window.openpopup (480×680→ a window, not a tab; all routes open_blankdeliberately, since a named window would be silently reused by the next click). Routes: stays open · auto-closes 3 s after confirmation · two popups at once · self-redirects to?step=reviewafter 1.5 s (title AND URL change post-open — the capture-before-action trap). Verify with OTP inside the popup opens a grandchild window. Payments settle throughPOST api/v1/payments, OTP throughPOST api/v1/otp/verify(see Backend below) with byte-identical local fallbacks. Fixed refs:SP-77214, OTP902137. Break:driftauto-closes every route;goneopens nothing. -
smart-tab.html — the Smart Tab lab (spec: Confluence TB, flag
smart_tab_handling_whitelist). 19 launchers each move exactly one property: open order, tracking params, path, query, fragment classification, title, title-mutates-after-load, self-closing, inserted tab, identical twin, and a URL containing the literal stringdevtools(the spec's exclusion — a naive substring filter makes that legitimate tab unfindable). The score oracle runs the spec's arithmetic live and names the expected winner. Verified values on the deployed site (the base path counts as a real segment):/cart/checkout32 ·/store/cart/checkout20 ·/account/checkout17.5 (16.67 on a bare local server), clean URL ties at 32 with the same URL +utm_*/gclid/fbclid. Fragment weights only apply when the recorded tab carried a fragment — group E is classification-only on this page. Stated oracle assumptions the spec leaves open: order-independent segment match, recorded-count denominator. -
console.html — Custom actions (JavaScript), all three product types, each with a copy-paste snippet card and an assertable readout. Every value is deliberately out of reach of a normal step: a canvas-painted balance (no text node, no a11y node — the agent must reach for Custom JS unprompted or abstain),
::beforegenerated content, a shadow-root meter,sessionStorage/localStorage, a readonly discount field that only reacts to a dispatchedinputevent, an async hook (undocumented in the product docs — this probes it), a throwing hook, and a volatile ticket that proves a saved step re-executes instead of freezing its value.?ctx=Bopens a second context (ORD-99117vsORD-88231) so the returned value names the document the step really ran in. Break:driftrelocates every hook towindow.lca.v2.*and deletes the originals — the recorded snippet throws, and Custom JS is outsideAIAutoHealer's 7 healable types, so it must fail cleanly. -
customjs-fail.html — the failure side of Custom JS: 18 snippets that must fail a step, in five groups — never runs (SyntaxError, undefined global, null element, not-a-function) · throws (Error, bare string, inside a callback) · wrong return contract (the string
"false", missingreturn, object, null) · async and timeouts (pending Promise, rejection, never settles, value assigned after return, blocking sync loop) · no error but wrong outcome (.valuewithout a dispatched event, reaching into an iframe from the top document). Each card's displayed snippet is the string it executes, and Run it here grades it strictly against the documented contract (validation passes only on=== true, Create variable only on a non-empty string) — so the page is its own oracle and any product divergence is a finding. Verified in-browser: 14 fail as intended · 2 pass while being wrong · 2 decided by the executor · 0 unexpected. The twopasses wronglycards are the page's thesis — a throw inside asetTimeoutcallback and a.valueassignment with no event both raise no error at all while recording a wrong outcome (the fixture readout still says "No discount applied." with the field readingSAVE20). The twodependscards can't be graded by a browser: whether a pending Promise or a blocking loop fails rests entirely on whether the executor awaits and enforces a step timeout — both undocumented for Custom JS.Section F · breaks only on replay exists because an agentic recorder cannot author a failing step — its objective is a green test, so asked to add one it will rewrite the snippet into a passing validation, skip the step entirely, or run the snippet as a
page.evaluateprobe and never record it. All three come back green. So four flag-driven hooks return clean values while healthy (window.lcaQuote()→"Q-5591",lcaCheckStock()→true,lcaOrderId()→"ORD-88231",lcaTotal()→ the live total) and the same saved snippet fails on drift four different ways: never settles (step must time out), returns the string"false"(truthy trap), returnsnull, and returns a stale$149.00while the page moved to$199.00. Undergonethey throw or vanish instead. That is the only route to a genuine Custom-JS step failure through the normal record-then-replay lifecycle. The stale-read hook cross-checks itself against the live DOM, so the page reportsPASSES — wrongly · returned "$149.00" · but the page shows $199.00.
Targets: tabs/target.html (query-driven: ?tab ?title ?code ?titledrift ?autoclose ?slow), tabs/framed.html, the path family tabs/cart/checkout.html · tabs/account/checkout.html · tabs/store/cart/checkout.html, and windows/pay.html · windows/otp.html.
sw.js is a root-scoped service worker that behaves like a small same-origin REST API — real, DevTools-visible requests with POST semantics, latency and error legs, on a static host. Registered from assets/page.js and assets/tabkit.js on every page; state is in-memory, so each fresh browser session (every Automate VM) starts clean.
| Endpoint | Method | Behaviour |
|---|---|---|
api/v1/health |
GET | {engine:"service-worker"} when live; the static fixture answers {engine:"static-fixture"} when not |
api/v1/orders/INV-4417 |
GET | order state; status flips to paid after a payment this session |
api/v1/payments |
POST | 350 ms latency → 201 {status:"approved", ref:"SP-77214"}; action:"decline" → declined |
api/v1/otp/verify |
POST | 200 {accepted:true} for 902137, else 401 |
api/v1/tickets |
POST | 201 {ticket:"TKT-n"}, counts up per session |
api/v1/quote |
GET | 1200 ms latency → {quote:"Q-5591"} |
api/v1/status/{code} |
GET | that status code — deterministic local replacement for httpbin |
api/v1/latency/{ms} |
GET | delayed 200 (cap 10 s) |
Caveats by design: the SW never controls the very first navigation of a fresh session, so every wired page keeps a local fallback with byte-identical readout text; static *.json fixtures under api/v1/ serve GETs for anything that bypasses the browser (server-side API-step executors won't see the SW — its POST endpoints exist only in-browser). api.html's failure legs now use api/v1/status/* locally; httpbin is kept as the cross-origin leg only.
flags/index.html — reads the launched Chrome and reports which of the 8 Advanced-Configuration browser flags (auto screen-share, camera/mic, location, clipboard, incognito, block-all, dark mode, language) actually took effect. Point the Automate-with-AI Explore URL flow at this URL to verify a run's flags end to end; readouts are live DOM text + a machine-readable snapshot the agent can assert on. flags/language.html is the language deep-dive (localized content across all 11 presets, active row highlighted).
Confidence is marked per flag: strong (definitive JS signal) vs heuristic (incognito quota-gap, dark-mode screenshot swatch — verify by comparing on/off). Incognito on Chrome 133+: quota reports min(real, usage + 10 GiB) — a normal profile reads exactly usage + 10 GiB while incognito's RAM-derived cap (≈1–4 GiB) shows through, so the gap is the signal; it also catches the #1946 regression where the MCP page swap's newPage() dropped an --incognito session's agent tab into the normal profile.
The launch path matters: the live agentic path (preCreateSeleniumSession → BrowserFlagsHandler) applies flag args and prefs, dedupes repeats, and its defaults already allow clipboard (pref = 1) — so Clipboard Enabled is indistinguishable from default, is not counted toward the active-toggles metric (a zero-flag control run reads 0 / 7), and only Disabled is observable. Since lcnc-services #1946 + lcnc-backend #4486, the validation replay and Argus cached-run paths route through the same canonical handler + agent overlay (args and prefs), the locale finally applies on the live agent session (intl.accept_languages), and group-level popup-suppression defaults are overridable fallbacks — the user's catalog choice wins. All run types should now read identically on these pages; a per-run-type mismatch is the regression signal. (Pre-fix: the replay paths dropped user flags entirely and force-denied geolocation/camera/mic; the older pwCaps path appended a 7-flag hand-picked subset, args only.)
flags/diff.html — launch diff: renders the default LCNC Chrome command line (desktop-app or cloud-agentic baseline), verifies the baseline's JS-observable fingerprints (navigator.webdriver, window.gc, File-System-Access APIs stripped, autoplay policy…), and overlays the extra args/prefs inferred live from the 8 user flags — so you can see what a run added on top of the defaults. Probes auto-run when permission states are pre-decided (agentic runs), so it self-populates with zero clicks; ?noauto=1 disables.