CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-detox

Gray box end-to-end testing and automation framework for mobile applications with advanced synchronization capabilities

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

web-view-testing.mddocs/

Web View Testing

Complete testing capabilities for hybrid applications with web view content, supporting both regular and secured web elements with DOM-based interactions and assertions.

Capabilities

Web View Selection

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'));

Web Element Interactions

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);

JavaScript Execution

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);

Web Element Selection

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');

Secured Web Elements (iOS Only)

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);

Web Element Assertions

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();

Multi-Element Selection

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();

Common Web View Patterns

Typical usage patterns for hybrid app testing.

Form Interactions

// 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();
}

Dynamic Content

// 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);
}

Single Page Application Navigation

// 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');
}

Error Handling

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);
}

Types

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

docs

assertions.md

configuration.md

device-management.md

element-interaction.md

element-selection.md

index.md

synchronization.md

web-view-testing.md

tile.json