koala73/worldmonitor · error

PlaywrightProvider does not support search mode. Use Exa ins

Error message

PlaywrightProvider does not support search mode. Use Exa instead.

What it means

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.

Source

Thrown at consumer-prices-core/src/acquisition/playwright.ts:58

      }

      const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout });

      if (opts.waitForSelector) {
        await page.waitForSelector(opts.waitForSelector, { timeout: 10_000 }).catch(() => {});
      }

      const html = await page.content();
      const statusCode = response?.status() ?? 200;

      return { url, html, statusCode, provider: this.name, fetchedAt: new Date() };
    } finally {
      await page.close();
    }
  }

  async search(_query: string, _opts?: SearchOptions): Promise<SearchResult[]> {
    throw new Error('PlaywrightProvider does not support search mode. Use Exa instead.');
  }

  async validate(): Promise<boolean> {
    try {
      await this.getContext();
      return true;
    } catch {
      return false;
    }
  }

  async teardown(): Promise<void> {
    const timeout = new Promise<void>(r => setTimeout(r, 5000));
    await Promise.race([
      Promise.allSettled([this.context?.close(), this.browser?.close()]),
      timeout,
    ]);
    this.context = null;

View on GitHub (pinned to a96956387a)

Solutions

  1. Route search to ExaProvider — search calls must resolve provider 'exa'
  2. Add a capability check before dispatching so only search-capable providers receive search work
  3. Fix the config: keep playwright in acquisition.provider/fallback (fetch) positions only

Example fix

// before — 'playwright' resolved for a search step
const provider = getProvider(config.provider); // === 'playwright'
await provider.search(query);

// after — dispatch search to a provider that supports it
const provider = getProvider('exa');
await provider.search(query);
Defensive patterns

Strategy: validation

Validate before calling

const SEARCH_CAPABLE = new Set(['exa', 'firecrawl', 'p0']); // playwright.search() throws by design
// before dispatching search work
if (!SEARCH_CAPABLE.has(config.provider)) {
  throw new Error(`provider '${config.provider}' cannot run search — use exa`);
}

Type guard

function supportsSearch(p: AcquisitionProvider): boolean {
  return p.name !== 'playwright'; // PlaywrightProvider.search is a hard throw
}

Try / catch

try {
  await provider.search(query);
} catch (err) {
  if (err instanceof Error && err.message.includes('does not support search mode')) {
    return await getProvider('exa').search(query); // reroute, do not retry
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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'.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@a96956387a (2026-08-21). Data as JSON: /api/errors/f3c19661285281ff. Report an issue: GitHub.