aboutsummaryrefslogtreecommitdiff
path: root/test/deep-research
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmail.com>2026-07-08 21:24:32 +0530
committeramankumarsingh77 <amankumarsingh7702@gmail.com>2026-07-08 21:24:32 +0530
commit68bf7fd2988e42343833be09e4115cd7175029d0 (patch)
tree52b8eaf99fa7e4d383ff169dd3bdccfe13911217 /test/deep-research
parentc5c42df5976ed63faac2adcf3b04a4ad2f519b32 (diff)
feat: add browser_read_page reader-mode tool
URL (or owned targetId) → clean main-article text with nav/ads/footer boilerplate stripped, via a dependency-free readability heuristic. DOM walk lives in an in-page capture expression; scoring/selection is a pure, fixture- tested function (link-density + boilerplate-ancestor filtering, body-text fallback for structure-less pages). Reuses the isolated-attach lifecycle; registered unserialized (safe concurrent read). 10 extractor unit scenarios. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'test/deep-research')
-rw-r--r--test/deep-research/fixtures/article-capture.json17
-rw-r--r--test/deep-research/fixtures/no-article-capture.json10
-rw-r--r--test/deep-research/readability-test.ts46
3 files changed, 73 insertions, 0 deletions
diff --git a/test/deep-research/fixtures/article-capture.json b/test/deep-research/fixtures/article-capture.json
new file mode 100644
index 0000000..df9c67c
--- /dev/null
+++ b/test/deep-research/fixtures/article-capture.json
@@ -0,0 +1,17 @@
+{
+ "url": "https://blog.example.com/how-cdp-works",
+ "title": "How the Chrome DevTools Protocol Works",
+ "blocks": [
+ { "kind": "other", "text": "Home Products Blog About Contact", "linkTextLength": 33, "inBoilerplate": true },
+ { "kind": "heading", "text": "How the Chrome DevTools Protocol Works", "linkTextLength": 0, "inBoilerplate": false },
+ { "kind": "paragraph", "text": "The Chrome DevTools Protocol lets tools instrument, inspect, and control Chromium-based browsers over a WebSocket connection.", "linkTextLength": 0, "inBoilerplate": false },
+ { "kind": "paragraph", "text": "Each command targets a domain such as Page, Runtime, or Network, and receives a structured JSON response that the client correlates by id.", "linkTextLength": 0, "inBoilerplate": false },
+ { "kind": "heading", "text": "Sessions and Targets", "linkTextLength": 0, "inBoilerplate": false },
+ { "kind": "paragraph", "text": "A target is a page or worker; attaching to one yields a session id that scopes subsequent commands to that target without disturbing others.", "linkTextLength": 0, "inBoilerplate": false },
+ { "kind": "listitem", "text": "Attach with Target.attachToTarget", "linkTextLength": 0, "inBoilerplate": false },
+ { "kind": "listitem", "text": "Drive with Runtime.evaluate", "linkTextLength": 0, "inBoilerplate": false },
+ { "kind": "paragraph", "text": "Subscribe here for our newsletter and follow us on social media for updates", "linkTextLength": 55, "inBoilerplate": true },
+ { "kind": "other", "text": "Copyright 2026 Example Inc. Privacy Terms Cookies", "linkTextLength": 24, "inBoilerplate": true }
+ ],
+ "bodyText": "Home Products Blog How the Chrome DevTools Protocol Works The Chrome DevTools Protocol lets tools instrument inspect and control browsers Copyright 2026"
+}
diff --git a/test/deep-research/fixtures/no-article-capture.json b/test/deep-research/fixtures/no-article-capture.json
new file mode 100644
index 0000000..d367b7e
--- /dev/null
+++ b/test/deep-research/fixtures/no-article-capture.json
@@ -0,0 +1,10 @@
+{
+ "url": "https://app.example.com/dashboard",
+ "title": "Dashboard",
+ "blocks": [
+ { "kind": "other", "text": "Menu", "linkTextLength": 4, "inBoilerplate": true },
+ { "kind": "listitem", "text": "Widget A", "linkTextLength": 8, "inBoilerplate": true },
+ { "kind": "listitem", "text": "Widget B", "linkTextLength": 8, "inBoilerplate": true }
+ ],
+ "bodyText": "Dashboard Widget A Widget B Your account is active. Usage this month is within limits and no action is required at this time from you."
+}
diff --git a/test/deep-research/readability-test.ts b/test/deep-research/readability-test.ts
new file mode 100644
index 0000000..10f9a8b
--- /dev/null
+++ b/test/deep-research/readability-test.ts
@@ -0,0 +1,46 @@
+/**
+ * Unit tests for the pure readability extractor — no browser required.
+ *
+ * Run: npx tsx test/deep-research/readability-test.ts
+ */
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import { extractReadable, type PageCapture } from "../../src/domains/readpage/readability";
+
+let passed = 0;
+let failed = 0;
+const check = (cond: boolean, label: string): void => {
+ if (cond) {
+ passed++;
+ console.log(` ✓ ${label}`);
+ } else {
+ failed++;
+ console.error(` ✗ ${label}`);
+ }
+};
+
+const fixture = (name: string): PageCapture =>
+ JSON.parse(readFileSync(join(import.meta.dirname, "fixtures", name), "utf8")) as PageCapture;
+
+// S10: boilerplate stripped, article content preserved
+{
+ const page = extractReadable(fixture("article-capture.json"));
+ check(page.text.includes("Chrome DevTools Protocol lets tools instrument"), "S10: article body text is present");
+ check(page.text.includes("Sessions and Targets"), "S10: article headings are preserved");
+ check(!page.text.includes("Home Products Blog"), "S10: nav boilerplate is dropped");
+ check(!page.text.includes("Copyright 2026"), "S10: footer boilerplate is dropped");
+ check(!page.text.includes("Subscribe here for our newsletter"), "S10: high-link-density promo block is dropped");
+ check(page.text.includes("## Sessions and Targets"), "S10: paragraph/heading structure preserved as markdown");
+ check(page.title === "How the Chrome DevTools Protocol Works" && page.url.startsWith("https://"), "S10: title and url passed through");
+ check(page.wordCount > 40, "S10: word count reflects the article body");
+}
+
+// S11: no clear article node falls back to bounded body text
+{
+ const page = extractReadable(fixture("no-article-capture.json"));
+ check(page.text.length > 0, "S11: structure-less page falls back to non-empty body text");
+ check(page.text.includes("Your account is active"), "S11: fallback body text carries the real content");
+}
+
+console.log(`\n${passed} passed, ${failed} failed`);
+if (failed > 0) process.exit(1);