UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

124 lines 5.32 kB
"use strict"; 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