diff options
| author | Igor Soarez <igor@soarez.org> | 2026-08-03 21:18:02 +0100 |
|---|---|---|
| committer | Igor Soarez <igor@soarez.org> | 2026-08-03 21:18:02 +0100 |
| commit | 495de0d5283dd3e4a6ef715b596c4a2892e95915 (patch) | |
| tree | aae2ec78d59cb96d8e90c56ab7626a6886d30be2 /test/format.test.ts | |
Web search for pi through an existing Chrome over CDP
Attaches to a browser that is already running — never launches one —
using the same endpoint configuration as pi-browser-harness, so a single
/browser-target choice governs both packages.
One tool, castle_cdp_search, deliberately not named web_search so it
coexists with pi-web-access rather than shadowing it.
Notes from validating against castle's Chrome:
- tbs=qdr:*, the parameter Google's own Tools menu writes, renders an
empty page on this profile; the older as_qdr=* works. Any date filter
combined with udm=14 is also empty, so recency drops udm.
- Target.createTarget must not be raced against the abort signal:
raceAbort abandons the promise but cannot cancel the command, and the
command's side effect is a tab nothing is left holding.
- A search cancelled while queued has to be removed from the semaphore
queue, or the slot handed to it later is never counted back.
- A decaying rate-limit block stops serving /sorry/ and returns an empty
results page instead, indistinguishable from a genuine zero-hit search.
Diffstat (limited to 'test/format.test.ts')
| -rw-r--r-- | test/format.test.ts | 52 |
1 files changed, 52 insertions, 0 deletions
diff --git a/test/format.test.ts b/test/format.test.ts new file mode 100644 index 0000000..3060a62 --- /dev/null +++ b/test/format.test.ts @@ -0,0 +1,52 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; + +import { formatResults } from "../src/format.ts"; +import { SearchChallengeError } from "../src/errors.ts"; + +const results = [ + { title: "First", url: "https://example.com/1", snippet: "A snippet." }, + { title: "Second", url: "https://example.com/2", snippet: "" }, +]; + +test("results are numbered, with the URL on its own line", () => { + const text = formatResults({ + query: "q", + searchUrl: "https://www.google.com/search?q=q", + finalUrl: "https://www.google.com/search?q=q", + endpoint: "10.88.0.25:9223 (built-in default)", + results, + }); + assert.match(text, /^Search results for "q" \(2 hits, via Chrome at 10\.88\.0\.25:9223 \(built-in default\)\)/); + assert.match(text, /^1\. First$/m); + assert.match(text, /^ {3}https:\/\/example\.com\/1$/m); + assert.match(text, /^2\. Second$/m); + // A missing snippet must not leave a stray blank indented line. + assert.ok(!/\n {3}\n/.test(text)); +}); + +test("a redirect is reported, because the query URL is then not where we looked", () => { + const text = formatResults({ + query: "q", + searchUrl: "https://www.google.com/search?q=q", + finalUrl: "https://www.google.com/search?q=q&sei=abc", + endpoint: "e", + results, + }); + assert.match(text, /^Landed on: https:\/\/www\.google\.com\/search\?q=q&sei=abc$/m); +}); + +test("the challenge error tells the agent to hand off to a human, with the URL", () => { + const err = new SearchChallengeError({ + challenge: "captcha-widget", + pageUrl: "https://www.google.com/sorry/index?continue=x", + endpoint: "10.88.0.25:9223 (built-in default)", + detail: "Our systems have detected unusual traffic", + }); + assert.equal(err.name, "SearchChallengeError"); + assert.ok(err instanceof Error, "must be throwable as an Error"); + assert.match(err.message, /captcha-widget/); + assert.match(err.message, /https:\/\/www\.google\.com\/sorry\/index\?continue=x/); + assert.match(err.message, /Ask the user/); + assert.match(err.message, /10\.88\.0\.25:9223/); +}); |
