Gray box end-to-end testing and automation framework for mobile applications with advanced synchronization capabilities
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Complete testing capabilities for hybrid applications with web view content, supporting both regular and secured web elements with DOM-based interactions and assertions.
Target and interact with web views within mobile applications using native element matchers.
/**
* Access web view for DOM-based element interactions
* @param matcher - Optional native matcher for specific web view (required for multiple web views)
* @returns Web view interface for element selection
*/
function web(matcher?: NativeMatcher): WebViewElement;
interface WebViewElement {
/**
* Select web element within the web view using DOM selectors
* @param webMatcher - DOM-based selector for web element
* @returns Web element interface with interaction methods
*/
element<T extends WebMatcher>(webMatcher: T): MaybeSecuredWebElement<T>;
/**
* Select specific web view by index when multiple exist (iOS only)
* @param index - Zero-based index of web view
* @returns Web view element at specified index
*/
atIndex(index: number): WebViewElement;
}Usage Examples:
// Single web view (most common case)
const webElement = web().element(by.web.id('submit_button'));
// Specific web view in multi-web-view scenario
const specificWebView = web(by.id('main_webview'));
const webElement = specificWebView.element(by.web.className('action-button'));
// Select web view by index (iOS only)
const secondWebView = web().atIndex(1);
const webElement = secondWebView.element(by.web.tag('input'));Standard DOM element interactions adapted for mobile web view testing.
interface WebElement {
/**
* Tap web element
*/
tap(): Promise<void>;
/**
* Type text into web input element
* @param text - Text to type
* @param isContentEditable - Whether element is content-editable (ignored on iOS)
*/
typeText(text: string, isContentEditable?: boolean): Promise<void>;
/**
* Replace all text in web input element
* @param text - Replacement text
*/
replaceText(text: string): Promise<void>;
/**
* Clear text from web input element
*/
clearText(): Promise<void>;
/**
* Scroll web element into view
*/
scrollToView(): Promise<void>;
/**
* Get text content from web element
* @returns Current text content
*/
getText(): Promise<string>;
/**
* Set focus on web element
*/
focus(): Promise<void>;
/**
* Select all text in web input element (Android: content-editable only)
*/
selectAllText(): Promise<void>;
/**
* Move cursor to end of text in web input element (Android: content-editable only)
*/
moveCursorToEnd(): Promise<void>;
}Usage Examples:
// Basic web element interactions
await web().element(by.web.id('username')).typeText('john.doe@example.com');
await web().element(by.web.id('password')).typeText('secretpassword');
await web().element(by.web.className('submit-btn')).tap();
// Text manipulation
await web().element(by.web.name('email')).replaceText('new.email@example.com');
await web().element(by.web.id('search_input')).clearText();
// Advanced text operations
await web().element(by.web.id('editor')).focus();
await web().element(by.web.id('editor')).selectAllText();
await web().element(by.web.id('editor')).moveCursorToEnd();
// Scroll element into view
await web().element(by.web.id('footer_button')).scrollToView();
// Get element content
const currentText = await web().element(by.web.id('status_message')).getText();
console.log('Current status:', currentText);Execute custom JavaScript within web view context for advanced interactions and data retrieval.
/**
* Execute JavaScript function within web view context
* @param script - JavaScript function as string or function reference
* @param args - Optional arguments to pass to the script
* @returns Result of JavaScript execution
*/
function runScript(script: string, args?: unknown[]): Promise<any>;
function runScript<F>(script: (...args: any[]) => F, args?: unknown[]): Promise<F>;
/**
* Get current page URL from web view
* @returns Current page URL
*/
function getCurrentUrl(): Promise<string>;
/**
* Get current page title from web view
* @returns Current page title
*/
function getTitle(): Promise<string>;Usage Examples:
// Execute custom JavaScript
await web().element(by.web.id('custom_element')).runScript('element => element.click()');
// Execute with parameters
await web().element(by.web.id('input_field')).runScript(
'function setValue(element, value) { element.value = value; }',
['New Value']
);
// Execute with function reference
await web().element(by.web.id('canvas')).runScript(
function drawCircle(canvas, x, y, radius) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.stroke();
},
[100, 100, 50]
);
// Get page information
const currentUrl = await web().element(by.web.tag('body')).getCurrentUrl();
const pageTitle = await web().element(by.web.tag('body')).getTitle();
console.log('Page:', pageTitle, 'at', currentUrl);DOM-based element selection methods providing comprehensive targeting options.
/**
* Web element matchers for DOM-based selection
*/
interface ByWebFacade {
/** Select by DOM element ID */
id(id: string): WebMatcher;
/** Select by CSS class name */
className(className: string): WebMatcher;
/** Select by CSS selector */
cssSelector(cssSelector: string): WebMatcher;
/** Select by name attribute */
name(name: string): WebMatcher;
/** Select by XPath expression */
xpath(xpath: string): WebMatcher;
/** Select link by href attribute */
href(linkText: string): WebMatcher;
/** Select link by partial href match */
hrefContains(linkTextFragment: string): WebMatcher;
/** Select by HTML tag name */
tag(tagName: string): WebMatcher;
/** Select by element value (iOS only) */
value(value: string): WebMatcher;
/** Select by accessibility label (iOS only) */
label(text: string): MaybeSecuredWebMatcher;
/** Select secured element by type (iOS only) */
type(type: string): SecuredWebMatcher;
}Usage Examples:
// DOM ID selection
await web().element(by.web.id('login_form')).tap();
// CSS class selection
await web().element(by.web.className('primary-button')).tap();
// CSS selector selection
await web().element(by.web.cssSelector('input[type="email"]')).typeText('user@example.com');
await web().element(by.web.cssSelector('.modal .close-button')).tap();
// Name attribute selection
await web().element(by.web.name('username')).typeText('johndoe');
// XPath selection
await web().element(by.web.xpath('//button[@type="submit"]')).tap();
await web().element(by.web.xpath('//div[contains(@class, "error")]')).getText();
// Link selection
await web().element(by.web.href('https://example.com/terms')).tap();
await web().element(by.web.hrefContains('privacy')).tap();
// Tag selection
await web().element(by.web.tag('h1')).getText();
await web().element(by.web.tag('input')).typeText('search query');Enhanced security context for sensitive web interactions on iOS platform.
interface SecuredWebElement {
/**
* Tap secured web element (iOS only)
*/
tap(): Promise<void>;
/**
* Type text into secured web element (iOS only)
* @param text - Text to type
* @param isContentEditable - Whether element is content-editable
*/
typeText(text: string, isContentEditable: boolean): Promise<void>;
/**
* Replace text in secured web element (iOS only)
* @param text - Replacement text
*/
replaceText(text: string): Promise<void>;
/**
* Clear text from secured web element (iOS only)
*/
clearText(): Promise<void>;
}
interface MaybeSecuredWebMatcher {
/**
* Convert to secured web element (iOS only)
* @returns Secured web element interface
*/
asSecured(): IndexableSecuredWebElement;
}Usage Examples:
// iOS secured web elements
await web().element(by.web.label('Secure Login')).asSecured().tap();
await web().element(by.web.type('textField')).asSecured().typeText('password', false);
await web().element(by.web.type('secureTextField')).asSecured().replaceText('newpassword');
await web().element(by.web.type('textField')).asSecured().clearText();
// Multiple secured elements with index
await web().element(by.web.type('textField')).asSecured().atIndex(1).typeText('value', true);Validate web element states and content within web view context.
/**
* Web element assertion interface
*/
interface WebExpect {
/**
* Assert web element has specific text content
* @param text - Expected text content
*/
toHaveText(text: string): Promise<void>;
/**
* Assert web element exists in DOM tree
*/
toExist(): Promise<void>;
/**
* Negate web element assertion
*/
not: WebExpect;
}
/**
* Secured web element assertion interface (iOS only)
*/
interface SecuredWebExpect {
/**
* Assert secured web element exists in DOM tree (iOS only)
*/
toExist(): Promise<void>;
/**
* Negate secured web element assertion (iOS only)
*/
not: SecuredWebExpect;
}Usage Examples:
// Web element assertions
await expect(web().element(by.web.id('welcome_message'))).toHaveText('Welcome to our app!');
await expect(web().element(by.web.className('error-banner'))).toExist();
await expect(web().element(by.web.id('loading_spinner'))).not.toExist();
// Secured web element assertions (iOS only)
await expect(web().element(by.web.label('Secure Field')).asSecured()).toExist();
await expect(web().element(by.web.type('hiddenField')).asSecured()).not.toExist();Handle multiple matching web elements with indexing and iteration.
interface IndexableWebElement extends WebElement {
/**
* Select specific web element by index when multiple matches exist
* @param index - Zero-based index of element
* @returns Specific web element instance
*/
atIndex(index: number): WebElement;
}Usage Examples:
// Select specific element from multiple matches
await web().element(by.web.tag('button')).atIndex(0).tap(); // First button
await web().element(by.web.tag('button')).atIndex(2).tap(); // Third button
// Work with form inputs
await web().element(by.web.tag('input')).atIndex(0).typeText('username');
await web().element(by.web.tag('input')).atIndex(1).typeText('password');
// Select from list items
const itemText = await web().element(by.web.className('list-item')).atIndex(5).getText();Typical usage patterns for hybrid app testing.
// Complete form submission flow
async function fillAndSubmitForm() {
// Fill form fields
await web().element(by.web.name('email')).typeText('user@example.com');
await web().element(by.web.name('password')).typeText('securepassword');
await web().element(by.web.name('confirmPassword')).typeText('securepassword');
// Select dropdown option
await web().element(by.web.id('country_select')).tap();
await web().element(by.web.cssSelector('option[value="US"]')).tap();
// Check checkbox
await web().element(by.web.id('terms_checkbox')).tap();
// Submit form
await web().element(by.web.cssSelector('button[type="submit"]')).tap();
// Verify success
await expect(web().element(by.web.className('success-message'))).toExist();
}// Handle dynamic content loading
async function waitForDynamicContent() {
// Wait for initial load
await waitFor(web().element(by.web.id('content_container')))
.toExist()
.withTimeout(10000);
// Trigger more content
await web().element(by.web.id('load_more_button')).tap();
// Wait for new content
await waitFor(web().element(by.web.className('new-content')))
.toExist()
.withTimeout(15000);
}// Navigate SPA routes
async function navigateSPA() {
// Click navigation link
await web().element(by.web.href('/products')).tap();
// Wait for route change
await waitFor(web().element(by.web.id('products_page')))
.toExist()
.withTimeout(5000);
// Verify URL changed
const currentUrl = await web().element(by.web.tag('body')).getCurrentUrl();
expect(currentUrl).toContain('/products');
}Common error scenarios and resolution strategies for web view testing.
// Web view not ready
try {
await web().element(by.web.id('element')).tap();
} catch (error) {
// Wait for web view to load
await waitFor(element(by.type('WebView')))
.toBeVisible()
.withTimeout(10000);
// Retry interaction
await web().element(by.web.id('element')).tap();
}
// Element not found in DOM
try {
await web().element(by.web.id('missing_element')).tap();
} catch (error) {
// Check if element exists with different selector
const exists = await web().runScript('() => !!document.getElementById("missing_element")');
if (!exists) {
console.log('Element not found in DOM');
}
}
// JavaScript execution errors
try {
await web().element(by.web.id('element')).runScript('element => element.nonexistentMethod()');
} catch (error) {
console.log('JavaScript execution failed:', error.message);
}interface WebViewElement {
element<T extends WebMatcher>(webMatcher: T): MaybeSecuredWebElement<T>;
atIndex(index: number): WebViewElement;
}
interface WebElement {
tap(): Promise<void>;
typeText(text: string, isContentEditable?: boolean): Promise<void>;
replaceText(text: string): Promise<void>;
clearText(): Promise<void>;
scrollToView(): Promise<void>;
getText(): Promise<string>;
focus(): Promise<void>;
selectAllText(): Promise<void>;
moveCursorToEnd(): Promise<void>;
runScript(script: string | Function, args?: unknown[]): Promise<any>;
getCurrentUrl(): Promise<string>;
getTitle(): Promise<string>;
}
interface SecuredWebElement {
tap(): Promise<void>;
typeText(text: string, isContentEditable: boolean): Promise<void>;
replaceText(text: string): Promise<void>;
clearText(): Promise<void>;
}
interface WebMatcher {
__web__: any; // Internal marker
}
interface SecuredWebMatcher {
__web__: any; // Internal marker
}
interface MaybeSecuredWebMatcher {
__web__: any; // Internal marker
asSecured(): IndexableSecuredWebElement;
}Install with Tessl CLI
npx tessl i tessl/npm-detox