UNPKG

playwright-json-runner

Version:

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

346 lines (336 loc) 11.8 kB
import { webkit, firefox, chromium } from 'playwright'; import { error } from 'console'; import { cosmiconfigSync } from 'cosmiconfig'; import { expect } from 'playwright/test'; import { JSDOM } from 'jsdom'; // src/runner.ts async function setLocatorValue(locator, value) { const html = await locator.evaluate((el) => el.outerHTML); const config2 = getConfiguration(); 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(`\u274C Couldn't find a rule match for on element ${locator} \b html: ${html}`); } async function getLocatorValue(locator) { const html = await locator.evaluate((el) => el.outerHTML); const config2 = getConfiguration(); 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(`\u274C Couldn't find a rule match for on element ${locator} \b html: ${html}`); } function getRuleMatch(html, config2) { const { document } = new JSDOM(html).window; const element = document.body; if (!element) return null; let xpathMatch = void 0; let matchedChild = void 0; const xpath = (xpath2) => { const result = document.evaluate(xpath2, document, null, 9, null); const matchedElement = result.singleNodeValue; if (matchedElement) { xpathMatch = xpath2; matchedChild = element.firstElementChild !== matchedElement; return true; } return false; }; for (const [id, condition] of Object.entries(config2.rules)) { try { if (condition({ document, element, xpathEval: xpath })) { return { id, xpath: xpathMatch, matchedChild: matchedChild != null ? matchedChild : false }; } } catch (err) { console.error("error executing condition: ", id); throw err; } } return null; } // src/defaults/action-type-handlers.ts var actionTypeHandlers = { "sleep": async (_, { value }) => { }, "navigate": async (_, { value }) => { if (value) { console.log(`Navigating to ${value}`); } else { throw new Error("The 'navigate' action requires a 'url' property."); } }, "setfieldvalue": async (locator, { value }) => { if (!locator) { throw new Error("The 'setFieldValue' action requires a 'locator' property."); } if (value === void 0 || value === null) { throw new Error("The 'setFieldValue' action requires a non-null 'value' property."); } await setLocatorValue(locator, value); }, "click": async (locator) => { if (locator) { await locator.click(); } else { throw new Error("The 'click' action requires a 'locator' or 'selector' property."); } }, "assertFieldValueEquals": async (locator, { value }) => { if (!locator) { throw new Error("The 'setFieldValue' action requires a 'locator' property."); } if (value === void 0 || value === null) { throw new Error("The 'setFieldValue' action requires a non-null 'value' property."); } const actual = await getLocatorValue(locator); expect(actual).toBe(value); }, "assertFieldValueContains": async (locator, { value }) => { if (!locator) { throw new Error("The 'setFieldValue' action requires a 'locator' property."); } if (value === void 0 || value === null) { throw new Error("The 'setFieldValue' action requires a non-null 'value' property."); } const actual = await getLocatorValue(locator); expect(actual).toContain(value); }, "assertElementExists": async (locator) => { await expect(locator).toBeAttached(); }, "expect": async (locator, { value, playwrightFunction }) => { if (!locator) { throw new Error("The 'expect' action requires a 'locator' property."); } if (!value) { throw new Error("The 'expect' action requires a non-null 'value' property."); } if (!playwrightFunction) { throw new Error("The 'expect' action requires an 'playwrightFunction' property."); } if (typeof expect(locator)[playwrightFunction] !== "function") { throw new Error(`Invalid 'playwrightFunction': '${playwrightFunction}' is not a valid Playwright assertion.`); } await expect(locator)[playwrightFunction](value); } }; var action_type_handlers_default = actionTypeHandlers; // src/defaults/getter-setter-rules.ts var getterSetterRules = { "input.datepicker": ({ element }) => element.matches(".custom-datepicker"), "select": ({ xpathEval }) => xpathEval("//select"), "text": ({ xpathEval }) => xpathEval("//input | //textarea") }; var getter_setter_rules_default = getterSetterRules; // src/defaults/getter-strategies.ts var getterStrategies = { "input.datepicker": async ({ locator }) => { return await locator.inputValue(); }, "select": async ({ locator }) => { const selectedOption = locator.locator("option:checked"); return selectedOption ? await selectedOption.innerText() : ""; }, "text": async ({ locator }) => { return await locator.inputValue(); } }; var getter_strategies_default = getterStrategies; // src/defaults/setter-strategies.ts var setterStrategies = { "select": async ({ locator, value }) => { await locator.selectOption({ label: value, 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(locatorStrategies2, page, strategy) { const handler = locatorStrategies2[strategy.type]; if (!handler) { throw new Error(`Invalid locator strategy: ${JSON.stringify(strategy)}`); } return handler(page, strategy); } // src/defaults/locator-strategies.ts var locatorStrategies = { selector: async (page, strategy) => page.locator(strategy.value), role: async (page, strategy) => { var _a; return page.getByRole(strategy.value.role, (_a = strategy.value.options) != null ? _a : {}); }, testId: async (page, strategy) => page.getByTestId(strategy.value), text: async (page, strategy) => page.getByText(strategy.value), nested: async (page, strategy) => { const parentLocator = await resolveLocator(locatorStrategies, page, strategy.parent); const childLocator = await resolveLocator(locatorStrategies, page, strategy.child); return parentLocator.locator(childLocator); } }; var locator_strategies_default = locatorStrategies; // src/config.ts var baseConfig = { actionTypeHandlers: action_type_handlers_default, rules: getter_setter_rules_default, setterStrategies: setter_strategies_default, getterStrategies: getter_strategies_default, locatorStrategies: locator_strategies_default, jsonTestDir: "json-tests", jsonTestMatch: `**/*.playwright.json` }; function extendConfig(extensions) { var _a, _b, _c, _d, _e; return { ...baseConfig, ...extensions, // Merge top-level properties locatorStrategies: { ...baseConfig.locatorStrategies, ...(_a = extensions.locatorStrategies) != null ? _a : {} // Merge objects }, actionTypeHandlers: { ...baseConfig.actionTypeHandlers, ...(_b = extensions.actionTypeHandlers) != null ? _b : {} // Merge objects }, rules: { ...baseConfig.rules, ...(_c = extensions.rules) != null ? _c : {} // Merge objects }, getterStrategies: { ...baseConfig.getterStrategies, ...(_d = extensions.getterStrategies) != null ? _d : {} // Merge objects }, setterStrategies: { ...baseConfig.setterStrategies, ...(_e = extensions.setterStrategies) != null ? _e : {} // Merge objects } }; } 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() { try { if (explorer) { const result = explorer.search(); if (result && result.config) { return result.config || result.config.default; } else { return baseConfig; } } else { return baseConfig; } } catch (error2) { console.error("\u274C Failed to load user config:", error2); return baseConfig; } } // src/runner.ts async function runTests(testRun) { const config2 = getConfiguration(); const browserType = { chrome: chromium, firefox, webkit }[testRun.browser] || 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 { 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, _b; console.log(` - \u{1F539} Performing action: ` + ((_a = action.label) != null ? _a : `${action.type}`)); if (action.type === "navigate") { await HandleActionTypeNavigate(action, page); return; } if (action.type === "sleep") { if (!action.value) { throw error("Action type: sleep must have 'value' prop in MS"); } await page.waitForTimeout(Number.parseInt(action.value)); return; } if (action.selector) { action.locator = { type: "selector", value: action.selector }; } if (!action.locator) { throw new Error(`Action must have a valid locator: ${JSON.stringify(action)}`); } const locator = await resolveLocator(config2.locatorStrategies, page, action.locator); const handler = (_b = Object.entries(config2.actionTypeHandlers).find( ([key]) => key.toLowerCase() === action.type.toLowerCase() )) == null ? void 0 : _b[1]; if (!handler) { throw new Error(`No handler found for action type: ${action.type}`); } await handler(locator, action); } async function HandleActionTypeNavigate(action, page) { if (action.value) { console.log("navigating to: ", action.value); await page.goto(action.value); } else { throw error("navigate action requires the url to be provided as value"); } } export { baseConfig, executeAction, extendConfig, getConfiguration, getLocatorValue, loadConfiguration, resolveLocator, runTests, setLocatorValue }; //# sourceMappingURL=chunk-CLGBQMKP.mjs.map //# sourceMappingURL=chunk-CLGBQMKP.mjs.map