UNPKG

playwright-json-runner

Version:

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

558 lines (548 loc) 21.1 kB
import { __export, __reExport } from './chunk-KTR6D2I2.mjs'; import { webkit, firefox, chromium } from 'playwright'; import { cosmiconfigSync } from 'cosmiconfig'; import path from 'path'; import * as default2 from 'playwright/test'; import default2__default from 'playwright/test'; import { JSDOM } from 'jsdom'; // node_modules/@playwright/test/index.mjs var test_exports = {}; __export(test_exports, { default: () => default2__default }); __reExport(test_exports, default2); async function setLocatorValue(locator, value, options) { var _a; const config2 = getConfiguration(); const timeout = (_a = options == null ? void 0 : options.timeout) != null ? _a : config2.fieldValueTimeout; await locator.waitFor({ state: "attached", timeout }); const html = await locator.evaluate((el) => el.outerHTML); const ruleMatch = getRuleMatch(html, config2); if (ruleMatch && config2.setterStrategies[ruleMatch.id]) { const targetLocator = ruleMatch.matchedChild && ruleMatch.xpath ? locator.locator(ruleMatch.xpath) : locator; return await config2.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 _a; const config2 = getConfiguration(); const timeout = (_a = options == null ? void 0 : options.timeout) != null ? _a : config2.fieldValueTimeout; await locator.waitFor({ state: "attached", timeout }); const html = await locator.evaluate((el) => el.outerHTML); const ruleMatch = getRuleMatch(html, config2); if (ruleMatch && config2.getterStrategies[ruleMatch.id]) { const targetLocator = ruleMatch.matchedChild && ruleMatch.xpath ? locator.locator(ruleMatch.xpath) : locator; return await config2.getterStrategies[ruleMatch.id]({ locator: targetLocator, ruleMatch }); } throw new Error(`No matching getter rule for element: ${html.substring(0, 200)}`); } function getRuleMatch(html, config2) { var _a; const { document } = new JSDOM(html).window; const root = (_a = document.body.firstElementChild) != null ? _a : 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(config2.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 _a; const a = action; await page.waitForTimeout((_a = a.duration) != null ? _a : 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 }; function extendConfig(extensions) { var _a, _b, _c, _d, _e; return { ...baseConfig, ...extensions, locatorStrategies: { ...baseConfig.locatorStrategies, ...(_a = extensions.locatorStrategies) != null ? _a : {} }, actionTypeHandlers: { ...baseConfig.actionTypeHandlers, ...(_b = extensions.actionTypeHandlers) != null ? _b : {} }, rules: { ...baseConfig.rules, ...(_c = extensions.rules) != null ? _c : {} }, setterStrategies: { ...baseConfig.setterStrategies, ...(_d = extensions.setterStrategies) != null ? _d : {} }, getterStrategies: { ...baseConfig.getterStrategies, ...(_e = extensions.getterStrategies) != null ? _e : {} } }; } var config; function getConfiguration() { if (config) return config; config = loadConfiguration(); return config; } var explorer = cosmiconfigSync("playwright-json", { searchPlaces: ["playwright-json.config.ts", "playwright-json.config.js"] }); function loadConfiguration() { var _a; try { if (explorer) { const result = explorer.search(); if (result && result.config) { const userConfig = (_a = result.config.default) != null ? _a : result.config; userConfig.configDir = path.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 runTests(testRun) { var _a; const config2 = getConfiguration(); const browserType = (_a = { chrome: chromium, firefox, webkit }[testRun.browser]) != null ? _a : chromium; const browser = await browserType.launch(); console.log(`\u{1F680} Running tests on ${testRun.host} using ${testRun.browser}`); await executeTestRun(config2, browser, testRun); await browser.close(); } async function executeTestRun(config2, browser, testRun) { for (const scenario of testRun.scenarios) { const context = await browser.newContext({ baseURL: testRun.host, recordVideo: { dir: "./videos" } }); const page = await context.newPage(); try { await executeScenario(config2, page, scenario); } finally { await context.close(); } } } async function executeScenario(config2, page, scenario) { var _a; console.log(`\u{1F4CC} Executing scenario: ${(_a = scenario.label) != null ? _a : scenario.name}`); await page.goto("/"); for (const step of scenario.steps) { await executeStep(config2, page, step); } } async function executeStep(config2, page, step) { var _a; console.log(` \u{1F6E0} Step: ${(_a = step.label) != null ? _a : step.description}`); for (const action of step.actions) { await executeAction(config2, page, action); } } async function executeAction(config2, page, action) { var _a; const a = action; console.log(` - \u{1F539} Performing action: ${(_a = a.label) != null ? _a : a.action}`); const handler = config2.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(config2.locatorStrategies, page, a.locator) : void 0; await handler({ page, locator }, action); } export { baseConfig, executeAction, extendConfig, getConfiguration, getLocatorValue, loadConfiguration, resolveLocator, runTests, setLocatorValue, test_exports }; //# sourceMappingURL=chunk-HUKYBBJG.mjs.map //# sourceMappingURL=chunk-HUKYBBJG.mjs.map