playwright-injected
v1.59.1
Published
Playwright's browser-side engine (InjectedScript, selector engine, locator utils, accessibility/ARIA utils, DOM utils) extracted as a standalone module.
Maintainers
Readme
playwright-injected
Playwright's browser-side engine extracted as a standalone module. Includes the InjectedScript (selector engine, element querying, state checking), locator conversion utilities, accessibility/ARIA utilities, and DOM helpers. This is the exact code that powers playwright codegen and the trace viewer.
Why
Playwright's locator generation, ARIA snapshots, and DOM inspection are all internal — there's no public API to use them, and no way to run them in the browser. This package extracts them as a standalone module you can import and call directly, in the browser without Playwright installed.
Use cases:
- AI / LLM browser agents — feed
ariaSnapshot()output to an LLM for structured page understanding, generate selectors for agent actions - Accessibility inspection — get accessible names, roles, descriptions, and visibility for any DOM element, using the same logic as Playwright's assertions
- Custom selector generation — get all candidate selectors for an element and apply your own prioritization logic, use custom test ID attributes (
data-cy,data-e2e,data-qa, etc.) - Browser extensions — embed Playwright's selector engine and inspector in a Chrome extension without a full Playwright install
- Locator conversion — convert between internal selectors and human-readable locator strings across JavaScript, Python, Java, and C#
Install
npm install playwright-injectedNo runtime dependencies. Everything is pre-bundled from Playwright's source at build time using esbuild.
Stability warning: This package exposes Playwright's internal APIs which are not part of Playwright's public contract and may change without notice between Playwright versions. Pin your version and test after upgrading. The core concepts (InjectedScript, selector generation, locator conversion) have been stable for years, but individual function signatures, types, and internal selector formats may shift.
Usage
import {
InjectedScript,
asLocator,
locatorOrSelectorAsSelector,
getByRoleSelector,
parseSelector,
getAriaRole,
getElementAccessibleName,
isElementVisible,
} from 'playwright-injected';
// --- Setup (once per page) ---
const injected = new InjectedScript(window, {
isUnderTest: false,
sdkLanguage: 'javascript',
testIdAttributeName: 'data-testid', // configurable: 'data-cy', 'data-e2e', etc.
stableRafCount: 0,
browserName: 'chromium',
customEngines: [],
});
// --- Generate a selector from a DOM element ---
const el = document.querySelector('button');
const { selector, selectors } = injected.generateSelector(el);
// selector => 'internal:role=button[name="Submit"i]' (best match)
// selectors => ['internal:role=button[name="Submit"i]', '#submit-btn', ...]
// (all candidates — filter/reorder for custom prioritization)
// --- Convert selectors <-> locator strings ---
asLocator('javascript', selector);
// => "getByRole('button', { name: 'Submit' })"
asLocator('python', selector);
// => "get_by_role('button', name='Submit')"
locatorOrSelectorAsSelector('javascript', "getByText('hello')", 'data-testid');
// => 'internal:text="hello"i'
// --- Build selectors programmatically ---
getByRoleSelector('button', { name: 'Submit' });
// => 'internal:role=button[name="Submit"i]'
// --- Query elements from a selector ---
const parsed = injected.parseSelector(selector);
const matches = injected.querySelectorAll(parsed, document);
// --- Element state ---
injected.elementState(el, 'visible'); // { matches: true, received: 'visible' }
injected.elementState(el, 'enabled'); // { matches: true, received: 'enabled' }
// --- Accessibility ---
// Same logic that powers Playwright's toHaveAccessibleName, toHaveRole, etc.
getAriaRole(el); // 'button'
getElementAccessibleName(el, false); // 'Submit'
isElementVisible(el); // true
// --- ARIA snapshots ---
// Structured page representation useful for LLM-driven agents
injected.ariaSnapshot(el, { mode: 'ai' });
// => '- button "Submit" [ref=e1]'
// --- Parse a selector into its AST ---
parseSelector('internal:role=button[name="OK"i]');
// => { parts: [{ name: 'internal:role', body: 'button[name="OK"i]', source: '...' }] }See app/playwright-helpers.ts for a more complete usage example showing how to build an element inspector on top of the library.
Vite plugin (advanced)
Most users should use the main
playwright-injectedimport above — it's pre-bundled and works out of the box with no extra setup. The Vite plugin is only needed if you want to pin to a specificplaywright-coreversion you already have installed and bundle directly from its source:
// vite.config.ts
import playwrightInjected from 'playwright-injected/vite';
export default defineConfig({
plugins: [playwrightInjected()],
});The plugin provides:
virtual:playwright-injected-- the InjectedScript as an ES module@playwright-isomorphic/*-- Playwright's isomorphic utilities
import { InjectedScript } from 'virtual:playwright-injected';
import { asLocator } from '@playwright-isomorphic/locatorGenerators';TypeScript: Add the type declarations via a triple-slash reference or in tsconfig.json:
/// <reference types="playwright-injected/virtual-playwright-injected" />Development
Setup
git clone https://github.com/kolodny/playwright-injected.git
cd playwright-injected
npm install
npm run sync # clones playwright, bundles source into src/generated/
npm run build # packages src/generated/ into dist/Syncing with Playwright
npm run sync clones the playwright repo on first run, then builds everything into src/generated/. To update to a new Playwright version:
cd playwright
git fetch && git checkout v1.XX.X # or main
cd ..
npm run sync
npm run buildHow it works
The build clones the Playwright repo and uses esbuild to bundle Playwright's browser-side internals into a single zero-dependency ESM bundle. Types are generated with tsc --declaration.
