UNPKG

playwright-json-runner

Version:

Extends Playwright to run tests using JSON-based test definitions.

1,076 lines (1,058 loc) 37 kB
'use strict'; var zod = require('zod'); require('playwright'); var cosmiconfig = require('cosmiconfig'); var path2 = require('path'); var default2 = require('playwright/test'); var jsdom = require('jsdom'); var fs = require('fs'); var glob = require('glob'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var path2__default = /*#__PURE__*/_interopDefault(path2); var default2__namespace = /*#__PURE__*/_interopNamespace(default2); var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget); var withLabelSchema = zod.z.object({ label: zod.z.string().optional() }); var withDescriptionSchema = zod.z.object({ description: zod.z.string().optional() }); var testObjectSchema = withLabelSchema.merge(withDescriptionSchema); zod.z.object({ checked: zod.z.boolean().optional(), disabled: zod.z.boolean().optional(), exact: zod.z.boolean().optional(), expanded: zod.z.boolean().optional(), includeHidden: zod.z.boolean().optional(), level: zod.z.number().optional(), // For name, accept string or RegExp. // If you strictly need to parse only real RegExp objects at runtime, keep it like this. // If you want to accept a "string that might be a pattern," consider a string-based approach. name: zod.z.union([zod.z.string(), zod.z.instanceof(RegExp)]).optional(), pressed: zod.z.boolean().optional(), selected: zod.z.boolean().optional() }).optional(); var PlaywrightRoleSchema = zod.z.enum([ "alert", "alertdialog", "application", "article", "banner", "blockquote", "button", "caption", "cell", "checkbox", "code", "columnheader", "combobox", "complementary", "contentinfo", "definition", "deletion", "dialog", "directory", "document", "emphasis", "feed", "figure", "form", "generic", "grid", "gridcell", "group", "heading", "img", "insertion", "link", "list", "listbox", "listitem", "log", "main", "marquee", "math", "meter", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "navigation", "none", "note", "option", "paragraph", "presentation", "progressbar", "radio", "radiogroup", "region", "row", "rowgroup", "rowheader", "scrollbar", "search", "searchbox", "separator", "slider", "spinbutton", "status", "strong", "subscript", "superscript", "switch", "tab", "table", "tablist", "tabpanel", "term", "textbox", "time", "timer", "toolbar", "tooltip", "tree", "treegrid", "treeitem" ]); // src/schemas/locators/locator-parameters.ts var nthField = { nth: zod.z.number().optional() }; var selectorLocatorSchema = zod.z.object({ by: zod.z.literal("selector"), value: zod.z.string().describe("CSS selector"), ...nthField }); var xpathLocatorSchema = zod.z.object({ by: zod.z.literal("xpath"), value: zod.z.string().describe("XPath expression"), ...nthField }); var roleLocatorSchema = zod.z.object({ by: zod.z.literal("role"), role: PlaywrightRoleSchema, name: zod.z.union([zod.z.string(), zod.z.instanceof(RegExp)]).optional(), exact: zod.z.boolean().optional(), checked: zod.z.boolean().optional(), disabled: zod.z.boolean().optional(), expanded: zod.z.boolean().optional(), includeHidden: zod.z.boolean().optional(), level: zod.z.number().optional(), pressed: zod.z.boolean().optional(), selected: zod.z.boolean().optional(), ...nthField }); var textLocatorSchema = zod.z.object({ by: zod.z.literal("text"), value: zod.z.string(), exact: zod.z.boolean().optional(), ...nthField }); var labelLocatorSchema = zod.z.object({ by: zod.z.literal("label"), value: zod.z.string(), exact: zod.z.boolean().optional(), ...nthField }); var placeholderLocatorSchema = zod.z.object({ by: zod.z.literal("placeholder"), value: zod.z.string(), exact: zod.z.boolean().optional(), ...nthField }); var altTextLocatorSchema = zod.z.object({ by: zod.z.literal("altText"), value: zod.z.string(), exact: zod.z.boolean().optional(), ...nthField }); var titleLocatorSchema = zod.z.object({ by: zod.z.literal("title"), value: zod.z.string(), exact: zod.z.boolean().optional(), ...nthField }); var testIdLocatorSchema = zod.z.object({ by: zod.z.literal("testId"), value: zod.z.string(), ...nthField }); var locatorParamsSchema; var nestedLocatorSchema = zod.z.lazy( () => zod.z.object({ by: zod.z.literal("nested"), parent: locatorParamsSchema, child: locatorParamsSchema, ...nthField }) ); var customLocatorSchema = zod.z.object({ by: zod.z.string(), value: zod.z.string().optional(), ...nthField }); locatorParamsSchema = zod.z.union([ zod.z.discriminatedUnion("by", [ selectorLocatorSchema, xpathLocatorSchema, roleLocatorSchema, textLocatorSchema, labelLocatorSchema, placeholderLocatorSchema, altTextLocatorSchema, titleLocatorSchema, testIdLocatorSchema ]), nestedLocatorSchema, customLocatorSchema ]); // src/schemas/test-action.ts var timeoutOpts = zod.z.object({ timeout: zod.z.number().optional() }).optional(); var interactOpts = zod.z.object({ timeout: zod.z.number().optional(), force: zod.z.boolean().optional(), trial: zod.z.boolean().optional() }).optional(); var waitUntilEnum = zod.z.enum(["commit", "domcontentloaded", "load", "networkidle"]); var loadStateEnum = zod.z.enum(["domcontentloaded", "load", "networkidle"]); var waitForStateEnum = zod.z.enum(["attached", "detached", "visible", "hidden"]); var base = testObjectSchema; var withLocator = base.extend({ locator: locatorParamsSchema }); var navigateActionSchema = base.extend({ action: zod.z.literal("navigate"), url: zod.z.string(), options: zod.z.object({ timeout: zod.z.number().optional(), waitUntil: waitUntilEnum.optional() }).optional() }); var sleepActionSchema = base.extend({ action: zod.z.literal("sleep"), duration: zod.z.number().describe("milliseconds to wait") }); var waitForURLActionSchema = base.extend({ action: zod.z.literal("waitForURL"), url: zod.z.string(), options: zod.z.object({ timeout: zod.z.number().optional(), waitUntil: waitUntilEnum.optional() }).optional() }); var goBackActionSchema = base.extend({ action: zod.z.literal("goBack"), options: zod.z.object({ timeout: zod.z.number().optional(), waitUntil: waitUntilEnum.optional() }).optional() }); var goForwardActionSchema = base.extend({ action: zod.z.literal("goForward"), options: zod.z.object({ timeout: zod.z.number().optional(), waitUntil: waitUntilEnum.optional() }).optional() }); var waitForLoadStateActionSchema = base.extend({ action: zod.z.literal("waitForLoadState"), state: loadStateEnum.optional(), options: timeoutOpts }); var assertURLActionSchema = base.extend({ action: zod.z.literal("assertURL"), value: zod.z.string(), options: timeoutOpts }); var assertTitleActionSchema = base.extend({ action: zod.z.literal("assertTitle"), value: zod.z.string(), options: timeoutOpts }); var scrollActionSchema = base.extend({ action: zod.z.literal("scroll"), deltaX: zod.z.number().describe("Horizontal scroll pixels"), deltaY: zod.z.number().describe("Vertical scroll pixels") }); var waitForTextActionSchema = base.extend({ action: zod.z.literal("waitForText"), value: zod.z.string(), options: zod.z.object({ timeout: zod.z.number().optional(), state: waitForStateEnum.optional() }).optional() }); var clickCoordinatesActionSchema = base.extend({ action: zod.z.literal("clickCoordinates"), x: zod.z.number(), y: zod.z.number(), options: zod.z.object({ button: zod.z.enum(["left", "middle", "right"]).optional(), clickCount: zod.z.number().optional(), delay: zod.z.number().optional() }).optional() }); var screenshotActionSchema = base.extend({ action: zod.z.literal("screenshot"), locator: locatorParamsSchema.optional(), path: zod.z.string().optional(), options: zod.z.object({ fullPage: zod.z.boolean().optional(), timeout: zod.z.number().optional() }).optional() }); var assertSnapshotActionSchema = base.extend({ action: zod.z.literal("assertSnapshot"), locator: locatorParamsSchema.optional(), name: zod.z.string().optional().describe("Baseline snapshot filename"), options: zod.z.object({ timeout: zod.z.number().optional(), maxDiffPixels: zod.z.number().optional(), maxDiffPixelRatio: zod.z.number().optional(), threshold: zod.z.number().optional() }).optional() }); var clickActionSchema = withLocator.extend({ action: zod.z.literal("click"), options: zod.z.object({ button: zod.z.enum(["left", "middle", "right"]).optional(), clickCount: zod.z.number().optional(), delay: zod.z.number().optional(), force: zod.z.boolean().optional(), timeout: zod.z.number().optional(), trial: zod.z.boolean().optional() }).optional() }); var dblclickActionSchema = withLocator.extend({ action: zod.z.literal("dblclick"), options: interactOpts }); var fillActionSchema = withLocator.extend({ action: zod.z.literal("fill"), value: zod.z.string(), options: zod.z.object({ force: zod.z.boolean().optional(), timeout: zod.z.number().optional(), noWaitAfter: zod.z.boolean().optional() }).optional() }); var typeActionSchema = withLocator.extend({ action: zod.z.literal("type"), value: zod.z.string(), options: zod.z.object({ delay: zod.z.number().optional(), timeout: zod.z.number().optional() }).optional() }); var pressActionSchema = withLocator.extend({ action: zod.z.literal("press"), key: zod.z.string().describe("Key or chord, e.g. 'Enter', 'Tab', 'Control+a'"), options: zod.z.object({ delay: zod.z.number().optional(), timeout: zod.z.number().optional() }).optional() }); var checkActionSchema = withLocator.extend({ action: zod.z.literal("check"), options: interactOpts }); var uncheckActionSchema = withLocator.extend({ action: zod.z.literal("uncheck"), options: interactOpts }); var selectOptionActionSchema = withLocator.extend({ action: zod.z.literal("selectOption"), value: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]), options: zod.z.object({ force: zod.z.boolean().optional(), timeout: zod.z.number().optional() }).optional() }); var hoverActionSchema = withLocator.extend({ action: zod.z.literal("hover"), options: interactOpts }); var focusActionSchema = withLocator.extend({ action: zod.z.literal("focus"), options: timeoutOpts }); var blurActionSchema = withLocator.extend({ action: zod.z.literal("blur"), options: timeoutOpts }); var clearActionSchema = withLocator.extend({ action: zod.z.literal("clear"), options: interactOpts }); var scrollIntoViewActionSchema = withLocator.extend({ action: zod.z.literal("scrollIntoView"), options: timeoutOpts }); var waitForActionSchema = withLocator.extend({ action: zod.z.literal("waitFor"), state: waitForStateEnum.optional(), options: timeoutOpts }); var waitForHiddenActionSchema = withLocator.extend({ action: zod.z.literal("waitForHidden"), options: timeoutOpts }); var waitForSelectorActionSchema = withLocator.extend({ action: zod.z.literal("waitForSelector"), options: timeoutOpts }); var setFieldValueActionSchema = withLocator.extend({ action: zod.z.literal("setFieldValue"), value: zod.z.string(), options: timeoutOpts }); var assertFieldValueEqualsActionSchema = withLocator.extend({ action: zod.z.literal("assertFieldValueEquals"), value: zod.z.string(), options: timeoutOpts }); var assertFieldValueContainsActionSchema = withLocator.extend({ action: zod.z.literal("assertFieldValueContains"), value: zod.z.string(), options: timeoutOpts }); var assertVisibleSchema = withLocator.extend({ action: zod.z.literal("assertVisible"), options: timeoutOpts }); var assertHiddenSchema = withLocator.extend({ action: zod.z.literal("assertHidden"), options: timeoutOpts }); var assertEnabledSchema = withLocator.extend({ action: zod.z.literal("assertEnabled"), options: timeoutOpts }); var assertDisabledSchema = withLocator.extend({ action: zod.z.literal("assertDisabled"), options: timeoutOpts }); var assertCheckedSchema = withLocator.extend({ action: zod.z.literal("assertChecked"), checked: zod.z.boolean().optional().describe("Default true; set false to assert unchecked"), options: timeoutOpts }); var assertTextSchema = withLocator.extend({ action: zod.z.literal("assertText"), value: zod.z.string(), options: zod.z.object({ timeout: zod.z.number().optional(), ignoreCase: zod.z.boolean().optional() }).optional() }); var assertContainsTextSchema = withLocator.extend({ action: zod.z.literal("assertContainsText"), value: zod.z.string(), options: zod.z.object({ timeout: zod.z.number().optional(), ignoreCase: zod.z.boolean().optional() }).optional() }); var assertValueSchema = withLocator.extend({ action: zod.z.literal("assertValue"), value: zod.z.string(), options: timeoutOpts }); var assertCountSchema = withLocator.extend({ action: zod.z.literal("assertCount"), count: zod.z.number(), options: timeoutOpts }); var assertAttributeSchema = withLocator.extend({ action: zod.z.literal("assertAttribute"), attribute: zod.z.string(), value: zod.z.string(), options: timeoutOpts }); var customActionSchema = base.extend({ action: zod.z.string(), locator: locatorParamsSchema.optional(), value: zod.z.string().optional() }).passthrough(); var knownActionsSchema = zod.z.discriminatedUnion("action", [ navigateActionSchema, sleepActionSchema, goBackActionSchema, goForwardActionSchema, scrollActionSchema, waitForURLActionSchema, waitForLoadStateActionSchema, waitForTextActionSchema, clickCoordinatesActionSchema, assertURLActionSchema, assertTitleActionSchema, screenshotActionSchema, assertSnapshotActionSchema, clickActionSchema, dblclickActionSchema, fillActionSchema, typeActionSchema, pressActionSchema, checkActionSchema, uncheckActionSchema, selectOptionActionSchema, hoverActionSchema, focusActionSchema, blurActionSchema, clearActionSchema, scrollIntoViewActionSchema, waitForActionSchema, waitForHiddenActionSchema, waitForSelectorActionSchema, assertVisibleSchema, assertHiddenSchema, assertEnabledSchema, assertDisabledSchema, assertCheckedSchema, assertTextSchema, assertContainsTextSchema, assertValueSchema, assertCountSchema, assertAttributeSchema, setFieldValueActionSchema, assertFieldValueEqualsActionSchema, assertFieldValueContainsActionSchema ]); var testActionSchema = zod.z.union([knownActionsSchema, customActionSchema]); var testStepSchema = testObjectSchema.extend({ description: zod.z.string(), actions: zod.z.array(testActionSchema) }); // src/schemas/test-scenario.ts var testScenarioSchema = testObjectSchema.extend({ name: zod.z.string(), steps: zod.z.array(testStepSchema) }); // src/schemas/test-run.ts testObjectSchema.extend({ browser: zod.z.enum(["chrome", "firefox", "webkit"]), host: zod.z.string(), scenarios: zod.z.array(testScenarioSchema) }); // node_modules/@playwright/test/index.mjs var test_exports = {}; __export(test_exports, { default: () => default2__namespace.default }); __reExport(test_exports, default2__namespace); async function setLocatorValue(locator, value, options) { var _a2; const config3 = getConfiguration(); const timeout = (_a2 = options == null ? void 0 : options.timeout) != null ? _a2 : config3.fieldValueTimeout; await locator.waitFor({ state: "attached", timeout }); const html = await locator.evaluate((el) => el.outerHTML); const ruleMatch = getRuleMatch(html, config3); if (ruleMatch && config3.setterStrategies[ruleMatch.id]) { const targetLocator = ruleMatch.matchedChild && ruleMatch.xpath ? locator.locator(ruleMatch.xpath) : locator; return await config3.setterStrategies[ruleMatch.id]({ locator: targetLocator, ruleMatch, value }); } throw new Error(`No matching setter rule for element: ${html.substring(0, 200)}`); } async function getLocatorValue(locator, options) { var _a2; const config3 = getConfiguration(); const timeout = (_a2 = options == null ? void 0 : options.timeout) != null ? _a2 : config3.fieldValueTimeout; await locator.waitFor({ state: "attached", timeout }); const html = await locator.evaluate((el) => el.outerHTML); const ruleMatch = getRuleMatch(html, config3); if (ruleMatch && config3.getterStrategies[ruleMatch.id]) { const targetLocator = ruleMatch.matchedChild && ruleMatch.xpath ? locator.locator(ruleMatch.xpath) : locator; return await config3.getterStrategies[ruleMatch.id]({ locator: targetLocator, ruleMatch }); } throw new Error(`No matching getter rule for element: ${html.substring(0, 200)}`); } function getRuleMatch(html, config3) { var _a2; const { document } = new jsdom.JSDOM(html).window; const root = (_a2 = document.body.firstElementChild) != null ? _a2 : document.body; if (!root) return null; let xpathMatch; let matchedChild; const xpathEval = (xpath) => { const result = document.evaluate(xpath, root, null, 5, null); const firstNode = result.iterateNext(); if (!firstNode) return false; const hasMultiple = !!result.iterateNext(); xpathMatch = xpath; matchedChild = !hasMultiple && root !== firstNode; return true; }; for (const [id, condition] of Object.entries(config3.rules)) { try { xpathMatch = void 0; matchedChild = void 0; if (condition({ document, element: root, xpathEval })) { return { id, xpath: xpathMatch, matchedChild: matchedChild != null ? matchedChild : false }; } } catch (err) { console.error("Error executing rule:", id); throw err; } } return null; } // src/defaults/action-type-handlers.ts function requireLocator(context, actionName) { if (!context.locator) { throw new Error(`Action "${actionName}" requires a locator.`); } return context.locator; } var actionTypeHandlers = { // ── Page-level ──────────────────────────────────────────────────────────── navigate: async ({ page }, action) => { const a = action; await page.goto(a.url, a.options); }, sleep: async ({ page }, action) => { var _a2; const a = action; await page.waitForTimeout((_a2 = a.duration) != null ? _a2 : 0); }, waitForURL: async ({ page }, action) => { const a = action; await page.waitForURL(a.url, a.options); }, goBack: async ({ page }, action) => { await page.goBack(action.options); }, goForward: async ({ page }, action) => { await page.goForward(action.options); }, scroll: async ({ page }, action) => { const a = action; await page.mouse.wheel(a.deltaX, a.deltaY); }, waitForText: async ({ page }, action) => { const a = action; await page.getByText(a.value).waitFor(a.options); }, clickCoordinates: async ({ page }, action) => { const a = action; await page.mouse.click(a.x, a.y, a.options); }, waitForLoadState: async ({ page }, action) => { const a = action; await page.waitForLoadState(a.state, a.options); }, assertURL: async ({ page }, action) => { const a = action; await (0, test_exports.expect)(page).toHaveURL(a.value, a.options); }, assertTitle: async ({ page }, action) => { const a = action; await (0, test_exports.expect)(page).toHaveTitle(a.value, a.options); }, screenshot: async (context, action) => { const a = action; if (context.locator) { await context.locator.screenshot({ path: a.path, ...a.options }); } else { await context.page.screenshot({ path: a.path, ...a.options }); } }, assertSnapshot: async (context, action) => { const a = action; const nameArg = a.name ? [a.name] : []; if (context.locator) { await (0, test_exports.expect)(context.locator).toHaveScreenshot(...nameArg, a.options); } else { await (0, test_exports.expect)(context.page).toHaveScreenshot(...nameArg, a.options); } }, // ── Locator interactions ────────────────────────────────────────────────── click: async (context, action) => { const locator = requireLocator(context, "click"); await locator.click(action.options); }, dblclick: async (context, action) => { const locator = requireLocator(context, "dblclick"); await locator.dblclick(action.options); }, fill: async (context, action) => { const locator = requireLocator(context, "fill"); const a = action; await locator.fill(a.value, a.options); }, type: async (context, action) => { const locator = requireLocator(context, "type"); const a = action; await locator.pressSequentially(a.value, a.options); }, press: async (context, action) => { const locator = requireLocator(context, "press"); const a = action; await locator.press(a.key, a.options); }, check: async (context, action) => { const locator = requireLocator(context, "check"); await locator.check(action.options); }, uncheck: async (context, action) => { const locator = requireLocator(context, "uncheck"); await locator.uncheck(action.options); }, selectOption: async (context, action) => { const locator = requireLocator(context, "selectOption"); const a = action; await locator.selectOption(a.value, a.options); }, hover: async (context, action) => { const locator = requireLocator(context, "hover"); await locator.hover(action.options); }, focus: async (context, action) => { const locator = requireLocator(context, "focus"); await locator.focus(action.options); }, blur: async (context, action) => { const locator = requireLocator(context, "blur"); await locator.blur(action.options); }, clear: async (context, action) => { const locator = requireLocator(context, "clear"); await locator.clear(action.options); }, scrollIntoView: async (context, action) => { const locator = requireLocator(context, "scrollIntoView"); await locator.scrollIntoViewIfNeeded(action.options); }, waitFor: async (context, action) => { const locator = requireLocator(context, "waitFor"); const a = action; await locator.waitFor({ state: a.state, ...a.options }); }, waitForHidden: async (context, action) => { const locator = requireLocator(context, "waitForHidden"); await locator.waitFor({ state: "hidden", ...action.options }); }, waitForSelector: async (context, action) => { const locator = requireLocator(context, "waitForSelector"); await locator.waitFor({ state: "visible", ...action.options }); }, // ── Field value actions ─────────────────────────────────────────────────── setFieldValue: async (context, action) => { const locator = requireLocator(context, "setFieldValue"); const a = action; await setLocatorValue(locator, a.value, a.options); }, assertFieldValueEquals: async (context, action) => { const locator = requireLocator(context, "assertFieldValueEquals"); const a = action; const actual = await getLocatorValue(locator, a.options); (0, test_exports.expect)(actual).toBe(a.value); }, assertFieldValueContains: async (context, action) => { const locator = requireLocator(context, "assertFieldValueContains"); const a = action; const actual = await getLocatorValue(locator, a.options); (0, test_exports.expect)(actual).toContain(a.value); }, // ── Assertions ──────────────────────────────────────────────────────────── assertVisible: async (context, action) => { const locator = requireLocator(context, "assertVisible"); await (0, test_exports.expect)(locator).toBeVisible(action.options); }, assertHidden: async (context, action) => { const locator = requireLocator(context, "assertHidden"); await (0, test_exports.expect)(locator).toBeHidden(action.options); }, assertEnabled: async (context, action) => { const locator = requireLocator(context, "assertEnabled"); await (0, test_exports.expect)(locator).toBeEnabled(action.options); }, assertDisabled: async (context, action) => { const locator = requireLocator(context, "assertDisabled"); await (0, test_exports.expect)(locator).toBeDisabled(action.options); }, assertChecked: async (context, action) => { const locator = requireLocator(context, "assertChecked"); const a = action; await (0, test_exports.expect)(locator).toBeChecked({ checked: a.checked, ...a.options }); }, assertText: async (context, action) => { const locator = requireLocator(context, "assertText"); const a = action; await (0, test_exports.expect)(locator).toHaveText(a.value, a.options); }, assertContainsText: async (context, action) => { const locator = requireLocator(context, "assertContainsText"); const a = action; await (0, test_exports.expect)(locator).toContainText(a.value, a.options); }, assertValue: async (context, action) => { const locator = requireLocator(context, "assertValue"); const a = action; await (0, test_exports.expect)(locator).toHaveValue(a.value, a.options); }, assertCount: async (context, action) => { const locator = requireLocator(context, "assertCount"); const a = action; await (0, test_exports.expect)(locator).toHaveCount(a.count, a.options); }, assertAttribute: async (context, action) => { const locator = requireLocator(context, "assertAttribute"); const a = action; await (0, test_exports.expect)(locator).toHaveAttribute(a.attribute, a.value, a.options); } }; var action_type_handlers_default = actionTypeHandlers; // src/defaults/getter-setter-rules.ts var getterSetterRules = { "input.datepicker": ({ element }) => element.matches(".custom-datepicker"), checkbox: ({ xpathEval }) => xpathEval("//input[@type='checkbox']"), radio: ({ xpathEval }) => xpathEval("//input[@type='radio']"), contenteditable: ({ element }) => element.matches("[contenteditable='true'], [contenteditable='']") || element.querySelector("[contenteditable='true'], [contenteditable='']") !== null, "select.bootstrap": ({ element }) => element.querySelector(".dropdown-toggle, [data-bs-toggle='dropdown'], [data-toggle='dropdown']") !== null, select: ({ xpathEval }) => xpathEval("//select"), text: ({ xpathEval }) => xpathEval("//input | //textarea") }; var getter_setter_rules_default = getterSetterRules; // src/defaults/getter-strategies.ts var getterStrategies = { checkbox: async ({ locator }) => { const checked = await locator.isChecked(); return String(checked); }, radio: async ({ locator }) => { const isInput = await locator.evaluate((el) => el.tagName.toLowerCase() === "input"); if (isInput) { return await locator.isChecked() ? await locator.inputValue() : null; } const checked = locator.locator('input[type="radio"]:checked'); const count = await checked.count(); return count > 0 ? await checked.inputValue() : null; }, contenteditable: async ({ locator }) => { return await locator.innerText(); }, "select.bootstrap": async ({ locator }) => { const toggle = locator.locator( ".dropdown-toggle, [data-bs-toggle='dropdown'], [data-toggle='dropdown']" ); return await toggle.innerText(); }, select: async ({ locator }) => { const selectedOption = locator.locator("option:checked"); return selectedOption ? await selectedOption.innerText() : ""; }, text: async ({ locator }) => { return await locator.inputValue(); }, "input.datepicker": async ({ locator }) => { return await locator.inputValue(); } }; var getter_strategies_default = getterStrategies; // src/defaults/setter-strategies.ts var setterStrategies = { checkbox: async ({ locator, value }) => { const shouldBeChecked = (value == null ? void 0 : value.toLowerCase()) === "true" || value === "1"; await locator.setChecked(shouldBeChecked); }, radio: async ({ locator, value }) => { const isInput = await locator.evaluate((el) => el.tagName.toLowerCase() === "input"); if (isInput) { await locator.check(); } else { await locator.locator(`input[type="radio"][value="${value}"]`).check(); } }, contenteditable: async ({ locator, value }) => { await locator.click(); await locator.evaluate((el) => el.innerHTML = ""); await locator.pressSequentially(value != null ? value : ""); }, "select.bootstrap": async ({ locator, value }) => { const toggle = locator.locator( ".dropdown-toggle, [data-bs-toggle='dropdown'], [data-toggle='dropdown']" ); await toggle.click(); const menu = locator.locator(".dropdown-menu"); const byValue = menu.locator(`.dropdown-item[data-value="${value}"]`); if (await byValue.count() > 0) { await byValue.first().click(); } else { await menu.locator(".dropdown-item", { hasText: value }).click(); } }, select: async ({ locator, value }) => { const byValue = locator.locator(`option[value="${value}"]`); if (await byValue.count() > 0) { await locator.selectOption({ value }); } else { await locator.selectOption({ label: value }); } }, text: async ({ locator, value }) => { await locator.fill(value != null ? value : ""); }, "input.datepicker": async ({ locator, value }) => { await locator.click(); await locator.page().locator(`//button[text()='${value}']`).click(); } }; var setter_strategies_default = setterStrategies; // src/locator-resolver.ts async function resolveLocator(strategies, page, params) { const handler = strategies[params.by]; if (!handler) { throw new Error(`No locator strategy found for by="${params.by}". Register it in your config's locatorStrategies.`); } let locator = await handler(page, params); if (params.nth !== void 0) { locator = locator.nth(params.nth); } return locator; } // src/defaults/locator-strategies.ts var locatorStrategies = { selector: async (page, params) => page.locator(params.value), xpath: async (page, params) => page.locator(params.value), role: async (page, params) => { const p = params; return page.getByRole(p.role, { name: p.name, exact: p.exact, checked: p.checked, disabled: p.disabled, expanded: p.expanded, includeHidden: p.includeHidden, level: p.level, pressed: p.pressed, selected: p.selected }); }, text: async (page, params) => { const p = params; return page.getByText(p.value, { exact: p.exact }); }, label: async (page, params) => { const p = params; return page.getByLabel(p.value, { exact: p.exact }); }, placeholder: async (page, params) => { const p = params; return page.getByPlaceholder(p.value, { exact: p.exact }); }, altText: async (page, params) => { const p = params; return page.getByAltText(p.value, { exact: p.exact }); }, title: async (page, params) => { const p = params; return page.getByTitle(p.value, { exact: p.exact }); }, testId: async (page, params) => page.getByTestId(params.value), nested: async (page, params) => { const p = params; const parentLocator = await resolveLocator(locatorStrategies, page, p.parent); const childLocator = await resolveLocator(locatorStrategies, page, p.child); return parentLocator.locator(childLocator); } }; var locator_strategies_default = locatorStrategies; // src/config.ts var baseConfig = { snapshotDir: "snapshots", jsonTestDir: "json-tests", jsonTestMatch: `**/*.playwright.json`, locatorStrategies: locator_strategies_default, actionTypeHandlers: action_type_handlers_default, rules: getter_setter_rules_default, setterStrategies: setter_strategies_default, getterStrategies: getter_strategies_default, fieldValueTimeout: 1e4 }; var config; function getConfiguration() { if (config) return config; config = loadConfiguration(); return config; } var explorer = cosmiconfig.cosmiconfigSync("playwright-json", { searchPlaces: ["playwright-json.config.ts", "playwright-json.config.js"] }); function loadConfiguration() { var _a2; try { if (explorer) { const result = explorer.search(); if (result && result.config) { const userConfig = (_a2 = result.config.default) != null ? _a2 : result.config; userConfig.configDir = path2__default.default.dirname(result.filepath); return userConfig; } } return baseConfig; } catch (error) { console.error("\u274C Failed to load user config:", error); return baseConfig; } } // src/runner.ts async function executeAction(config3, page, action) { var _a2; const a = action; console.log(` - \u{1F539} Performing action: ${(_a2 = a.label) != null ? _a2 : a.action}`); const handler = config3.actionTypeHandlers[a.action]; if (!handler) { throw new Error(`No handler found for action type: "${a.action}". Register it in your config's actionTypeHandlers.`); } const locator = a.locator != null ? await resolveLocator(config3.locatorStrategies, page, a.locator) : void 0; await handler({ page, locator }, action); } function loadTestFiles() { const config3 = getConfiguration(); const finalGlobPattern = `**/${config3.jsonTestDir}/${config3.jsonTestMatch}`; console.log("Glob pattern to find test files: ", finalGlobPattern); const jsonFiles2 = glob.globSync(finalGlobPattern, { cwd: config3.configDir, absolute: true }); console.log("Found ", jsonFiles2 == null ? void 0 : jsonFiles2.length, " Files."); return { jsonFiles: jsonFiles2, config: config3 }; } function resolveSnapshotDir(config3) { var _a2; const dir = config3.snapshotDir; if (path2__default.default.isAbsolute(dir)) return dir; return path2__default.default.join((_a2 = config3.configDir) != null ? _a2 : process.cwd(), dir); } var { jsonFiles, config: config2 } = loadTestFiles(); var _a; for (const testFilePath of jsonFiles) { const testRun = JSON.parse(fs.readFileSync(testFilePath, "utf-8")); for (const scenario of testRun.scenarios) { (0, test_exports.test)((_a = scenario.label) != null ? _a : scenario.name, async ({ page }, testInfo) => { var _a2, _b, _c; const userSetSnapshotDir = testInfo.project.snapshotDir !== testInfo.project.testDir; if (!userSetSnapshotDir) { testInfo.project.snapshotDir = resolveSnapshotDir(config2); testInfo._projectInternal.snapshotPathTemplate = "{snapshotDir}/{arg}{-projectName}{-snapshotSuffix}{ext}"; } let idx = 0; await page.goto(testRun.host); console.log(`\u{1F4CC} Executing scenario: ${(_a2 = scenario.label) != null ? _a2 : scenario.name}`); for (const step of scenario.steps) { console.log(` \u{1F6E0} Step: ${(_b = step.label) != null ? _b : step.description}`); await test_exports.test.step((_c = step.label) != null ? _c : step.description, async () => { for (const action of step.actions) { try { await page.evaluate((id) => console.log(`JSONIDX:${id}:START`), idx); await executeAction(config2, page, action); await page.evaluate((id) => console.log(`JSONIDX:${id}:END`), idx); } finally { idx++; } } }); } }); } } //# sourceMappingURL=runner-playwright.js.map //# sourceMappingURL=runner-playwright.js.map