{"record":{"id":"f3c19661285281ff","repo":"koala73/worldmonitor","slug":"playwrightprovider-does-not-support-search-mode-u","errorCode":null,"errorMessage":"PlaywrightProvider does not support search mode. Use Exa instead.","messagePattern":"PlaywrightProvider does not support search mode\\. Use Exa instead\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"consumer-prices-core/src/acquisition/playwright.ts","lineNumber":58,"sourceCode":"      }\n\n      const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout });\n\n      if (opts.waitForSelector) {\n        await page.waitForSelector(opts.waitForSelector, { timeout: 10_000 }).catch(() => {});\n      }\n\n      const html = await page.content();\n      const statusCode = response?.status() ?? 200;\n\n      return { url, html, statusCode, provider: this.name, fetchedAt: new Date() };\n    } finally {\n      await page.close();\n    }\n  }\n\n  async search(_query: string, _opts?: SearchOptions): Promise<SearchResult[]> {\n    throw new Error('PlaywrightProvider does not support search mode. Use Exa instead.');\n  }\n\n  async validate(): Promise<boolean> {\n    try {\n      await this.getContext();\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  async teardown(): Promise<void> {\n    const timeout = new Promise<void>(r => setTimeout(r, 5000));\n    await Promise.race([\n      Promise.allSettled([this.context?.close(), this.browser?.close()]),\n      timeout,\n    ]);\n    this.context = null;","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/koala73/worldmonitor/blob/a96956387a927b8cd7aa34b0c41fca357e746be9/consumer-prices-core/src/acquisition/playwright.ts#L40-L76","documentation":"PlaywrightProvider implements the AcquisitionProvider interface but only for browser fetching: search() is a hard, unconditional throw because a local headless browser cannot perform web search. Any code path that resolves provider 'playwright' for a search operation hits this immediately. It is a deterministic contract violation, not a runtime condition — the message itself says to use Exa.","triggerScenarios":"An AcquisitionConfig with provider or fallback set to 'playwright' passed to a code path that calls search(); iterating the registry's provider map and calling search() on each entry; a config-driven chain that lists playwright under a search/discovery step.","commonSituations":"Copy-pasting a retailer acquisition config that worked for scrape into a discovery/search context; generic code that walks all providers assuming uniform capability; test suites exercising 'every provider implements search'.","solutions":["Route search to ExaProvider — search calls must resolve provider 'exa'","Add a capability check before dispatching so only search-capable providers receive search work","Fix the config: keep playwright in acquisition.provider/fallback (fetch) positions only"],"exampleFix":"// before — 'playwright' resolved for a search step\nconst provider = getProvider(config.provider); // === 'playwright'\nawait provider.search(query);\n\n// after — dispatch search to a provider that supports it\nconst provider = getProvider('exa');\nawait provider.search(query);","handlingStrategy":"validation","validationCode":"const SEARCH_CAPABLE = new Set(['exa', 'firecrawl', 'p0']); // playwright.search() throws by design\n// before dispatching search work\nif (!SEARCH_CAPABLE.has(config.provider)) {\n  throw new Error(`provider '${config.provider}' cannot run search — use exa`);\n}","typeGuard":"function supportsSearch(p: AcquisitionProvider): boolean {\n  return p.name !== 'playwright'; // PlaywrightProvider.search is a hard throw\n}","tryCatchPattern":"try {\n  await provider.search(query);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('does not support search mode')) {\n    return await getProvider('exa').search(query); // reroute, do not retry\n  }\n  throw err;\n}","preventionTips":["Model provider capabilities in config (fetch vs search) instead of assuming interface implies support","Assert provider roles at startup: every search-mode step must resolve 'exa'","Keep playwright out of discovery chains — it is fetch-only"],"tags":["playwright","provider-capability","search-api","contract-violation"],"backgroundTag":"unsupported-operation","analyzedSha":"a96956387a927b8cd7aa34b0c41fca357e746be9","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}