/* All working test components. Each exports a key in window.TestComponents */ const TestComponents = {}; /* ---------- Text Input ---------- */ TestComponents["text-input"] = function TextInputTest({ resetKey }) { const [value, setValue] = useState(""); const [label, setLabel] = useState("Button"); useEffect(() => { setValue(""); setLabel("Button"); }, [resetKey]); return (
setValue(e.target.value)} />
); }; /* ---------- Client Side Delay ---------- */ TestComponents["client-side-delay"] = function ClientDelayTest({ resetKey }) { const [phase, setPhase] = useState("idle"); // idle | running | done useEffect(() => { setPhase("idle"); }, [resetKey]); const start = () => { setPhase("running"); setTimeout(() => setPhase("done"), 5000); }; return (
{phase === "done" && (
Computation complete · 42 ops finished
)}
); }; /* ---------- AJAX Data (real fetch — visible in DevTools Network) ---------- */ TestComponents["ajax-data"] = function AjaxDataTest({ resetKey }) { const [phase, setPhase] = useState("idle"); // idle | loading | done | error const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { setPhase("idle"); setData(null); setError(null); }, [resetKey]); const fetchData = async () => { setPhase("loading"); setError(null); setData(null); try { // POST a fake records payload to httpbin's delay endpoint. The // server holds the response for ~3 seconds and then echoes the // body back under `json`, giving us: // • a real, slow network request candidates can intercept // • a real records array in the response they can count. const RECORD_COUNT = 24; const records = Array.from({ length: RECORD_COUNT }, (_, i) => ({ id: i + 1, name: `Record #${i + 1}`, value: Math.floor(Math.random() * 1000), })); const res = await fetch("https://httpbin.org/delay/3", { method: "POST", headers: { "Content-Type": "application/json" }, cache: "no-store", body: JSON.stringify({ records }), }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = await res.json(); // httpbin's /delay endpoint sometimes echoes the body under `json` // (already parsed) and sometimes only as a raw string in `data`. // Try both so the record count always reflects what the server // actually returned. let returned = []; if (Array.isArray(json?.json?.records)) { returned = json.json.records; } else if (typeof json?.data === "string" && json.data.length) { try { const parsed = JSON.parse(json.data); if (Array.isArray(parsed?.records)) returned = parsed.records; } catch (_) { /* leave returned as [] */ } } setData({ count: returned.length, records: returned }); setPhase("done"); } catch (e) { setError(e.message || "Network error"); setPhase("error"); } }; return (
{phase === "done" && data && (
✓ Loaded {data.count} records · 200 OK
)} {phase === "error" && (
✗ Request failed: {error}
)}
); }; /* ---------- Scrollbars ---------- */ TestComponents["scrollbars"] = function ScrollbarsTest({ resetKey }) { const [clicked, setClicked] = useState(false); const containerRef = useRef(null); useEffect(() => { setClicked(false); if (containerRef.current) containerRef.current.scrollTo(0, 0); }, [resetKey]); return (
{clicked && (
✓ Target reached and clicked.
)}
); }; /* ---------- Dynamic Table ---------- */ TestComponents["dynamic-table"] = function DynamicTableTest({ resetKey }) { const rows = [ { Name: "Chrome", CPU: "12.3%", Memory: "1.2 GB", Network: "245 KB/s", Battery: "—" }, { Name: "Firefox", CPU: "8.1%", Memory: "896 MB", Network: "112 KB/s", Battery: "—" }, { Name: "Safari", CPU: "3.4%", Memory: "612 MB", Network: "0 KB/s", Battery: "—" }, { Name: "Edge", CPU: "5.8%", Memory: "742 MB", Network: "82 KB/s", Battery: "—" }, { Name: "Slack", CPU: "2.1%", Memory: "412 MB", Network: "14 KB/s", Battery: "—" }, ]; const columnOrders = useMemo(() => [ ["Name", "CPU", "Memory", "Network", "Battery"], ["Name", "Memory", "CPU", "Battery", "Network"], ["Name", "Battery", "Network", "CPU", "Memory"], ["Name", "Network", "CPU", "Battery", "Memory"], ], []); const [orderIdx, setOrderIdx] = useState(0); useEffect(() => { setOrderIdx((i) => (i + 1) % columnOrders.length); }, [resetKey, columnOrders.length]); const cols = columnOrders[orderIdx]; const chrome = rows.find((r) => r.Name === "Chrome"); return (
Chrome CPU: {chrome.CPU}
{cols.map((c) => ( ))} {rows.map((r) => ( {cols.map((c) => ( ))} ))}
{c}
{r[c]}
); }; /* ---------- Progress Bar (no visible % label) ---------- */ TestComponents["progress-bar"] = function ProgressBarTest({ resetKey }) { const [pct, setPct] = useState(0); const [running, setRunning] = useState(false); const tickRef = useRef(null); useEffect(() => { setPct(0); setRunning(false); if (tickRef.current) clearInterval(tickRef.current); }, [resetKey]); useEffect(() => () => { if (tickRef.current) clearInterval(tickRef.current); }, []); const start = () => { if (running || pct >= 100) return; setRunning(true); tickRef.current = setInterval(() => { setPct((p) => { if (p >= 100) { clearInterval(tickRef.current); return 100; } return p + 1; }); }, 100); }; const stop = () => { setRunning(false); if (tickRef.current) clearInterval(tickRef.current); }; const finishedAt75 = !running && pct === 75; return (
{!running && pct > 0 && (
{finishedAt75 ? `✓ Stopped at exactly 75%.` : `✗ Stopped at ${pct}%. Target: 75%.`}
)}
); }; /* ---------- Visibility ---------- */ TestComponents["visibility"] = function VisibilityTest({ resetKey }) { const [hidden, setHidden] = useState(null); useEffect(() => { setHidden(null); }, [resetKey]); const targetStyles = { null: {}, display: { display: "none" }, visibility: { visibility: "hidden" }, opacity: { opacity: 0, pointerEvents: "none" }, offscreen: { position: "absolute", left: "-9999px" }, "zero-size": { width: 0, height: 0, padding: 0, overflow: "hidden", border: 0 }, covered: {}, }; const techniques = [ { key: "display", label: "Hide A" }, { key: "visibility", label: "Hide B" }, { key: "opacity", label: "Hide C" }, { key: "offscreen", label: "Hide D" }, { key: "zero-size", label: "Hide E" }, { key: "covered", label: "Hide F" }, ]; return (
I am the target {hidden === "covered" && (
)}
{techniques.map((t) => ( ))}
); }; /* ---------- Overlapped Element (must click first) ---------- */ TestComponents["overlapped-element"] = function OverlappedTest({ resetKey }) { const [email, setEmail] = useState(""); const [clickedField, setClickedField] = useState(false); const [warn, setWarn] = useState(false); const ref = useRef(null); useEffect(() => { setEmail(""); setClickedField(false); setWarn(false); if (ref.current) ref.current.scrollTo(0, 0); }, [resetKey]); // Capture typing-without-click as failure: track keydown when not focused via click const onKeyDown = (e) => { if (!clickedField) { e.preventDefault(); setWarn(true); } }; return (
⓵ Personal info (scroll inside this box)
{ setClickedField(true); setWarn(false); }} onKeyDown={onKeyDown} onChange={(e) => clickedField && setEmail(e.target.value)} onBlur={() => setClickedField(false)} placeholder="you@example.com" />
{warn && (
✗ Typed without clicking the field first. Click the email field first, then type.
)} {!warn && email && (
✓ Email captured: {email}
)}
); }; /* ---------- Shadow DOM ---------- */ TestComponents["shadow-dom"] = function ShadowDomTest({ resetKey }) { const hostRef = useRef(null); const [outerEcho, setOuterEcho] = useState(""); useEffect(() => { const host = hostRef.current; if (!host) return; let root = host.shadowRoot; if (!root) root = host.attachShadow({ mode: "open" }); root.innerHTML = `
custom component
`; const input = root.querySelector('[data-testid="shadow-input"]'); const submit = root.querySelector('[data-testid="shadow-submit"]'); const result = root.querySelector('[data-testid="shadow-result"]'); const handler = () => { const v = input.value; result.style.display = v ? "block" : "none"; result.textContent = v ? `✓ Submitted: ${v}` : ""; setOuterEcho(v); }; submit.addEventListener("click", handler); return () => submit.removeEventListener("click", handler); }, [resetKey]); return (
{outerEcho && (
Outer page received: {outerEcho}
)}
); }; /* ---------- File Upload ---------- */ TestComponents["file-upload"] = function FileUploadTest({ resetKey }) { const [files, setFiles] = useState([]); const [dragging, setDragging] = useState(false); const inputRef = useRef(null); useEffect(() => { setFiles([]); if (inputRef.current) inputRef.current.value = ""; }, [resetKey]); const addFiles = (list) => { const next = Array.from(list).map((f) => ({ name: f.name, size: f.size, type: f.type || "application/octet-stream" })); setFiles((prev) => [...prev, ...next]); }; return (
{files.length > 0 && (
{files.map((f, i) => (
{f.name} {(f.size/1024).toFixed(1)} KB
))}
)}
); }; /* ---------- Mystery Button (was Frames; no iframe hint anywhere on the page) ---------- */ TestComponents["mystery-button"] = function MysteryButtonTest({ resetKey }) { const [counter, setCounter] = useState(0); useEffect(() => { setCounter(0); }, [resetKey]); const frameHtml = useMemo(() => { return `
(button must be clicked to win)
`; }, []); useEffect(() => { const onMsg = (e) => { if (e.data?.type === "mystery-click") setCounter((n) => n + 1); }; window.addEventListener("message", onMsg); return () => window.removeEventListener("message", onMsg); }, []); const [k, setK] = useState(0); useEffect(() => { setK((x) => x + 1); }, [resetKey]); return (