Module: runner/playwrightSelectorGenerator

DIF-015b — Loader for Playwright's internal InjectedScript source so the recorder's in-page selectorGenerator() can delegate to Playwright's own, well-tested selector-generation algorithm instead of the hand-rolled heuristic.

Why this exists

The hand-rolled selectorGenerator() inside RECORDER_SCRIPT was producing lower-quality selectors than Playwright's codegen tool (no noise-scoring, no cross-ancestor scoring loop, no shadow-DOM traversal, no iframe locator chain). Rather than re-implement ~600 LOC of Playwright internals, we load Playwright's pre-bundled injectedScriptSource.js — the same IIFE Playwright itself injects into pages — and call InjectedScript.prototype.generateSelector(...) (or generateSelectorSimple(...), whichever is exposed in the pinned Playwright version).

Why it's best-effort with a fallback

InjectedScript's filename, constructor signature, and public methods all live under playwright-core/lib/server/injected/… which is explicitly marked as internal and not covered by Playwright's semver. A Renovate bump can move / rename symbols without notice. We therefore:

  1. Resolve and read the source at Node module-load time (not every recorder launch — the file is ~300 KB).
  2. Probe for the expected symbols at recorder launch time.
  3. Fall back silently to the hand-rolled selectorGenerator() on any failure, logging once per process via formatLogLine.

The hand-rolled fallback is the path every existing recorder test is pinned against, so "Playwright source missing" is a zero-regression outcome.

Source:

Members

(inner) cached :LoadedInjectedScriptSource|null

Type:
  • LoadedInjectedScriptSource | null
Source:

Methods

(static) buildInjectedBootstrapScript() → {string}

Build the in-page bootstrap snippet that:

  1. Evaluates Playwright's injectedScriptSource IIFE in page scope so pwExport (Playwright's own bundle export name) is defined.
  2. Constructs an InjectedScript instance with conservative defaults.
  3. Exposes window.__playwrightSelector(element) as the public entry point the recorder script will call.

API-surface uncertainty. Playwright marks lib/server/injected/* as internal and the constructor signature + public-method names of InjectedScript have shifted across minor releases. We feature-detect:

  • generateSelectorSimple(element) — returns a string directly (newer releases).
  • generateSelector(element, options) — may return { selector } or a string depending on version.
  • pwExport.InjectedScript — class export shape.
  • pwExport itself being the constructor (legacy). If none probe true, __playwrightSelector is left undefined and the recorder's hand-rolled fallback runs.

Returns the empty string when the source isn't loadable, so the caller can safely string-concat without a guard.

Source:
Returns:
Type
string

(static) loadPlaywrightInjectedScriptSource() → {LoadedInjectedScriptSource}

Resolve and read playwright-core/lib/server/injected/injectedScriptSource.js as a string. This is the pre-bundled IIFE Playwright ships for its own page-context injection — unlike the unbundled selectorGenerator.js, it is self-contained and has no require() calls (webpack has already inlined every dependency).

Result is cached for the lifetime of the Node process. Returns { available: false } on any failure; callers must check available before using source.

Source:
Returns:
Type
LoadedInjectedScriptSource

Type Definitions

LoadedInjectedScriptSource

Type:
  • Object
Properties:
Name Type Attributes Description
source string | null

Pre-bundled Playwright injected-script source as a string, or null when the bundle could not be resolved.

available boolean

true iff source is non-empty and safe to inject. Callers must check this before using source.

reason string <optional>

Diagnostic message describing why the bundle could not be loaded; only present when available === false.

Source: