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/search-url.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/search-url.test.ts')
| -rw-r--r-- | test/search-url.test.ts | 35 |
1 files changed, 35 insertions, 0 deletions
diff --git a/test/search-url.test.ts b/test/search-url.test.ts new file mode 100644 index 0000000..13f58d1 --- /dev/null +++ b/test/search-url.test.ts @@ -0,0 +1,35 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; + +import { buildSearchUrl } from "../src/search.ts"; + +const paramsOf = (url: string): URLSearchParams => new URL(url).searchParams; + +test("a plain query asks for the Web tab in English", () => { + const url = buildSearchUrl({ query: "typebox json schema", numResults: 10 }); + assert.equal(new URL(url).origin + new URL(url).pathname, "https://www.google.com/search"); + const p = paramsOf(url); + assert.equal(p.get("q"), "typebox json schema"); + assert.equal(p.get("num"), "10"); + assert.equal(p.get("hl"), "en"); + assert.equal(p.get("udm"), "14"); + assert.equal(p.get("tbs"), null); +}); + +test("recency uses as_qdr and drops udm — tbs and udm both render an empty page", () => { + const cases = { day: "d", week: "w", month: "m", year: "y" } as const; + for (const [recency, expected] of Object.entries(cases)) { + const url = buildSearchUrl({ query: "q", numResults: 5, recency: recency as keyof typeof cases }); + const p = paramsOf(url); + assert.equal(p.get("as_qdr"), expected, recency); + assert.equal(p.get("tbs"), null, `${recency}: tbs must not be used`); + assert.equal(p.get("udm"), null, `${recency}: udm must be dropped alongside a date filter`); + } +}); + +test("queries with characters that would break a URL are encoded", () => { + const query = 'site:example.com "exact phrase" a&b?c=d #frag +plus/slash'; + const url = buildSearchUrl({ query, numResults: 3 }); + assert.equal(paramsOf(url).get("q"), query); + assert.ok(!url.includes(" "), "no raw spaces in the URL"); +}); |
