donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
124 lines • 5.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildLocator = buildLocator;
const TemplateInterpolator_1 = require("../../../utils/TemplateInterpolator");
/**
* Resolves any `{{$.env.X}}` placeholders in a step field against the
* supplied env data. Returns the input verbatim when no env data is given,
* or when the field has no placeholder syntax — backwards compatible with
* cached entries that contain literal values only.
*
* Only applied to `text`, `name`, and `testId` step fields. `selector`
* (CSS/XPath) and `frames[]` entries are left literal because raw env
* values cannot be safely embedded into a CSS selector without escaping.
*/
function resolveStepField(value, envData) {
if (!envData || !value.includes('{{')) {
return value;
}
return (0, TemplateInterpolator_1.interpolateString)(value, { env: envData, calls: [] });
}
/**
* Interpolate env placeholders, then optionally compile the result as a
* regex. Mirrors the order used by `buildAssertExecutor` so env-var × regex
* semantics stay consistent across cache executors.
*
* On `new RegExp(...)` failure (invalid pattern) the original string is
* returned, letting Playwright apply literal substring matching rather than
* throwing inside the cache replay path.
*/
function resolveAndCompile(value, isRegex, envData) {
const resolved = resolveStepField(value, envData);
if (!isRegex) {
return resolved;
}
try {
return new RegExp(resolved);
}
catch {
return resolved;
}
}
/**
* Mechanically construct a Playwright {@link Locator} from a structured
* {@link LocateResult}. No `eval` or string parsing — every branch maps to a
* direct Playwright API call.
*
* When `envData` is supplied, `{{$.env.X}}` placeholders inside `text`,
* `name`, and `testId` step fields are resolved against it before being
* applied. `selector` and `frames[]` are left untouched.
*/
function buildLocator(page, result, envData) {
// 1. Resolve frame chain (if any)
let frameScope;
if (result.frames && result.frames.length > 0) {
for (const frame of result.frames) {
frameScope = applyFrameStep(frameScope ?? page, frame);
}
}
// 2. Apply locator steps
const base = frameScope ?? page;
let locator = applyStep(base, result.steps[0], envData);
for (let i = 1; i < result.steps.length; i++) {
locator = applyStepToLocator(locator, result.steps[i], envData);
}
// 3. nth disambiguation
if (result.nth !== undefined) {
locator = locator.nth(result.nth);
}
return locator;
}
function applyFrameStep(parent, step) {
switch (step.method) {
case 'frameLocator':
return parent.frameLocator(step.selector ?? 'iframe');
case 'frame':
if (!step.name) {
throw new Error('FrameStep with method "frame" requires a name attribute');
}
return parent.frameLocator(`iframe[name="${step.name}"]`);
default:
throw new Error(`Unknown frame method: ${step.method}`);
}
}
function applyStep(base, step, envData) {
return applyStepTo(base, step, envData);
}
function applyStepToLocator(parent, step, envData) {
return applyStepTo(parent, step, envData);
}
function applyStepTo(parent, step, envData) {
// `exact` and `*IsRegex` are mutually exclusive. If the AI emits both
// (shouldn't happen — the prompt forbids it), regex wins because passing
// `exact: true` with a `RegExp` matcher to Playwright is meaningless.
const exactOpt = step.exact !== undefined ? { exact: step.exact } : undefined;
switch (step.method) {
case 'getByRole': {
const roleOpts = {};
if (step.name !== undefined) {
roleOpts.name = resolveAndCompile(step.name, step.nameIsRegex, envData);
}
if (step.exact !== undefined && !step.nameIsRegex) {
roleOpts.exact = step.exact;
}
return parent.getByRole((step.role ?? 'generic'), Object.keys(roleOpts).length > 0 ? roleOpts : undefined);
}
case 'getByText':
return parent.getByText(resolveAndCompile(step.text ?? '', step.textIsRegex, envData), step.textIsRegex ? undefined : exactOpt);
case 'getByLabel':
return parent.getByLabel(resolveAndCompile(step.text ?? '', step.textIsRegex, envData), step.textIsRegex ? undefined : exactOpt);
case 'getByPlaceholder':
return parent.getByPlaceholder(resolveAndCompile(step.text ?? '', step.textIsRegex, envData), step.textIsRegex ? undefined : exactOpt);
case 'getByTestId':
return parent.getByTestId(resolveStepField(step.testId ?? '', envData));
case 'locator':
// `selector` is a raw CSS/XPath string — interpolating env values into
// it can produce invalid syntax silently. The locate prompt steers the
// AI toward semantic locators when env values are involved; cached
// selectors stay literal.
return parent.locator(step.selector ?? '*');
default:
throw new Error(`Unknown locator method: ${step.method}`);
}
}
//# sourceMappingURL=buildLocator.js.map