@progress/kendo-e2e
Version:
Kendo UI end-to-end test utilities.
1,155 lines (1,154 loc) • 54.6 kB
JavaScript
"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);
}