aboutsummaryrefslogtreecommitdiff
path: root/test-suite/challenges
diff options
context:
space:
mode:
authorTianren Dong <tianrendong.dtr@gmail.com>2026-05-16 14:01:00 -0400
committerTianren Dong <tianrendong.dtr@gmail.com>2026-05-16 14:01:00 -0400
commitb792f22f2c2ef4dfb7bd300e7381a186e34e0c18 (patch)
treea31a4e6d1213478f5ac96e2208d1a1c16fc825b9 /test-suite/challenges
parentf133011c8ecda06ae63068649a9714c88a7b901e (diff)
0.15.25: fix reload after stale singleton
Diffstat (limited to 'test-suite/challenges')
-rw-r--r--test-suite/challenges/39-strict-csp-fallback.html14
-rw-r--r--test-suite/challenges/39-strict-csp-fallback.js27
-rw-r--r--test-suite/challenges/40-dynamic-wait-readiness.html41
-rw-r--r--test-suite/challenges/41-tab-lifecycle.html44
4 files changed, 126 insertions, 0 deletions
diff --git a/test-suite/challenges/39-strict-csp-fallback.html b/test-suite/challenges/39-strict-csp-fallback.html
new file mode 100644
index 0000000..93bd9cf
--- /dev/null
+++ b/test-suite/challenges/39-strict-csp-fallback.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'none'">
+<title>39 strict CSP fallback</title>
+<link rel="stylesheet" href="../_style.css">
+<script src="../_lib.js"></script>
+<body>
+<main>
+ <p>Goal: page blocks <code>unsafe-eval</code>. Agent should fall back to screenshot/coordinate observation and click the target without relying on snapshot/evaluate.</p>
+ <p id="hint">The green target is fixed at known viewport coordinates. Use a screenshot, then click inside it.</p>
+ <button id="cspTarget" aria-label="strict CSP target">CSP TARGET</button>
+</main>
+<script src="39-strict-csp-fallback.js"></script>
+</body>
diff --git a/test-suite/challenges/39-strict-csp-fallback.js b/test-suite/challenges/39-strict-csp-fallback.js
new file mode 100644
index 0000000..4f2d91e
--- /dev/null
+++ b/test-suite/challenges/39-strict-csp-fallback.js
@@ -0,0 +1,27 @@
+Challenge.init({ id: "strict-csp-fallback", instructions: "use screenshot/coordinates; click CSP TARGET" });
+
+const btn = document.getElementById("cspTarget");
+btn.style.cssText = [
+ "position:fixed",
+ "left:220px",
+ "top:220px",
+ "width:180px",
+ "height:72px",
+ "font:700 16px system-ui",
+ "background:#1f7a1f",
+ "color:white",
+ "border:0",
+ "border-radius:10px",
+ "box-shadow:0 0 0 4px rgba(31,122,31,.25)"
+].join(";");
+
+document.getElementById("cspTarget").addEventListener("click", (e) => {
+ const r = btn.getBoundingClientRect();
+ const bad = [];
+ if (!e.isTrusted) bad.push("click isTrusted=false");
+ if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) {
+ bad.push(`click coordinates ${e.clientX},${e.clientY} outside target rect ${Math.round(r.left)},${Math.round(r.top)},${Math.round(r.right)},${Math.round(r.bottom)}`);
+ }
+ if (bad.length) Challenge.fail(...bad);
+ else Challenge.pass("strict CSP page completed via trusted viewport click");
+});
diff --git a/test-suite/challenges/40-dynamic-wait-readiness.html b/test-suite/challenges/40-dynamic-wait-readiness.html
new file mode 100644
index 0000000..adf006d
--- /dev/null
+++ b/test-suite/challenges/40-dynamic-wait-readiness.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>40 dynamic wait readiness</title>
+<link rel="stylesheet" href="../_style.css">
+<script src="../_lib.js"></script>
+<body>
+<main>
+ <p>Goal: wait until an async control exists and is enabled, then click it. Direct early clicks or fixed sleeps should not be required.</p>
+ <div id="status" role="status">Loading async action…</div>
+ <div id="mount" style="margin-top:20px"></div>
+</main>
+<script>
+Challenge.init({ id: "dynamic-wait-readiness", instructions: "wait for async button, then click" });
+const start = performance.now();
+setTimeout(() => {
+ const btn = document.createElement("button");
+ btn.id = "readyAction";
+ btn.textContent = "Run ready action";
+ btn.disabled = true;
+ btn.style.cssText = "padding:14px 22px;font-size:16px;background:#1f7a1f;color:#fff;border:0;border-radius:6px";
+ document.getElementById("mount").appendChild(btn);
+ document.getElementById("status").textContent = "Button mounted; enabling soon…";
+ Challenge.log("mounted", { at: performance.now() - start });
+ setTimeout(() => {
+ btn.disabled = false;
+ btn.dataset.ready = "true";
+ document.getElementById("status").textContent = "Ready.";
+ Challenge.log("enabled", { at: performance.now() - start });
+ }, 450);
+ btn.addEventListener("click", (e) => {
+ const elapsed = performance.now() - start;
+ const bad = [];
+ if (!e.isTrusted) bad.push("click isTrusted=false");
+ if (btn.disabled || btn.dataset.ready !== "true") bad.push("clicked before enabled/ready");
+ if (elapsed < 650) bad.push(`clicked too early at ${elapsed.toFixed(0)}ms`);
+ if (bad.length) Challenge.fail(...bad);
+ else Challenge.pass(`waited for async ready state (${elapsed.toFixed(0)}ms)`);
+ });
+}, 350);
+</script>
+</body>
diff --git a/test-suite/challenges/41-tab-lifecycle.html b/test-suite/challenges/41-tab-lifecycle.html
new file mode 100644
index 0000000..2ba002d
--- /dev/null
+++ b/test-suite/challenges/41-tab-lifecycle.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>41 tab lifecycle</title>
+<link rel="stylesheet" href="../_style.css">
+<script src="../_lib.js"></script>
+<body>
+<main>
+ <p>Goal: create a new tab, inspect it, close it, return to parent, then verify lifecycle state.</p>
+ <p id="msg"></p>
+ <button id="verify">Verify tab lifecycle</button>
+</main>
+<script>
+Challenge.init({ id: "tab-lifecycle", instructions: "new tab → inspect child → close child → return parent → Verify" });
+const qs = new URLSearchParams(location.search);
+const key = "pi-chrome-suite:tab-lifecycle:state";
+function readState(){ try { return JSON.parse(localStorage.getItem(key) || "{}"); } catch { return {}; } }
+function replaceState(next){ localStorage.setItem(key, JSON.stringify({ ...next, ts: Date.now() })); }
+function writeState(patch){ localStorage.setItem(key, JSON.stringify({ ...readState(), ...patch, ts: Date.now() })); }
+
+if (qs.get("child") === "1") {
+ document.title = "[CHILD] 41 tab lifecycle";
+ document.getElementById("msg").textContent = "Child tab opened. Close this tab, then return to parent.";
+ writeState({ opened: true, childUrl: location.href, childTitle: document.title });
+ window.addEventListener("pagehide", () => writeState({ closed: true, closedAt: Date.now() }));
+ window.addEventListener("beforeunload", () => writeState({ closed: true, closedAt: Date.now() }));
+ // Do not call Challenge.pass here: parent verification must be the only suite PASS.
+ Challenge.log("child-loaded", { url: location.href });
+} else {
+ document.getElementById("msg").textContent = "Parent tab. Runner should open this same file with ?child=1 in a new tab, inspect it, close it, then return here.";
+ replaceState({ parentLoaded: true, closed: false });
+ document.getElementById("verify").addEventListener("click", (e) => {
+ const s = readState();
+ const bad = [];
+ if (!e.isTrusted) bad.push("verify click isTrusted=false");
+ if (!s.opened) bad.push("child tab did not record opened=true");
+ if (!s.childUrl || !s.childUrl.includes("child=1")) bad.push("child URL not recorded");
+ if (!s.closed) bad.push("child tab close/pagehide was not recorded");
+ Challenge.log("tab-state", s);
+ if (bad.length) Challenge.fail(...bad);
+ else Challenge.pass("new/inspect/close/return tab lifecycle completed");
+ });
+}
+</script>
+</body>