@progress/kendo-e2e
Version:
Kendo UI end-to-end test utilities.
1,216 lines (1,209 loc) • 129 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/settings/settings.ts
var Settings;
var init_settings = __esm({
"src/settings/settings.ts"() {
Settings = class {
static get browserName() {
return process.env["BROWSER_NAME"] || "chrome";
}
static get browserWidth() {
return +process.env["BROWSER_WIDTH"] || 1366;
}
static get browserHeight() {
return +process.env["BROWSER_HEIGHT"] || 768;
}
static get headless() {
const value = process.env["HEADLESS"];
return value !== void 0 && value.toLocaleLowerCase() === "true";
}
static get baseUrl() {
return process.env["BASE_URL"] || "";
}
static get timeout() {
return +process.env["TIMEOUT"] || 3e4;
}
static get chromeArguments() {
const args = process.env["CHROME_ARGUMENTS"];
return args ? args.split(";").map((arg) => arg.trim()).filter((arg) => arg.length > 0) : [];
}
};
}
});
// src/selenium/driver-manager.ts
var import_selenium_webdriver, import_chrome, import_edge, import_firefox, import_safari, DriverManager;
var init_driver_manager = __esm({
"src/selenium/driver-manager.ts"() {
import_selenium_webdriver = require("selenium-webdriver");
import_chrome = require("selenium-webdriver/chrome");
import_edge = require("selenium-webdriver/edge");
import_firefox = require("selenium-webdriver/firefox");
import_safari = require("selenium-webdriver/safari");
init_settings();
DriverManager = class {
constructor() {
/**
* Default command-line arguments for Chromium-based browsers (Chrome, Edge).
*
* These options ensure consistent test behavior:
* - Fixed window size and scale factor for consistent screenshots
* - Disabled extensions and notifications to avoid interference
* - Reduced logging noise
* - Certificate error handling
* - Disabled search engine choice screen
*/
this.DEFAULT_CHROMIUM_OPTIONS = [
`--window-size=${Settings.browserWidth},${Settings.browserHeight}`,
"--force-device-scale-factor=1",
"--log-level=1",
"--disable-extensions",
"--disable-notifications",
"--disable-search-engine-choice-screen",
"--ignore-certificate-errors"
];
}
/**
* Creates a WebDriver instance based on Settings.browserName.
*
* Automatically selects the appropriate browser driver based on configuration.
* Supports mobile emulation and BiDi protocol for Chrome.
*
* @param options - Optional driver configuration
* @param options.mobileEmulation - Mobile device emulation settings (Chrome only)
* @param options.enableBidi - Enable BiDi protocol for advanced features (Chrome only)
* @returns Configured WebDriver instance
*
* @example
* ```typescript
* const manager = new DriverManager();
*
* // Basic driver
* const driver = manager.getDriver();
*
* // With mobile emulation
* const mobile = manager.getDriver({
* mobileEmulation: { deviceName: 'Pixel 5' }
* });
*
* // With BiDi for CDP features
* const advanced = manager.getDriver({ enableBidi: true });
* ```
*/
getDriver(options = {}) {
switch (Settings.browserName) {
case import_selenium_webdriver.Browser.CHROME: {
return this.getChromeDriver(options);
}
case import_selenium_webdriver.Browser.EDGE: {
return this.getEdgeDriver();
}
case import_selenium_webdriver.Browser.FIREFOX: {
return this.getFirefoxDriver();
}
case import_selenium_webdriver.Browser.SAFARI: {
return this.getSafariDriver();
}
default: {
throw new Error(`Unsupported browser: ${Settings.browserName}`);
}
}
}
/**
* Creates Chrome-specific options with custom arguments and settings.
*
* Configures Chrome with optimal settings for testing, including headless mode
* support, Docker compatibility, mobile emulation, and BiDi protocol.
*
* @param args - Command-line arguments for Chrome (default: DEFAULT_CHROMIUM_OPTIONS)
* @param options - Driver configuration options
* @returns Configured ChromeOptions instance
*
* @example
* ```typescript
* const manager = new DriverManager();
*
* // Get default options
* const options = manager.getChromeOptions();
*
* // Custom arguments
* const customOptions = manager.getChromeOptions([
* '--window-size=1920,1080',
* '--disable-gpu'
* ]);
*
* // With mobile emulation
* const mobileOptions = manager.getChromeOptions(
* manager.DEFAULT_CHROMIUM_OPTIONS,
* { mobileEmulation: { deviceName: 'iPhone 12' } }
* );
* ```
*/
getChromeOptions(args = this.DEFAULT_CHROMIUM_OPTIONS, options = {}) {
const chromeOptions = new import_chrome.Options();
const argumentsToUse = options.chromeArguments ?? [...args, ...Settings.chromeArguments];
argumentsToUse.forEach((argument) => {
chromeOptions.addArguments(argument);
});
if (Settings.headless) {
chromeOptions.addArguments("--headless=new");
}
if (process.env["CI_CONTAINER"]) {
chromeOptions.addArguments("--no-sandbox");
chromeOptions.addArguments("--disable-setuid-sandbox");
}
if (options.mobileEmulation) {
chromeOptions.setMobileEmulation(options.mobileEmulation);
}
if (options.enableBidi) {
chromeOptions.enableBidi();
}
return chromeOptions;
}
/**
* Creates a Chrome WebDriver instance.
*
* @param options - Either pre-configured ChromeOptions or DriverOptions
* @returns Chrome WebDriver instance
*
* @example
* ```typescript
* const manager = new DriverManager();
*
* // Basic Chrome driver
* const driver = manager.getChromeDriver();
*
* // With custom options
* const options = manager.getChromeOptions();
* options.addArguments('--start-maximized');
* const driver = manager.getChromeDriver(options);
* ```
*/
getChromeDriver(options) {
let chromeOptions;
if (options instanceof import_chrome.Options) {
chromeOptions = options;
} else {
chromeOptions = this.getChromeOptions(this.DEFAULT_CHROMIUM_OPTIONS, options);
}
return new import_selenium_webdriver.Builder().forBrowser(import_selenium_webdriver.Browser.CHROME).setChromeOptions(chromeOptions).build();
}
getEdgeOptions(args = this.DEFAULT_CHROMIUM_OPTIONS) {
const options = new import_edge.Options();
args.forEach((argument) => {
options.addArguments(argument);
});
if (Settings.headless) {
options.addArguments("--headless");
}
return options;
}
getEdgeDriver(options = this.getEdgeOptions()) {
return new import_selenium_webdriver.Builder().forBrowser(import_selenium_webdriver.Browser.EDGE).setEdgeOptions(options).build();
}
getFirefoxOptions() {
const options = new import_firefox.Options();
options.addArguments(`--width=${Settings.browserWidth}`);
options.addArguments(`--height=${Settings.browserHeight}`);
options.setLoggingPrefs(import_selenium_webdriver.logging.Level.SEVERE);
if (Settings.headless) {
options.addArguments("--headless");
}
return options;
}
getFirefoxDriver(options = this.getFirefoxOptions()) {
const prefs = new import_selenium_webdriver.logging.Preferences();
prefs.setLevel(import_selenium_webdriver.logging.Type.BROWSER, import_selenium_webdriver.logging.Level.ALL);
const service = new import_firefox.ServiceBuilder().setStdio("inherit");
return new import_selenium_webdriver.Builder().forBrowser(import_selenium_webdriver.Browser.FIREFOX).setLoggingPrefs(prefs).setFirefoxOptions(options).setFirefoxService(service).build();
}
getSafariOptions() {
const options = new import_safari.Options();
return options;
}
getSafariDriver(options = this.getSafariOptions()) {
const driver = new import_selenium_webdriver.Builder().forBrowser(import_selenium_webdriver.Browser.SAFARI).setSafariOptions(options).build();
const size = { width: Settings.browserWidth, height: Settings.browserHeight, x: 0, y: 0 };
driver.manage().window().setRect(size);
return driver;
}
};
}
});
// src/selenium/conditions.ts
var import_selenium_webdriver2, VIEWPORT_SCRIPT, EC;
var init_conditions = __esm({
"src/selenium/conditions.ts"() {
import_selenium_webdriver2 = require("selenium-webdriver");
VIEWPORT_SCRIPT = function(element) {
const box = element.getBoundingClientRect();
const cx = box.left + box.width / 2;
const cy = box.top + box.height / 2;
let target = document.elementFromPoint(cx, cy);
while (target) {
if (target === element) {
return true;
}
target = target.parentElement;
}
return false;
};
EC = class {
/**
* Creates a condition that waits for an element to have specific text.
*
* Compares the element's visible text content (via getText()) with the expected text.
* Match must be exact.
*
* @param element - WebElement to check text of
* @param text - Exact text to wait for
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* const message = await app.find('#message');
* await app.wait(EC.hasText(message, 'Operation completed'));
*
* // Or check without waiting
* const hasCorrectText = await app.hasText('#status', 'Active');
* ```
*/
static hasText(element, text) {
return () => element.getText().then((result) => {
return result === text;
});
}
/**
* Creates a condition that waits for an input element to have a specific value.
*
* Checks the 'value' attribute of form inputs. Perfect for validating input fields
* after auto-fill, dynamic updates, or user interaction.
*
* @param element - Input WebElement to check
* @param value - Expected value
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* const input = await app.find('#username');
* await app.type(input, 'testuser');
* await app.wait(EC.hasValue(input, 'testuser'));
*
* // Wait for auto-filled value
* await app.wait(EC.hasValue(await app.find('#email'), 'user@example.com'));
* ```
*/
static hasValue(element, value) {
return () => element.getAttribute("value").then((result) => {
return result === value;
});
}
/**
* Creates a condition that waits for an element to have keyboard focus.
*
* Checks if the element is the currently active (focused) element in the document.
* Useful for testing keyboard navigation and focus management.
*
* @param element - WebElement to check for focus
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* const input = await app.find('#search');
* await app.click(input);
* await app.wait(EC.hasFocus(input));
*
* // Verify focus moved after Tab key
* await app.sendKey(Key.TAB);
* const nextInput = await app.find('#next-field');
* await app.wait(EC.hasFocus(nextInput));
* ```
*/
static hasFocus(element) {
return async (driver) => {
const focused = await driver.switchTo().activeElement();
return await element.getId() === await focused.getId();
};
}
/**
* Creates a condition that waits for an element to lose keyboard focus.
*
* Opposite of {@link hasFocus}. Useful for testing blur events and focus movement.
*
* @param element - WebElement to check for lack of focus
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* const input = await app.find('#field');
* await app.focus(input);
* await app.sendKey(Key.TAB); // Move focus away
* await app.wait(EC.hasNoFocus(input));
* ```
*/
static hasNoFocus(element) {
return async (driver) => {
const focused = await driver.switchTo().activeElement();
return await element.getId() !== await focused.getId();
};
}
/**
* Creates a condition that waits for an element to have at least one child matching a locator.
*
* Checks if the parent element contains any child elements matching the selector.
* Useful for waiting for dynamic content to load within a container.
*
* @param element - Parent WebElement to search within
* @param locator - Child element selector (By locator or CSS selector string)
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* const list = await app.find('ul#results');
* await app.wait(EC.hasChild(list, 'li'));
*
* // Wait for specific child
* const table = await app.find('#data-table');
* await app.wait(EC.hasChild(table, '.loaded-row'));
* ```
*/
static hasChild(element, locator) {
return async () => {
return locator instanceof import_selenium_webdriver2.By ? element.findElements(locator).then((result) => {
return result.length > 0;
}) : element.findElements(import_selenium_webdriver2.By.css(locator)).then((result) => {
return result.length > 0;
});
};
}
/**
* Creates a condition that waits for an element to have a specific attribute value.
*
* Can check for exact match or partial match (contains). Useful for validating
* data attributes, ARIA attributes, disabled state, etc.
*
* @param element - WebElement to check
* @param attribute - Attribute name (e.g., 'disabled', 'data-id', 'aria-label')
* @param value - Expected value
* @param exactMatch - If true, value must match exactly; if false, attribute must contain value (default: true)
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* const button = await app.find('#submit');
*
* // Wait for button to become disabled
* await app.wait(EC.hasAttribute(button, 'disabled', 'true'));
*
* // Wait for data attribute (partial match)
* await app.wait(EC.hasAttribute(button, 'data-state', 'loading', false));
*
* // Check ARIA label
* await app.wait(EC.hasAttribute(button, 'aria-label', 'Submit form'));
* ```
*/
static hasAttribute(element, attribute, value, exactMatch = true) {
return () => element.getAttribute(attribute).then((result) => {
if (exactMatch) {
return result === value;
} else {
return result.includes(value);
}
});
}
/**
* Creates a condition that waits for an element to have a specific CSS class.
*
* Convenience method that checks the 'class' attribute. Can do exact or partial match.
* Perfect for waiting for state changes reflected in CSS classes.
*
* @param element - WebElement to check
* @param value - Class name to wait for
* @param exactMatch - If true, class attribute must match exactly; if false, must contain the class (default: false)
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* const button = await app.find('#submit');
*
* // Wait for class to be added (partial match)
* await app.wait(EC.hasClass(button, 'active'));
*
* // Wait for exact class attribute
* await app.wait(EC.hasClass(button, 'btn btn-primary', true));
*
* // Wait for loading class
* await app.wait(EC.hasClass(await app.find('.spinner'), 'loading'));
* ```
*/
static hasClass(element, value, exactMatch = false) {
return this.hasAttribute(element, "class", value, exactMatch);
}
/**
* Creates a condition that waits for an element to be visible.
*
* Element must be present in DOM and have display style that makes it visible.
* Accepts WebElement, By locator, or CSS selector string.
*
* @param element - Element to check visibility of (WebElement, By locator, or CSS selector)
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* // Wait for modal to appear
* await app.wait(EC.isVisible('#modal'));
*
* // Wait for element after click
* await app.click('#show-details');
* await app.wait(EC.isVisible('.details-panel'));
*
* // With By locator
* await app.wait(EC.isVisible(By.css('[data-test="banner"]')));
* ```
*/
static isVisible(element) {
return async (driver) => {
try {
if (!(element instanceof import_selenium_webdriver2.WebElement)) {
element = element instanceof import_selenium_webdriver2.By ? await driver.findElement(element) : await driver.findElement(import_selenium_webdriver2.By.css(element));
}
return await element.isDisplayed();
} catch {
return false;
}
};
}
/**
* Creates a condition that waits for an element to become hidden or not present.
*
* Element is considered not visible if it's either not in DOM or has display:none or similar.
* Opposite of {@link isVisible}.
*
* @param element - Element to check (WebElement, By locator, or CSS selector)
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* // Wait for loading spinner to disappear
* await app.wait(EC.notVisible('.spinner'));
*
* // Wait for modal to close
* await app.click('.modal .close');
* await app.wait(EC.notVisible('.modal'));
* ```
*/
static notVisible(element) {
return async (driver) => {
try {
if (!(element instanceof import_selenium_webdriver2.WebElement)) {
element = element instanceof import_selenium_webdriver2.By ? await driver.findElement(element) : await driver.findElement(import_selenium_webdriver2.By.css(element));
}
return !await element.isDisplayed();
} catch {
return true;
}
};
}
/**
* Creates a condition that waits for an element to be in the visible viewport.
*
* Checks if the center point of the element is actually visible in the viewport and not
* covered by other elements. Stricter than {@link isVisible} - element must be scrolled into view.
*
* @param element - Element to check (WebElement, By locator, or CSS selector)
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* // Wait for element to scroll into view
* await app.wait(EC.isInViewport('.footer-content'));
*
* // Verify element is actually visible to user
* await app.scrollIntoView('#target');
* await app.wait(EC.isInViewport('#target'));
* ```
*/
static isInViewport(element) {
return async (driver) => {
try {
if (!(element instanceof import_selenium_webdriver2.WebElement)) {
element = element instanceof import_selenium_webdriver2.By ? await driver.findElement(element) : await driver.findElement(import_selenium_webdriver2.By.css(element));
}
const result = await driver.executeScript(VIEWPORT_SCRIPT, element);
return result + "" === "true";
} catch {
return false;
}
};
}
/**
* Creates a condition that waits for an element to be outside the visible viewport.
*
* Checks if the element is scrolled out of view or covered. Opposite of {@link isInViewport}.
*
* @param element - Element to check (WebElement, By locator, or CSS selector)
* @returns Condition function for use with wait()
*
* @example
* ```typescript
* // Wait for element to scroll out of view
* await app.scrollIntoView('#bottom-element');
* await app.wait(EC.notInViewport('#top-element'));
* ```
*/
static notInViewport(element) {
return async (driver) => {
try {
if (!(element instanceof import_selenium_webdriver2.WebElement)) {
element = element instanceof import_selenium_webdriver2.By ? await driver.findElement(element) : await driver.findElement(import_selenium_webdriver2.By.css(element));
}
const result = await driver.executeScript(VIEWPORT_SCRIPT, element);
return result + "" === "false";
} catch {
return true;
}
};
}
};
}
});
// src/selenium/expect.ts
function expectSelector(driver, by) {
const DEFAULT_TIMEOUT = 3e3;
const DEFAULT_POLL_INTERVAL = 25;
function fail(message) {
throw new Error(message);
}
const getLocatorString = () => {
return by.toString();
};
const toHaveText = async function toHaveText2(expected, opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
let lastText = "";
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const text = (await element.getText())?.trim?.() ?? "";
lastText = text;
if (expected instanceof RegExp) {
if (expected.test(text)) return;
} else if (text === expected) {
return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const exp = expected instanceof RegExp ? `/${expected.source}/` : `"${expected}"`;
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to have text ${exp} but got "${lastText}"` : `Expected element ${locatorStr} to have text ${exp} but got "${lastText}"`;
fail(errorMessage);
};
const toHaveValue = async function toHaveValue2(expected, opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
let lastValue = "";
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const value = await element.getAttribute("value") ?? "";
lastValue = value;
if (value === expected) {
return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to have value "${expected}" but got "${lastValue}"` : `Expected element ${locatorStr} to have value "${expected}" but got "${lastValue}"`;
fail(errorMessage);
};
const toHaveFocus = async function toHaveFocus2(opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const activeElement = await driver.switchTo().activeElement();
const elementId = await element.getId();
const activeId = await activeElement.getId();
if (elementId === activeId) {
return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to have focus within ${timeout}ms` : `Expected element ${locatorStr} to have focus within ${timeout}ms`;
fail(errorMessage);
};
const toHaveNoFocus = async function toHaveNoFocus2(opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const activeElement = await driver.switchTo().activeElement();
const elementId = await element.getId();
const activeId = await activeElement.getId();
if (elementId !== activeId) {
return;
}
} catch {
return;
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to not have focus within ${timeout}ms` : `Expected element ${locatorStr} to not have focus within ${timeout}ms`;
fail(errorMessage);
};
const toHaveAttribute = async function toHaveAttribute2(attribute, expected, opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const exactMatch = opts?.exactMatch ?? true;
const deadline = Date.now() + timeout;
let lastValue = "";
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const value = await element.getAttribute(attribute) ?? "";
lastValue = value;
if (exactMatch) {
if (value === expected) return;
} else {
if (value.includes(expected)) return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const matchType = exactMatch ? "exactly" : "to contain";
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} attribute "${attribute}" ${matchType} "${expected}" but got "${lastValue}"` : `Expected element ${locatorStr} attribute "${attribute}" ${matchType} "${expected}" but got "${lastValue}"`;
fail(errorMessage);
};
const toHaveClass = async function toHaveClass2(expected, opts) {
return toHaveAttribute("class", expected, opts);
};
const toBeVisible = async function toBeVisible2(opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
if (await element.isDisplayed()) {
return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to be visible within ${timeout}ms` : `Expected element ${locatorStr} to be visible within ${timeout}ms`;
fail(errorMessage);
};
const toBeNotVisible = async function toBeNotVisible2(opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
if (!await element.isDisplayed()) {
return;
}
} catch {
return;
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to become hidden within ${timeout}ms` : `Expected element ${locatorStr} to become hidden within ${timeout}ms`;
fail(errorMessage);
};
const toBeEnabled = async function toBeEnabled2(opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
if (await element.isEnabled()) {
return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to be enabled within ${timeout}ms` : `Expected element ${locatorStr} to be enabled within ${timeout}ms`;
fail(errorMessage);
};
const toBeDisabled = async function toBeDisabled2(opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
if (!await element.isEnabled()) {
return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to be disabled within ${timeout}ms` : `Expected element ${locatorStr} to be disabled within ${timeout}ms`;
fail(errorMessage);
};
const toBeChecked = async function toBeChecked2(opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
if (await element.isSelected()) {
return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to be checked within ${timeout}ms` : `Expected element ${locatorStr} to be checked within ${timeout}ms`;
fail(errorMessage);
};
const toBeNotChecked = async function toBeNotChecked2(opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
if (!await element.isSelected()) {
return;
}
} catch {
return;
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to not be checked within ${timeout}ms` : `Expected element ${locatorStr} to not be checked within ${timeout}ms`;
fail(errorMessage);
};
const toContainText = async function toContainText2(expected, opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
let lastText = "";
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const text = (await element.getText())?.trim?.() ?? "";
lastText = text;
if (text.includes(expected)) {
return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to contain text "${expected}" but got "${lastText}"` : `Expected element ${locatorStr} to contain text "${expected}" but got "${lastText}"`;
fail(errorMessage);
};
const toNotContainText = async function toNotContainText2(expected, opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
let lastText = "";
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const text = (await element.getText())?.trim?.() ?? "";
lastText = text;
if (!text.includes(expected)) {
return;
}
} catch {
return;
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to not contain text "${expected}" but got "${lastText}"` : `Expected element ${locatorStr} to not contain text "${expected}" but got "${lastText}"`;
fail(errorMessage);
};
const toHaveCount = async function toHaveCount2(expected, opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
let lastCount = 0;
while (Date.now() < deadline) {
try {
const elements = await driver.findElements(by);
lastCount = elements.length;
if (lastCount === expected) {
return;
}
} catch {
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected ${locatorStr} to have count ${expected} but found ${lastCount}` : `Expected ${locatorStr} to have count ${expected} but found ${lastCount}`;
fail(errorMessage);
};
const toNotHaveText = async function toNotHaveText2(expected, opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
let lastText = "";
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const text = (await element.getText())?.trim?.() ?? "";
lastText = text;
if (expected instanceof RegExp) {
if (!expected.test(text)) return;
} else if (text !== expected) {
return;
}
} catch {
return;
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const exp = expected instanceof RegExp ? `/${expected.source}/` : `"${expected}"`;
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to not have text ${exp} but got "${lastText}"` : `Expected element ${locatorStr} to not have text ${exp} but got "${lastText}"`;
fail(errorMessage);
};
const toNotHaveValue = async function toNotHaveValue2(expected, opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const deadline = Date.now() + timeout;
let lastValue = "";
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const value = await element.getAttribute("value") ?? "";
lastValue = value;
if (value !== expected) {
return;
}
} catch {
return;
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} to not have value "${expected}" but got "${lastValue}"` : `Expected element ${locatorStr} to not have value "${expected}" but got "${lastValue}"`;
fail(errorMessage);
};
const toNotHaveAttribute = async function toNotHaveAttribute2(attribute, expected, opts) {
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
const pollInterval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL;
const exactMatch = opts?.exactMatch ?? true;
const deadline = Date.now() + timeout;
let lastValue = "";
while (Date.now() < deadline) {
try {
const element = await driver.findElement(by);
const value = await element.getAttribute(attribute) ?? "";
lastValue = value;
if (exactMatch) {
if (value !== expected) return;
} else {
if (!value.includes(expected)) return;
}
} catch {
return;
}
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
}
const locatorStr = getLocatorString();
const matchType = exactMatch ? "exactly" : "to contain";
const customMessage = opts?.message;
const errorMessage = customMessage ? `${customMessage} Expected element ${locatorStr} attribute "${attribute}" to not match ${matchType} "${expected}" but got "${lastValue}"` : `Expected element ${locatorStr} attribute "${attribute}" to not match ${matchType} "${expected}" but got "${lastValue}"`;
fail(errorMessage);
};
const toNotHaveClass = async function toNotHaveClass2(expected, opts) {
return toNotHaveAttribute("class", expected, opts);
};
return {
toHaveText,
toBeVisible,
toHaveValue,
toHaveFocus,
toHaveAttribute,
toHaveClass,
toBeEnabled,
toBeDisabled,
toBeChecked,
toContainText,
toHaveCount,
not: {
toBeVisible: toBeNotVisible,
toHaveFocus: toHaveNoFocus,
toHaveText: toNotHaveText,
toHaveValue: toNotHaveValue,
toHaveAttribute: toNotHaveAttribute,
toHaveClass: toNotHaveClass,
toBeChecked: toBeNotChecked,
toContainText: toNotContainText,
toBeEnabled: toBeDisabled,
toBeDisabled: toBeEnabled
}
};
}
var init_expect = __esm({
"src/selenium/expect.ts"() {
}
});
// src/utils/rgb-to-hex.ts
function rgbToHex(color) {
const match = color.match(/\d+/g);
if (!match || match.length < 3) return color;
const [r, g, b] = match.map(Number);
return "#" + [r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("");
}
var init_rgb_to_hex = __esm({
"src/utils/rgb-to-hex.ts"() {
}
});
// src/selenium/web-app.ts
var import_selenium_webdriver3, WebApp;
var init_web_app = __esm({
"src/selenium/web-app.ts"() {
import_selenium_webdriver3 = require("selenium-webdriver");
init_conditions();
init_expect();
init_rgb_to_hex();
WebApp = class {
/**
* Creates a WebApp instance wrapping a Selenium WebDriver.
*
* @param driver - Selenium WebDriver instance to wrap
*/
constructor(driver) {
this.driver = driver;
}
/**
* Finds a single element with automatic waiting.
*
* **Automatically waits** up to the specified timeout for the element to appear in the DOM.
* This eliminates the need for manual waits and handles dynamic content loading.
*
* @param locator - CSS selector string or Selenium By locator
* @param options - Optional configuration
* @param options.timeout - Maximum time to wait in milliseconds (default: 10000)
* @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
* @returns Promise resolving to the WebElement
* @throws Error if element is not found within the timeout period
*
* @example
* ```typescript
* // Using CSS selector (recommended)
* const button = await app.find('#submit-btn');
* const firstItem = await app.find('.list-item');
*
* // Using Selenium By locator
* const element = await app.find(By.xpath('//div[@data-test="value"]'));
*
* // With custom timeout
* const slowElement = await app.find('#async-content', { timeout: 20000 });
* ```
*/
async find(locator, { timeout = 1e4, pollTimeout = 25 } = {}) {
const errorMessage = `Failed to find element located by ${locator}.`;
if (locator instanceof import_selenium_webdriver3.By) {
return await this.driver.wait(import_selenium_webdriver3.until.elementLocated(locator), timeout, errorMessage, pollTimeout);
} else {
return await this.driver.wait(import_selenium_webdriver3.until.elementLocated(import_selenium_webdriver3.By.css(locator)), timeout, errorMessage, pollTimeout);
}
}
/**
* Finds all matching elements without waiting.
*
* Returns an empty array if no elements are found. For scenarios where you need to wait
* for at least one element, use {@link findAllWithTimeout} instead.
*
* @param locator - CSS selector string or Selenium By locator
* @returns Promise resolving to array of WebElements (empty if none found)
*
* @example
* ```typescript
* // Get all list items
* const items = await app.findAll('.list-item');
* console.log(`Found ${items.length} items`);
*
* // Iterate over elements
* for (const item of items) {
* const text = await item.getText();
* console.log(text);
* }
*
* // Using By locator
* const buttons = await app.findAll(By.css('button'));
* ```
*/
async findAll(locator) {
if (locator instanceof import_selenium_webdriver3.By) {
return await this.driver.findElements(locator);
} else {
return await this.driver.findElements(import_selenium_webdriver3.By.css(locator));
}
}
/**
* Finds all matching elements with automatic waiting for at least one element to appear.
*
* Waits until at least one element matching the locator appears, then returns all matching elements.
* Use this when you expect elements to load dynamically and need to ensure they're present.
*
* @param locator - CSS selector string or Selenium By locator
* @param options - Optional configuration
* @param options.timeout - Maximum time to wait in milliseconds (default: 10000)
* @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
* @returns Promise resolving to array of WebElements
*
* @example
* ```typescript
* // Wait for search results to load
* const results = await app.findAllWithTimeout('.search-result');
*
* // With custom timeout for slow-loading content
* const items = await app.findAllWithTimeout('.async-item', { timeout: 15000 });
* ```
*/
async findAllWithTimeout(locator, { timeout = 1e4, pollTimeout = 25 } = {}) {
const byLocator = locator instanceof import_selenium_webdriver3.By ? locator : import_selenium_webdriver3.By.css(locator);
const start = Date.now();
let elements = [];
while (Date.now() - start < timeout) {
elements = await this.driver.findElements(byLocator);
if (elements.length > 0) {
return elements;
}
await this.driver.sleep(pollTimeout);
}
elements = await this.driver.findElements(byLocator);
return elements;
}
/**
* Finds a child element within a parent element with automatic waiting.
*
* Useful for scoped element searches within a specific container. Automatically waits
* for both the parent and child elements to appear.
*
* @param rootElement - Parent element (WebElement, By locator, or CSS selector)
* @param locator - Child element selector (By locator or CSS selector)
* @param options - Optional configuration
* @param options.waitForChild - Whether to wait for child to appear (default: true)
* @param options.timeout - Maximum time to wait in milliseconds (default: 10000)
* @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
* @returns Promise resolving to the child WebElement
* @throws Error if child element is not found within the timeout period
*
* @example
* ```typescript
* // Find button within a specific dialog
* const dialog = await app.find('.modal-dialog');
* const closeBtn = await app.findChild(dialog, '.close-button');
*
* // Or find child directly using parent selector
* const button = await app.findChild('.modal-dialog', 'button.submit');
*
* // Without waiting for child (if you know it exists)
* const child = await app.findChild(parent, '.child', { waitForChild: false });
* ```
*/
async findChild(rootElement, locator, { waitForChild = true, timeout = 1e4, pollTimeout = 25 } = {}) {
if (!(rootElement instanceof import_selenium_webdriver3.WebElement)) {
rootElement = await this.find(rootElement);
}
if (waitForChild) {
const message = `Failed to find child element located by ${locator}.`;
await this.wait(EC.hasChild(rootElement, locator), { timeout, message, pollTimeout });
}
return locator instanceof import_selenium_webdriver3.By ? rootElement.findElement(locator) : rootElement.findElement(import_selenium_webdriver3.By.css(locator));
}
/**
* Finds all child elements within a parent element with automatic waiting.
*
* Similar to {@link findChild} but returns all matching children instead of just the first one.
* Waits for at least one child to appear before returning.
*
* @param rootElement - Parent element (WebElement, By locator, or CSS selector)
* @param locator - Child elements selector (By locator or CSS selector)
* @param options - Optional configuration
* @param options.waitForChild - Whether to wait for at least one child to appear (default: true)
* @param options.timeout - Maximum time to wait in milliseconds (default: 10000)
* @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
* @returns Promise resolving to array of child WebElements
*
* @example
* ```typescript
* // Find all rows in a specific table
* const table = await app.find('#data-table');
* const rows = await app.findChildren(table, 'tr');
*
* // Or find children directly using parent selector
* const items = await app.findChildren('.dropdown-menu', 'li');
*