UNPKG

@progress/kendo-e2e

Version:

Kendo UI end-to-end test utilities.

1,155 lines (1,154 loc) 54.6 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; 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 rgb2hex_1 = __importDefault(require("rgb2hex")); /** * 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 visible.` : `Element located by ${element} is not visible.`; 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 or By locator) * @param target - Element to drop onto (WebElement or By locator) * @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.By) { sourceElement = yield this.find(source); } else { sourceElement = source; } if (target instanceof selenium_webdriver_1.By) { targetElement = yield this.find(target); } else { targetElement = 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 '@kendo/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 '@kendo/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 '@kendo/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(); }); } /** * @deprecated Use executeScript with proper types instead * @internal */ executeScript(script) { return __awaiter(this, void 0, void 0, function* () { return yield this.driver.executeScript(script); }); } /** * 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, rgb2hex_1.default)(color).hex; }); } /** * 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); }