microsoft/playwright · error · Error
"${engine.name}" selector engine has been already registered
Error message
"${engine.name}" selector engine has been already registered What it means
Thrown by Selectors.register when the engine name was already added to the _engines map by a prior successful register() call. Each custom engine name must be unique within a Selectors instance; re-registering is treated as a programming error rather than an update.
Source
Thrown at packages/playwright-core/src/server/selectors.ts:65
'aria-ref'
]);
this._builtinEnginesInMainWorld = new Set([
'_react', '_vue',
]);
this._engines = new Map();
this._testIdAttributeName = testIdAttributeName ?? 'data-testid';
for (const engine of engines)
this.register(engine);
}
register(engine: channels.SelectorEngine) {
if (!engine.name.match(/^[a-zA-Z_0-9-]+$/))
throw new Error('Selector engine name may only contain [a-zA-Z0-9_] characters');
// Note: we keep 'zs' for future use.
if (this._builtinEngines.has(engine.name) || engine.name === 'zs' || engine.name === 'zs:light')
throw new Error(`"${engine.name}" is a predefined selector engine`);
if (this._engines.has(engine.name))
throw new Error(`"${engine.name}" selector engine has been already registered`);
this._engines.set(engine.name, engine);
}
testIdAttributeName(): string {
return this._testIdAttributeName;
}
setTestIdAttributeName(testIdAttributeName: string) {
this._testIdAttributeName = testIdAttributeName;
}
parseSelector(selector: string | ParsedSelector, strict: boolean) {
const parsed = typeof selector === 'string' ? parseSelector(selector) : selector;
let needsMainWorld = false;
visitAllSelectorParts(parsed, part => {
const name = part.name;
const custom = this._engines.get(name);
if (!custom && !this._builtinEngines.has(name))View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Guard registration: only register if the engine is not already present (check before calling register).
- Centralize engine registration in a single module imported once at startup.
- Use a Set/flag to make registration idempotent across HMR reloads.
Example fix
// before
selectors.register({ name: 'my-engine', query: () => {...} });
// imported again elsewhere → throws
// after — idempotent guard
if (!selectors._engines.has('my-engine'))
selectors.register({ name: 'my-engine', query: () => {...} }); Defensive patterns
Strategy: validation
Validate before calling
// Guard against double registration (e.g. under HMR)
function registerOnce(selectors: any, engine: any) {
if (!selectors._engines.has(engine.name))
selectors.register(engine);
}
registerOnce(selectors, { name: 'my-engine', query: () => {/*...*/} }); Type guard
function isEngineRegistered(selectors: any, name: string): boolean {
return selectors._engines.has(name);
} Try / catch
try {
selectors.register(engine);
} catch (e) {
if (/has been already registered/.test(e.message)) return; // idempotent
throw e;
} Prevention
- Centralize engine registration in one module imported a single time.
- Guard register() with a has() check to make it idempotent under HMR/re-imports.
- Track registered names in a Set to avoid duplicate calls across test files.
When it happens
Trigger: Calling selectors.register(engine) twice with the same name; a setup module that runs register() being imported more than once (e.g. HMR reload, duplicate imports) without guarding.
Common situations: Hot-module reloading re-running a registration module; a shared helper that registers the same engine in multiple test files that all load; copy-paste registering 'foo' in two places.
Related errors
- Selector engine name may only contain [a-zA-Z0-9_] character
- "${engine.name}" is a predefined selector engine
- Unknown engine "${name}" while parsing selector ${stringifyS
- Function "${name}" has been already registered
- Function "${name}" has been already registered in one of the
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/53a843c33286517c.
Report an issue: GitHub.