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:
- Resolve and read the source at Node module-load time (not every recorder launch — the file is ~300 KB).
- Probe for the expected symbols at recorder launch time.
- Fall back silently to the hand-rolled
selectorGenerator()on any failure, logging once per process viaformatLogLine.
The hand-rolled fallback is the path every existing recorder test is pinned against, so "Playwright source missing" is a zero-regression outcome.
Members
(inner) cached :LoadedInjectedScriptSource|null
Type:
- LoadedInjectedScriptSource | null
Methods
(static) buildInjectedBootstrapScript() → {string}
Build the in-page bootstrap snippet that:
- Evaluates Playwright's
injectedScriptSourceIIFE in page scope sopwExport(Playwright's own bundle export name) is defined. - Constructs an
InjectedScriptinstance with conservative defaults. - 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.pwExportitself being the constructor (legacy). If none probe true,__playwrightSelectoris 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.
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.
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 |
|
available |
boolean |
|
|
reason |
string |
<optional> |
Diagnostic message describing why the
bundle could not be loaded; only present when |