UNPKG

@progress/kendo-e2e

Version:

Kendo UI end-to-end test utilities.

1,266 lines 54.8 kB
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebApp = void 0;
const selenium_webdriver_1 = require("selenium-webdriver");
const conditions_1 = require("./conditions");
const expect_1 = require("./expect");
const rgb_to_hex_1 = require("../utils/rgb-to-hex");
/**
 * Core class that provides automatic waiting and simplified interaction with web elements.
 *
 * **Key Features:**
 * - **Automatic waiting** - No need to add manual waits, all methods wait for elements automatically
 * - **Smart element location** - Accepts CSS selectors, By locators, or WebElement instances
 * - **Fluent API** - Chain methods for readable test code
 * - **Built-in retry logic** - Handles timing issues that cause flaky tests
 *
 * This eliminates the common Selenium pitfalls of StaleElementReferenceException and timing issues.
 *
 * @example
 * ```typescript
 * const app = new WebApp(driver);
 *
 * // Simple element interaction with automatic waiting
 * await app.click('#submit-button');
 * await app.type('#username', 'testuser');
 *
 * // Find and interact with elements
 * const header = await app.find('.page-header');
 * const text = await app.getText(header);
 *
 * // Wait for conditions
 * await app.wait(EC.isVisible('#success-message'));
 *
 * // Modern expect API with retry logic
 * await app.expect('#result').toHaveText('Success');
 * await app.expect('.modal').toBeVisible();
 * ```
 */
