playwright-json-runner
Version:
Extends Playwright to run tests using JSON-based test definitions.
1,076 lines (1,058 loc) • 37 kB
JavaScript
'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