class WebApp {
    /**
     * 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 });
     * ```
     */
    find(locator_1) {
        return __awaiter(this, arguments, void 0, function* (locator, { timeout = 10000, pollTimeout = 25 } = {}) {
            const errorMessage = `Failed to find element located by ${locator}.`;
            if (locator instanceof selenium_webdriver_1.By) {
                return yield this.driver.wait(selenium_webdriver_1.until.elementLocated(locator), timeout, errorMessage, pollTimeout);
            }
            else {
                return yield this.driver.wait(selenium_webdriver_1.until.elementLocated(selenium_webdriver_1.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'));
     * ```
     */
    findAll(locator) {
        return __awaiter(this, void 0, void 0, function* () {
            if (locator instanceof selenium_webdriver_1.By) {
                return yield this.driver.findElements(locator);
            }
            else {
                return yield this.driver.findElements(selenium_webdriver_1.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 });
     * ```
     */
    findAllWithTimeout(locator_1) {
        return __awaiter(this, arguments, void 0, function* (locator, { timeout = 10000, pollTimeout = 25 } = {}) {
            const byLocator = locator instanceof selenium_webdriver_1.By ? locator : selenium_webdriver_1.By.css(locator);
            const start = Date.now();
            let elements = [];
            while ((Date.now() - start) < timeout) {
                elements = yield this.driver.findElements(byLocator);
                if (elements.length > 0) {
                    return elements;
                }
                yield this.driver.sleep(pollTimeout);
            }
            // Final attempt after timeout
            elements = yield 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 });
     * ```
     */
    findChild(rootElement_1, locator_1) {
        return __awaiter(this, arguments, void 0, function* (rootElement, locator, { waitForChild = true, timeout = 10000, pollTimeout = 25 } = {}) {
            if (!(rootElement instanceof selenium_webdriver_1.WebElement)) {
                rootElement = yield this.find(rootElement);
            }
            if (waitForChild) {
                const message = `Failed to find child element located by ${locator}.`;
                yield this.wait(conditions_1.EC.hasChild(rootElement, locator), { timeout: timeout, message: message, pollTimeout: pollTimeout });
            }
            return (locator instanceof selenium_webdriver_1.By)
                ? rootElement.findElement(locator)
                : rootElement.findElement(selenium_webdriver_1.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');
     *
     * // Process all children
     * for (const row of rows) {
     *   const text = await row.getText();
     *   console.log(text);
     * }
     * ```
     */
    findChildren(rootElement_1, locator_1) {
        return __awaiter(this, arguments, void 0, function* (rootElement, locator, { waitForChild = true, timeout = 10000, pollTimeout = 25 } = {}) {
            if (!(rootElement instanceof selenium_webdriver_1.WebElement)) {
                rootElement = yield this.find(rootElement);
            }
            if (waitForChild) {
                const message = `Failed to find child element located by ${locator}.`;
                yield this.wait(conditions_1.EC.hasChild(rootElement, locator), { timeout: timeout, message: message, pollTimeout: pollTimeout });
            }
            return (locator instanceof selenium_webdriver_1.By)
                ? rootElement.findElements(locator)
                : rootElement.findElements(selenium_webdriver_1.By.css(locator));
        });
    }
    /**
     * Clicks an element with automatic waiting and retry logic.
     *
     * **Handles common click issues automatically:**
     * - Waits for element to appear in DOM
     * - Waits for element to be visible
     * - Waits for element to be enabled
     * - Retries if click fails initially
     *
     * This eliminates flaky tests caused by timing issues.
     *
     * @param element - Element to click (WebElement, By locator, or CSS selector)
     * @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 that resolves when click succeeds
     * @throws Error if element cannot be clicked within timeout
     *
     * @example
     * ```typescript
     * // Simple click using CSS selector
     * await app.click('#submit-button');
     *
     * // Click with custom timeout
     * await app.click('.slow-loading-btn', { timeout: 15000 });
     *
     * // Click using By locator
     * await app.click(By.xpath('//button[text()="Submit"]'));
     *
     * // Click a previously found element
     * const button = await app.find('#my-button');
     * await app.click(button);
     * ```
     */
    click(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            try {
                if (!(element instanceof selenium_webdriver_1.WebElement)) {
                    element = yield this.find(element, { timeout: timeout, pollTimeout: pollTimeout });
                }
                yield element.click();
            }
            catch (_a) {
                if (!(element instanceof selenium_webdriver_1.WebElement)) {
                    element = yield this.find(element, { timeout: timeout, pollTimeout: pollTimeout });
                }
                const notVisibleMessage = (element instanceof selenium_webdriver_1.WebElement)
                    ? `Element located is not visible.`
                    : `Element located by ${element} is not visible.`;
                const notEnabledMessage = (element instanceof selenium_webdriver_1.WebElement)
                    ? `Element located is not enabled.`
                    : `Element located by ${element} is not enabled.`;
                yield this.wait(selenium_webdriver_1.until.elementIsVisible(element), { timeout: timeout, message: notVisibleMessage });
                yield this.wait(selenium_webdriver_1.until.elementIsEnabled(element), { timeout: timeout, message: notEnabledMessage });
                yield element.click();
            }
        });
    }
    /**
     * Moves the mouse cursor over an element (hover action).
     *
     * Useful for testing hover states, tooltips, dropdown menus, and other hover-triggered UI.
     * Automatically waits for the element to be present before hovering.
     *
     * @param element - Element to hover over (WebElement, By locator, or CSS selector)
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait for element in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
     * @returns Promise that resolves when hover completes
     *
     * @example
     * ```typescript
     * // Hover to reveal dropdown menu
     * await app.hover('.menu-item');
     * await app.click('.submenu-option');
     *
     * // Hover to show tooltip
     * await app.hover('#info-icon');
     * const tooltip = await app.find('.tooltip');
     * const text = await app.getText(tooltip);
     *
     * // Hover on element found with By locator
     * await app.hover(By.css('[data-test="hover-target"]'));
     * ```
     */
    hover(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element, { timeout: timeout, pollTimeout: pollTimeout });
            }
            const actions = this.driver.actions({ async: true, bridge: true });
            yield actions.move({ origin: element }).perform();
        });
    }
    /**
     * Sets focus on an element programmatically.
     *
     * Directly focuses the element using JavaScript, which is more reliable than clicking for focus.
     * Useful for testing keyboard interactions, input fields, and focus-dependent behaviors.
     *
     * @param element - Element to focus (WebElement, By locator, or CSS selector)
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait for element in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
     * @returns Promise that resolves when focus is set
     *
     * @example
     * ```typescript
     * // Focus an input field
     * await app.focus('#username');
     * await app.sendKey(Key.CONTROL, 'a'); // Select all
     *
     * // Focus before typing
     * await app.focus('#search-box');
     * await app.type('#search-box', 'test query');
     *
     * // Test focus-dependent behavior
     * await app.focus('#email');
     * await app.expect('.validation-hint').toBeVisible();
     * ```
     */
    focus(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element, { timeout: timeout, pollTimeout: pollTimeout });
            }
            yield this.driver.executeScript(`arguments[0].focus();`, element);
            yield this.driver.sleep(50);
        });
    }
    /**
     * Performs a right-click (context menu click) on an element.
     *
     * Opens the context menu for the element, allowing you to test right-click menus and actions.
     *
     * @param element - Element to right-click (WebElement, By locator, or CSS selector)
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait for element in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
     * @returns Promise that resolves when right-click completes
     *
     * @example
     * ```typescript
     * // Open context menu and select option
     * await app.contextClick('.file-item');
     * await app.click('.context-menu-delete');
     *
     * // Right-click on canvas element
     * await app.contextClick('#drawing-canvas');
     * await app.expect('.context-menu').toBeVisible();
     * ```
     */
    contextClick(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element, { timeout: timeout, pollTimeout: pollTimeout });
            }
            const actions = this.driver.actions({ async: true, bridge: true });
            yield actions.contextClick(element).perform();
        });
    }
    /**
     * Performs a double-click on an element.
     *
     * Useful for testing double-click interactions like selecting text, opening files, or
     * triggering double-click-specific behaviors in your application.
     *
     * @param element - Element to double-click (WebElement, By locator, or CSS selector)
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait for element in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
     * @returns Promise that resolves when double-click completes
     *
     * @example
     * ```typescript
     * // Double-click to open file
     * await app.doubleClick('.file-icon');
     *
     * // Double-click to select word
     * await app.doubleClick('.text-content');
     *
     * // Double-click with custom wait
     * await app.doubleClick('#expandable-item', { timeout: 5000 });
     * ```
     */
    doubleClick(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element, { timeout: timeout, pollTimeout: pollTimeout });
            }
            const actions = this.driver.actions({ async: true, bridge: true });
            yield actions.doubleClick(element).perform();
        });
    }
    /**
     * Waits for an element to stop animating, then clicks it.
     *
     * Perfect for clicking elements that are animating into view (slides, fades, etc.).
     * Prevents clicks during animation which can cause missed clicks or wrong targets.
     *
     * @param element - Element to wait for and click (WebElement, By locator, or CSS selector)
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between animation checks in milliseconds (default: 50)
     * @returns Promise that resolves when element is stable and clicked
     *
     * @example
     * ```typescript
     * // Click button that slides into view
     * await app.waitForAnimationAndClick('.animated-button');
     *
     * // Click element in animated modal
     * await app.waitForAnimationAndClick('.modal .submit-btn', { timeout: 5000 });
     * ```
     */
    waitForAnimationAndClick(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 50 } = {}) {
            yield this.waitForAnimation(element, { timeout: timeout, pollTimeout: pollTimeout });
            yield this.click(element, { timeout: timeout, pollTimeout: pollTimeout });
        });
    }
    /**
     * Scrolls element into view and then clicks it.
     *
     * Handles elements that are not initially in viewport. Scrolls the element to the center
     * of the viewport before clicking, ensuring reliable clicks on off-screen elements.
     *
     * @param element - Element to scroll to and click (WebElement, By locator, or CSS selector)
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait for element in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
     * @returns Promise that resolves when element is scrolled into view and clicked
     *
     * @example
     * ```typescript
     * // Click element at bottom of page
     * await app.scrollAndClick('#footer-button');
     *
     * // Scroll and click in long form
     * await app.scrollAndClick('.form-submit', { timeout: 5000 });
     *
     * // Click element in scrollable container
     * await app.scrollAndClick('.list-item:last-child');
     * ```
     */
    scrollAndClick(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element, { timeout: timeout, pollTimeout: pollTimeout });
            }
            yield this.driver.executeScript("arguments[0].scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });", element);
            yield element.click();
        });
    }
    /**
     * Scrolls an element into the viewport without clicking it.
     *
     * Useful when you need an element visible for screenshots, visibility checks, or
     * before performing other actions. Centers the element in the viewport.
     *
     * @param locator - Element to scroll to (By locator or CSS selector)
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait for element in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
     * @returns Promise that resolves when element is scrolled into view
     *
     * @example
     * ```typescript
     * // Scroll to element for screenshot
     * await app.scrollIntoView('#chart');
     * const screenshot = await app.getScreenshot();
     *
     * // Scroll before checking visibility
     * await app.scrollIntoView('.lazy-load-content');
     * await app.expect('.lazy-load-content').toBeVisible();
     * ```
     */
    scrollIntoView(locator_1) {
        return __awaiter(this, arguments, void 0, function* (locator, { timeout = 10000, pollTimeout = 25 } = {}) {
            const element = yield this.find(locator, { timeout: timeout, pollTimeout: pollTimeout });
            yield this.driver.executeScript("arguments[0].scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });", element);
        });
    }
    /**
     * Drags a source element and drops it onto a target element.
     *
     * Performs a complete drag-and-drop operation, useful for testing sortable lists,
     * drag-and-drop file uploads, kanban boards, and similar interactions.
     *
     * @param source - Element to drag (WebElement, By locator, or CSS selector)
     * @param target - Element to drop onto (WebElement, By locator, or CSS selector)
     * @returns Promise that resolves when drag-and-drop completes
     *
     * @example
     * ```typescript
     * // Drag list item to reorder
     * await app.dragTo(
     *   By.css('.list-item:nth-child(1)'),
     *   By.css('.list-item:nth-child(3)')
     * );
     *
     * // Drag file to upload area
     * const file = await app.find('.file-icon');
     * const dropzone = await app.find('.upload-dropzone');
     * await app.dragTo(file, dropzone);
     *
     * // Drag card between columns (kanban)
     * await app.dragTo('#card-1', '#column-done');
     * ```
     */
    dragTo(source, target) {
        return __awaiter(this, void 0, void 0, function* () {
            let sourceElement;
            let targetElement;
            if (source instanceof selenium_webdriver_1.WebElement) {
                sourceElement = source;
            }
            else {
                sourceElement = yield this.find(source);
            }
            if (target instanceof selenium_webdriver_1.WebElement) {
                targetElement = target;
            }
            else {
                targetElement = yield this.find(target);
            }
            const actions = this.driver.actions({ async: true, bridge: true });
            yield actions.dragAndDrop(sourceElement, targetElement).perform();
        });
    }
    /**
     * Drags an element by a specified pixel offset.
     *
     * Moves an element by dragging it a specific number of pixels in X and Y directions.
     * Useful for testing sliders, resizable panels, draggable windows, and custom drag interactions.
     *
     * @param element - Element to drag (WebElement, By locator, or CSS selector)
     * @param offsetX - Horizontal offset in pixels (positive = right, negative = left)
     * @param offsetY - Vertical offset in pixels (positive = down, negative = up)
     * @returns Promise that resolves when drag completes
     *
     * @example
     * ```typescript
     * // Drag slider to the right
     * await app.dragByOffset('.slider-handle', 100, 0);
     *
     * // Drag element down and left
     * await app.dragByOffset('.draggable-box', -50, 75);
     *
     * // Resize panel by dragging splitter
     * await app.dragByOffset('.splitter', 0, -100);
     * ```
     */
    dragByOffset(element, offsetX, offsetY) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element);
            }
            const actions = this.driver.actions({ async: true, bridge: true });
            yield actions.dragAndDrop(element, { x: offsetX, y: offsetY }).perform();
        });
    }
    /**
     * Types text into an input element.
     *
     * Automatically finds the element, optionally clears existing content, types the text,
     * and can optionally press Enter after typing. Perfect for form filling and text input.
     *
     * @param element - Input element to type into (WebElement, By locator, or CSS selector)
     * @param text - Text to type
     * @param options - Optional configuration
     * @param options.clear - Whether to clear existing content first (default: true)
     * @param options.sendEnter - Whether to press Enter after typing (default: false)
     * @returns Promise that resolves when typing completes
     *
     * @example
     * ```typescript
     * // Type into input (clears existing text by default)
     * await app.type('#username', 'testuser');
     *
     * // Type and submit with Enter
     * await app.type('#search', 'search query', { sendEnter: true });
     *
     * // Type without clearing existing text
     * await app.type('#notes', 'additional text', { clear: false });
     *
     * // Fill form fields
     * await app.type('#email', 'user@example.com');
     * await app.type('#password', 'secret123');
     * await app.click('#login-button');
     * ```
     */
    type(element_1, text_1) {
        return __awaiter(this, arguments, void 0, function* (element, text, { clear = true, sendEnter = false } = {}) {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element);
            }
            if (clear) {
                yield element.clear();
            }
            yield element.sendKeys(text);
            if (sendEnter) {
                yield this.sendKey(selenium_webdriver_1.Key.ENTER);
            }
        });
    }
    /**
     * Sends a single keyboard key press.
     *
     * Simulates pressing a keyboard key. Use Selenium's Key constants for special keys.
     *
     * @param key - Key to press (use Key.ENTER, Key.TAB, Key.ESCAPE, etc.)
     * @returns Promise that resolves when key press completes
     *
     * @example
     * ```typescript
     * import { Key } from '@progress/kendo-e2e';
     *
     * // Press Enter
     * await app.sendKey(Key.ENTER);
     *
     * // Press Tab to move focus
     * await app.sendKey(Key.TAB);
     *
     * // Press Escape to close modal
     * await app.sendKey(Key.ESCAPE);
     *
     * // Press Arrow Down
     * await app.sendKey(Key.ARROW_DOWN);
     * ```
     */
    sendKey(key) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.driver.actions({ async: true, bridge: true }).sendKeys(key).perform();
        });
    }
    /**
     * Sends a two-key combination (e.g., Ctrl+C).
     *
     * Convenience method for common two-key combinations. For more keys, use {@link sendKeysCombination}.
     *
     * @param key1 - First key (typically a modifier like Key.CONTROL)
     * @param key2 - Second key
     * @returns Promise that resolves when key combination completes
     *
     * @example
     * ```typescript
     * import { Key } from '@progress/kendo-e2e';
     *
     * // Copy text
     * await app.sendKeyCombination(Key.CONTROL, 'c');
     *
     * // Paste text
     * await app.sendKeyCombination(Key.CONTROL, 'v');
     *
     * // Open browser dev tools (F12 might not work in some contexts)
     * await app.sendKeyCombination(Key.CONTROL, Key.SHIFT);
     * ```
     */
    sendKeyCombination(key1, key2) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.sendKeysCombination([key1, key2]);
        });
    }
    /**
     * Sends a combination of multiple keyboard keys simultaneously.
     *
     * Holds down all keys in order, then releases them in reverse order, simulating
     * a realistic key combination press.
     *
     * @param keys - Array of keys to press together
     * @returns Promise that resolves when key combination completes
     *
     * @example
     * ```typescript
     * import { Key } from '@progress/kendo-e2e';
     *
     * // Ctrl+Shift+Delete
     * await app.sendKeysCombination([Key.CONTROL, Key.SHIFT, Key.DELETE]);
     *
     * // Select all (Ctrl+A)
     * await app.sendKeysCombination([Key.CONTROL, 'a']);
     *
     * // Custom three-key combo
     * await app.sendKeysCombination([Key.ALT, Key.SHIFT, 'F']);
     * ```
     */
    sendKeysCombination(keys) {
        return __awaiter(this, void 0, void 0, function* () {
            const actions = this.driver.actions({ async: false, bridge: true });
            for (const key of keys) {
                actions.keyDown(key).pause(10);
            }
            for (const key of [...keys].reverse()) {
                actions.keyUp(key).pause(10);
            }
            yield actions.perform();
        });
    }
    /**
     * Sends Ctrl+key on Windows/Linux or Cmd+key on macOS automatically.
     *
     * Cross-platform helper that uses the appropriate modifier key for the current OS.
     * Perfect for common shortcuts like copy, paste, save, etc.
     *
     * @param key - Key to combine with Ctrl/Cmd
     * @returns Promise that resolves when key combination completes
     *
     * @example
     * ```typescript
     * // Copy (Ctrl+C on Windows/Linux, Cmd+C on macOS)
     * await app.sendControlKeyCombination('c');
     *
     * // Paste (cross-platform)
     * await app.sendControlKeyCombination('v');
     *
     * // Select all (cross-platform)
     * await app.sendControlKeyCombination('a');
     *
     * // Save (cross-platform)
     * await app.sendControlKeyCombination('s');
     * ```
     */
    sendControlKeyCombination(key) {
        return __awaiter(this, void 0, void 0, function* () {
            const control = (process.platform === 'darwin' ? selenium_webdriver_1.Key.COMMAND : selenium_webdriver_1.Key.CONTROL);
            yield this.sendKeysCombination([control, key]);
        });
    }
    isVisible(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            return yield this.waitSafely(conditions_1.EC.isVisible(element), { timeout: timeout, pollTimeout: pollTimeout });
        });
    }
    isNotVisible(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            return yield this.waitSafely(conditions_1.EC.notVisible(element), { timeout: timeout, pollTimeout: pollTimeout });
        });
    }
    isInViewport(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            return yield this.waitSafely(conditions_1.EC.isInViewport(element), { timeout: timeout, pollTimeout: pollTimeout });
        });
    }
    isNotInViewport(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            return yield this.waitSafely(conditions_1.EC.notInViewport(element), { timeout: timeout, pollTimeout: pollTimeout });
        });
    }
    hasFocus(element) {
        return __awaiter(this, void 0, void 0, function* () {
            return (element instanceof selenium_webdriver_1.WebElement)
                ? yield this.waitSafely(conditions_1.EC.hasFocus(element))
                : yield this.waitSafely(conditions_1.EC.hasFocus(yield this.find(element)));
        });
    }
    hasNoFocus(element) {
        return __awaiter(this, void 0, void 0, function* () {
            return (element instanceof selenium_webdriver_1.WebElement)
                ? yield this.waitSafely(conditions_1.EC.hasNoFocus(element))
                : yield this.waitSafely(conditions_1.EC.hasNoFocus(yield this.find(element)));
        });
    }
    hasText(element, text) {
        return __awaiter(this, void 0, void 0, function* () {
            return (element instanceof selenium_webdriver_1.WebElement)
                ? yield this.waitSafely(conditions_1.EC.hasText(element, text))
                : yield this.waitSafely(conditions_1.EC.hasText(yield this.find(element), text));
        });
    }
    hasValue(element, value) {
        return __awaiter(this, void 0, void 0, function* () {
            return (element instanceof selenium_webdriver_1.WebElement)
                ? yield this.waitSafely(conditions_1.EC.hasValue(element, value))
                : yield this.waitSafely(conditions_1.EC.hasValue(yield this.find(element), value));
        });
    }
    hasAttribute(element_1, attribute_1, value_1) {
        return __awaiter(this, arguments, void 0, function* (element, attribute, value, exactMatch = true) {
            return (element instanceof selenium_webdriver_1.WebElement)
                ? yield this.waitSafely(conditions_1.EC.hasAttribute(element, attribute, value, exactMatch))
                : yield this.waitSafely(conditions_1.EC.hasAttribute(yield this.find(element), attribute, value, exactMatch));
        });
    }
    hasClass(element_1, value_1) {
        return __awaiter(this, arguments, void 0, function* (element, value, exactMatch = false) {
            return (element instanceof selenium_webdriver_1.WebElement)
                ? yield this.waitSafely(conditions_1.EC.hasClass(element, value, exactMatch))
                : yield this.waitSafely(conditions_1.EC.hasClass(yield this.find(element), value, exactMatch));
        });
    }
    sleep(milliseconds) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.driver.sleep(milliseconds);
        });
    }
    /**
     * Waits for a condition to become true.
     *
     * Core waiting method that polls a condition until it returns true or timeout is reached.
     * Use predefined conditions from {@link EC} class or create custom conditions.
     *
     * @param condition - Condition function or WebElementCondition to wait for
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait in milliseconds (default: 10000)
     * @param options.message - Custom error message if condition times out (default: 'Failed to satisfy condition.')
     * @param options.pollTimeout - Interval between condition checks in milliseconds (default: 25)
     * @returns Promise that resolves when condition is met
     * @throws Error with the specified message if condition is not met within timeout
     *
     * @example
     * ```typescript
     * // Wait for element to be visible
     * await app.wait(EC.isVisible('#modal'));
     *
     * // Wait with custom timeout and message
     * await app.wait(EC.hasText(element, 'Success'), {
     *   timeout: 15000,
     *   message: 'Success message did not appear'
     * });
     *
     * // Wait for custom condition
     * await app.wait(async () => {
     *   const count = await app.findAll('.item');
     *   return count.length > 5;
     * }, { message: 'Less than 5 items found' });
     * ```
     */
    wait(condition_1) {
        return __awaiter(this, arguments, void 0, function* (condition, { timeout = 10000, message = 'Failed to satisfy condition.', pollTimeout = 25 } = {}) {
            yield this.driver.wait(condition, timeout, message, pollTimeout);
        });
    }
    /**
     * Waits for a condition without throwing an error if it fails.
     *
     * Returns true if condition is met, false if timeout is reached. Perfect for conditional
     * logic in tests where you want to check if something happened without failing the test.
     *
     * @param condition - Condition function or WebElementCondition to wait for
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between condition checks in milliseconds (default: 25)
     * @returns Promise resolving to true if condition met, false if timeout reached
     *
     * @example
     * ```typescript
     * // Check if element appears (don't fail if it doesn't)
     * const appeared = await app.waitSafely(EC.isVisible('.optional-message'));
     * if (appeared) {
     *   console.log('Message was shown');
     * }
     *
     * // Conditional test flow
     * const hasModal = await app.waitSafely(EC.isVisible('.modal'), { timeout: 3000 });
     * if (hasModal) {
     *   await app.click('.modal .close');
     * }
     *
     * // Check if element has specific text
     * const hasText = await app.waitSafely(EC.hasText('#status', 'Complete'));
     * ```
     */
    waitSafely(condition_1) {
        return __awaiter(this, arguments, void 0, function* (condition, { timeout = 10000, pollTimeout = 25 } = {}) {
            try {
                yield this.driver.wait(condition, timeout, null, pollTimeout);
                return true;
            }
            catch (_a) {
                return false;
            }
        });
    }
    /**
     * Waits for an element to stop moving or resizing (animation to complete).
     *
     * Monitors element position and size, waiting until they remain stable for a poll interval.
     * Essential for reliable interaction with animated elements.
     *
     * @param element - Element to monitor (WebElement, By locator, or CSS selector)
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between stability checks in milliseconds (default: 50)
     * @returns Promise that resolves when element is stable
     * @throws Error if element doesn't stabilize within timeout
     *
     * @example
     * ```typescript
     * // Wait for sliding panel to stop
     * await app.waitForAnimation('.slide-panel');
     * await app.click('.slide-panel button');
     *
     * // Wait for expanding accordion
     * await app.click('.accordion-header');
     * await app.waitForAnimation('.accordion-content');
     *
     * // Use before taking screenshots of animated content
     * await app.waitForAnimation('.chart');
     * const screenshot = await app.getScreenshot();
     * ```
     */
    waitForAnimation(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 50 } = {}) {
            const locatorStringValue = (element instanceof selenium_webdriver_1.WebElement)
                ? 'element'
                : element;
            yield this.wait(conditions_1.EC.isVisible(element), { timeout: timeout, message: `Failed to find ${locatorStringValue}` });
            const isElementStable = () => __awaiter(this, void 0, void 0, function* () {
                const rect = (element instanceof selenium_webdriver_1.WebElement)
                    ? yield element.getRect()
                    : yield (yield this.find(element)).getRect();
                yield this.sleep(pollTimeout);
                const newRect = (element instanceof selenium_webdriver_1.WebElement)
                    ? yield element.getRect()
                    : yield (yield this.find(element)).getRect();
                return (rect.x === newRect.x &&
                    rect.y === newRect.y &&
                    rect.width === newRect.width &&
                    rect.height === newRect.height);
            });
            yield this.wait(isElementStable, { timeout: timeout, message: `Element ${locatorStringValue} is still moving or resizing.` });
        });
    }
    getScreenshot() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.driver.takeScreenshot();
        });
    }
    /**
     * Gets the visible text content of an element.
     *
     * Returns the text that would be visible to a user, excluding hidden elements.
     * Automatically finds the element if a locator is provided.
     *
     * @param element - Element to get text from (WebElement, By locator, or CSS selector)
     * @returns Promise resolving to the element's text, or undefined if element has no text
     *
     * @example
     * ```typescript
     * // Get button text
     * const buttonText = await app.getText('#submit-btn');
     * console.log(buttonText); // 'Submit Form'
     *
     * // Get paragraph content
     * const message = await app.getText('.success-message');
     *
     * // Get text from found element
     * const element = await app.find('.label');
     * const text = await app.getText(element);
     * ```
     */
    getText(element) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element);
            }
            try {
                return yield element.getText();
            }
            catch (_a) {
                return undefined;
            }
        });
    }
    /**
     * Gets the value of an HTML attribute from an element.
     *
     * Retrieves attribute values like 'href', 'src', 'disabled', 'data-*', etc.
     * Returns null if the attribute doesn't exist.
     *
     * @param element - Element to get attribute from (WebElement, By locator, or CSS selector)
     * @param attribute - Name of the attribute to retrieve
     * @returns Promise resolving to the attribute value or null
     *
     * @example
     * ```typescript
     * // Get link href
     * const url = await app.getAttribute('a.download', 'href');
     *
     * // Check if button is disabled
     * const isDisabled = await app.getAttribute('#submit', 'disabled');
     *
     * // Get data attribute
     * const userId = await app.getAttribute('.user', 'data-user-id');
     *
     * // Get input value
     * const value = await app.getAttribute('#email', 'value');
     * ```
     */
    getAttribute(element, attribute) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element);
            }
            return yield element.getAttribute(attribute);
        });
    }
    /**
     * Gets a JavaScript property value from an element.
     *
     * Different from {@link getAttribute} - this gets DOM properties (like 'value', 'checked')
     * which may differ from HTML attributes. Properties reflect the current state.
     *
     * @param element - Element to get property from (WebElement, By locator, or CSS selector)
     * @param property - Name of the property to retrieve
     * @returns Promise resolving to the property value
     *
     * @example
     * ```typescript
     * // Get checkbox checked state (property, not attribute)
     * const isChecked = await app.getProperty('#agree', 'checked');
     *
     * // Get input value (current value, not initial)
     * const currentValue = await app.getProperty('#username', 'value');
     *
     * // Get element's innerHTML
     * const html = await app.getProperty('.container', 'innerHTML');
     *
     * // Get computed style property
     * const display = await app.getProperty('#element', 'style');
     * ```
     */
    getProperty(element, property) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element);
            }
            const script = function (element, property) { return element[property]; };
            return yield this.driver.executeScript(script, element, property);
        });
    }
    /**
     * Gets the text color (color CSS property) of an element as hex value.
     *
     * Converts the color from any format (rgb, rgba, named) to hex format (#RRGGBB).
     * Useful for visual testing and theme verification.
     *
     * @param element - Element to get color from (WebElement, By locator, or CSS selector)
     * @returns Promise resolving to hex color string (e.g., '#ff0000')
     *
     * @example
     * ```typescript
     * // Check error message color
     * const errorColor = await app.getColor('.error-message');
     * expect(errorColor).toBe('#ff0000'); // red
     *
     * // Verify link color
     * const linkColor = await app.getColor('a.primary');
     *
     * // Check themed text color
     * const textColor = await app.getColor('.themed-text');
     * ```
     */
    getColor(element) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element);
            }
            const color = yield element.getCssValue('color');
            return (0, rgb_to_hex_1.rgbToHex)(color);
        });
    }
    /**
     * Gets the background color (background-color CSS property) of an element as hex value.
     *
     * Converts the background color from any format to hex format (#RRGGBB).
     * Useful for verifying button states, highlights, and theme colors.
     *
     * @param element - Element to get background color from (WebElement, By locator, or CSS selector)
     * @returns Promise resolving to hex color string (e.g., '#ffffff')
     *
     * @example
     * ```typescript
     * // Check button background
     * const btnBg = await app.getBackgroundColor('#primary-btn');
     * expect(btnBg).toBe('#007bff'); // bootstrap primary
     *
     * // Verify selected item highlight
     * const selectedBg = await app.getBackgroundColor('.selected');
     *
     * // Check alert background color
     * const alertBg = await app.getBackgroundColor('.alert-warning');
     * ```
     */
    getBackgroundColor(element) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(element instanceof selenium_webdriver_1.WebElement)) {
                element = yield this.find(element);
            }
            const color = yield element.getCssValue('background-color');
            return (0, rgb_to_hex_1.rgbToHex)(color);
        });
    }
    /**
     * Hides the text cursor/caret for cleaner screenshots.
     *
     * When called without arguments, hides the cursor globally for all input/textarea elements.
     * When called with an element, hides the cursor only for that specific element.
     * Global hiding persists until page reload.
     *
     * @param element - Optional specific element to hide cursor for (WebElement, By locator, or CSS selector)
     * @param options - Optional configuration
     * @param options.timeout - Maximum time to wait for element in milliseconds (default: 10000)
     * @param options.pollTimeout - Interval between retry attempts in milliseconds (default: 25)
     * @returns Promise that resolves when cursor is hidden
     *
     * @example
     * ```typescript
     * // Hide cursor globally before screenshot
     * await app.hideCursor();
     * const screenshot = await app.getScreenshot();
     *
     * // Hide cursor for specific input
     * await app.focus('#username');
     * await app.hideCursor('#username');
     * const inputScreenshot = await app.getScreenshot();
     *
     * // Hide cursor in focused field for visual test
     * await app.type('#search', 'test');
     * await app.hideCursor('#search');
     * ```
     *
     * @see For more details, see [hideCursor documentation](../../docs/hideCursor.md)
     */
    hideCursor(element_1) {
        return __awaiter(this, arguments, void 0, function* (element, { timeout = 10000, pollTimeout = 25 } = {}) {
            if (!element) {
                yield this.driver.executeScript(() => {
                    const existing = document.head.querySelector('style[data-hide-caret="true"]');
                    if (existing) {
                        return;
                    }
                    const style = document.createElement('style');
                    style.setAttribute('data-hide-caret', 'true');
                    style.textContent = 'input, textarea { caret-color: transparent !important; }';
                    document.head.appendChild(style);
                });
            }
            else {
                if (!(element instanceof selenium_webdriver_1.WebElement)) {
                    element = yield this.find(element, { timeout: timeout, pollTimeout: pollTimeout });
                }
                yield this.driver.executeScript((el) => {
                    if (el && el instanceof HTMLElement) {
                        el.style.caretColor = 'transparent';
                    }
                }, element);
            }
        });
    }
    /**
     * Creates an expectation API for the specified element selector.
     * This provides a fluent interface for asserting element states with automatic retry logic.
     * The expect API will continuously retry finding the element and checking the condition
     * until it passes or the timeout is reached (default: 3000ms).
     *
     * @param selector - CSS selector string or By locator to identify the element
     * @returns ExpectApi object with assertion methods
     *
     * @example
     * ```typescript
     * // Assert element has specific text (default 3s timeout)
     * await app.expect('#result').toHaveText('Welcome user');
     *
     * // Assert element is visible
     * await app.expect('.modal').toBeVisible();
     *
     * // Assert element is not visible
     * await app.expect('.spinner').not.toBeVisible();
     *
     * // Assert with custom timeout and message
     * await app.expect('#message').toHaveText('Success', {
     *   timeout: 5000,
     *   message: 'Failed to load results.'
     * });
     *
     * // Assert using regex pattern
     * await app.expect('#status').toHaveText(/completed|success/i, { timeout: 10000 });
     *
     * // Assert element has value
     * await app.expect('#input').toHaveValue('expected value', { timeout: 2000 });
     *
     * // Assert element has focus
     * await app.expect('#activeInput').toHaveFocus();
     *
     * // Assert element has attribute
     * await app.expect('#btn').toHaveAttribute('disabled', 'true');
     *
     * // Assert element has class (partial match)
     * await app.expect('#div').toHaveClass('active', { exactMatch: false });
     *
     * // Assert element is enabled/disabled
     * await app.expect('#submit').toBeEnabled();
     * await app.expect('#submit').toBeDisabled();
     *
     * // Assert checkbox/radio is checked
     * await app.expect('#agree').toBeChecked();
     * await app.expect('#opt-out').not.toBeChecked();
     *
     * // Assert partial text match
     * await app.expect('#message').toContainText('success');
     *
     * // Assert element count
     * await app.expect('.list-item').toHaveCount(5);
     * ```
     */
    expect(selector) {
        const by = typeof selector === 'string' ? selenium_webdriver_1.By.css(selector) : selector;
        return (0, expect_1.expectSelector)(this.driver, by);
    }
}
exports.WebApp = WebApp;
//# sourceMappingURL=web-app.js.map