EXTENSIONS
UI Testing
Automate cross-browser web testing using Karate's built-in driver. Karate provides Chrome DevTools Protocol support for native Chrome automation, W3C WebDriver compatibility for cross-browser testing, and Playwright integration for advanced scenarios—all with the same simple Gherkin syntax.
On this page:
- Basic Setup - Launch browsers and navigate to pages
- Driver Configuration - Browser options, headless, remote WebDriver, proxy
- Driver Types - Chrome, Playwright, WebDriver, Appium
- Locators - CSS, XPath, wildcards, friendly locators, tree walking
- Element Interactions - Click, input, select, mouse, file upload
- Waiting Strategies - waitFor, retry, waitUntil, chaining
- Browser JavaScript - script(), scriptAll(), scriptAwait(), function composition
- Pages and Frames - Tabs, iframes, dialogs
- Cookies - Set, get, delete cookies
- Screenshots - Capture, PDF, visual regression
- HTTP Interception - Mock browser requests with Karate mocks
- Hybrid Tests - Combine API and UI testing
- Mobile Testing - Device emulation, Appium
- Docker and CI - karate-chrome, Selenium Grid, distributed testing
- Debugging - VS Code extension, karate.stop(), highlight
- Code Reuse - Shared features, locator patterns
- Java API - Driver and Chrome programmatic APIs
To understand how Karate compares to other UI automation frameworks, read: The world needs an alternative to Selenium - so we built one.
Basic Browser Setup
Configure the driver to launch a browser and navigate to a page:
Feature: Simple browser test
Scenario: Open a webpage
# Tell Karate to use Chrome browser
* configure driver = { type: 'chrome' }
# Open the GitHub login page - this launches the browser
* driver 'https://github.com/login'
# Verify the page loaded by checking the title
* match driver.title contains 'GitHub'
The driver keyword navigates to a URL and initializes the browser instance. All subsequent steps use this driver until the scenario ends.
- The browser launches on the first
driverkeyword - The browser closes automatically when the scenario ends
- Use
driver.quit()only if you need explicit cleanup mid-scenario
Driver Configuration
Configuration Options
Configure browser behavior with the configure driver statement:
Feature: Driver configuration
Scenario: Chrome with options
# headless: run without visible window, showDriverLog: log HTTP traffic for debugging
* configure driver = { type: 'chrome', headless: true, showDriverLog: true }
* driver 'https://example.com'
| Key | Description | Default |
|---|---|---|
type | Browser type (see Driver Types) | - |
executable | Path to browser or driver executable | Auto-detected |
start | Whether Karate should start the executable | true |
stop | Whether to close browser after scenario (set false for debugging) | true |
port | Port for driver communication | Varies by type |
host | Host for driver communication | localhost |
headless | Run without visible browser window (Chrome only) | false |
timeout | Page load timeout in milliseconds | 30000 |
pollAttempts | Number of attempts to wait for port to be ready | 20 |
pollInterval | Milliseconds between poll attempts | 250 |
showDriverLog | Include WebDriver HTTP traffic in report | false |
showProcessLog | Include executable/driver logs in report | false |
showBrowserLog | Include browser console logs in report | true |
addOptions | Additional CLI arguments as array | null |
beforeStart | OS command to run before scenario (e.g., start video recording) | null |
afterStop | OS command to run after scenario (e.g., stop video recording) | null |
videoFile | Path to video file to embed in HTML report | null |
httpConfig | HTTP client config for remote WebDriver, e.g., { readTimeout: 120000 } | null |
attach | URL pattern to attach to existing Chrome session (type: chrome, start: false) | null |
userDataDir | Path to Chrome user data directory (pass null to use system defaults) | Auto-created |
highlight | Highlight elements before actions (for demos/debugging) | false |
highlightDuration | Duration to highlight elements in milliseconds | 3000 |
screenshotOnFailure | Auto-capture screenshot on test failure | true |
Headless Mode
Run tests without a visible browser window. Ideal for CI/CD pipelines where no display is available:
Feature: Headless testing
Scenario: Run in headless mode
# headless: true runs Chrome without opening a visible window
* configure driver = { type: 'chrome', headless: true }
* driver 'https://example.com'
# You can still take screenshots in headless mode
* screenshot()
Chrome Options
Pass Chrome-specific arguments and preferences. This example shows how to set up driver config globally in karate-config.js:
function fn() {
var config = {
driverConfig: {
type: 'chrome',
// Chrome command-line arguments
addOptions: [
'--disable-notifications', // Block notification popups
'--disable-popup-blocking', // Allow popups (useful for OAuth flows)
'--start-maximized' // Open browser maximized
],
// Chrome preferences (browser settings)
prefs: {
'download.default_directory': '/tmp/downloads' // Set download folder
}
}
};
// Apply driver config globally for all scenarios
karate.configure('driver', config.driverConfig);
return config;
}
webDriverUrl
Connect to a remote WebDriver server (Selenium Grid, Zalenium, cloud providers):
Feature: Remote WebDriver
Scenario: Connect to Selenium Grid
# start: false means don't launch a local browser - connect to remote instead
# webDriverUrl points to the Selenium Grid hub
* configure driver = { type: 'chromedriver', start: false, webDriverUrl: 'http://localhost:4444/wd/hub' }
* driver 'https://example.com'
webDriverSession
Pass custom capabilities to WebDriver. This is useful for headless mode, window size, or browser-specific options:
Feature: Custom WebDriver session
Background:
# Define WebDriver capabilities - this controls browser behavior
# alwaysMatch: capabilities that must be supported by the browser
* def session = { capabilities: { alwaysMatch: { browserName: 'chrome', 'goog:chromeOptions': { args: ['--headless', 'window-size=1280,720'] } } } }
Scenario: Chrome with custom capabilities
# Pass the session config to the driver
* configure driver = { type: 'chromedriver', webDriverSession: '#(session)' }
* driver 'https://example.com'
Common capabilities you can customize:
acceptInsecureCerts- Accept self-signed certificatesmoz:firefoxOptions- Firefox-specific options (e.g., headless mode)proxy- Proxy configuration (see Proxy Configuration)
Also see driver.sessionId to retrieve the WebDriver session ID for downloading test reports or videos.
webDriverPath
For Appium or Selenium Grid on localhost, you may need to append a path:
Feature: Appium with path
Scenario: Connect to local Appium
* configure driver = { type: 'android', webDriverPath: '/wd/hub' }
* driver { webDriverSession: { desiredCapabilities: { app: 'com.example.app' } } }
Driver Types
Choose the driver type based on your testing needs:
| Type | Port | Description |
|---|---|---|
chrome | 9222 | Native Chrome via DevTools Protocol. Recommended for development. |
playwright | 4444 | Playwright integration for cross-browser testing. |
msedge | 9222 | Microsoft Edge (Chromium) via DevTools Protocol. |
chromedriver | 9515 | W3C Chrome WebDriver. |
geckodriver | 4444 | W3C Firefox WebDriver. |
safaridriver | 5555 | W3C Safari WebDriver (macOS only). |
msedgedriver | 9515 | W3C Microsoft Edge WebDriver. |
mswebdriver | 17556 | Microsoft Edge Legacy WebDriver (deprecated). |
iedriver | 5555 | Internet Explorer 11 WebDriver (deprecated). |
android | 4723 | Android automation via Appium. |
ios | 4723 | iOS automation via Appium. |
winappdriver | 4727 | Windows desktop application automation. |
Start with type: 'chrome' for development—it's fastest and requires no additional setup. Switch to WebDriver types for cross-browser testing or CI environments.
Playwright Integration
Playwright provides cross-browser support with Chromium, Firefox, and WebKit engines.
Setup
Add the Playwright dependency before karate-core:
<dependency>
<groupId>io.karate</groupId>
<artifactId>karate-playwright</artifactId>
<scope>test</scope>
</dependency>
Basic Usage
Feature: Playwright browser
Scenario: Test with Playwright
* configure driver = { type: 'playwright' }
* driver 'https://example.com'
* match driver.title == 'Example Domain'
Playwright Options
Configure browser type, context, and other Playwright-specific settings:
Feature: Playwright options
Background:
* def pwOptions = { browserType: 'firefox', context: { viewport: { width: 1280, height: 720 } } }
Scenario: Firefox with Playwright
* configure driver = { type: 'playwright', playwrightOptions: '#(pwOptions)' }
* driver 'https://example.com'
| Option | Description | Default |
|---|---|---|
browserType | chromium, firefox, or webkit | chromium |
channel | Browser channel (e.g., chrome, msedge) | chrome |
context | Playwright browser context options | - |
installBrowsers | Auto-download browsers on first run | true |
For connecting to a remote Playwright server, use playwrightUrl:
Feature: Remote Playwright
Scenario: Connect to Playwright server
* configure driver = { type: 'playwright', start: false, playwrightUrl: 'ws://localhost:4444' }
* driver 'https://example.com'
Playwright Legacy (NodeJS)
For environments requiring a separate Playwright server, you can start one using NodeJS:
npm i -D playwright
Create a server script (playwright/server.js):
const playwright = require('playwright');
const port = process.argv[2] || 4444;
const browserType = process.argv[3] || 'chromium';
const headless = process.argv[4] == 'true';
const serverPromise = playwright[browserType].launchServer({ headless: headless, port: port });
serverPromise.then(bs => console.log(bs.wsEndpoint()));
Create a batch file to start the server, and configure Karate to use it:
Feature: Playwright Legacy
Scenario: Use external Playwright server
* configure driver = { type: 'playwright', executable: 'path/to/start-server' }
* driver 'https://example.com'
Karate scans the log for ws:// URLs and passes three arguments to the executable: port, browserType, and headless.
Proxy Configuration
For Chrome, use addOptions:
Feature: Chrome proxy
Scenario: With proxy
* configure driver = { type: 'chrome', addOptions: ['--proxy-server="https://proxy:5000"'] }
* driver 'https://example.com'
For WebDriver types, use webDriverSession:
Feature: WebDriver proxy
Background:
* def session = { capabilities: { browserName: 'chrome', proxy: { proxyType: 'manual', httpProxy: 'proxy:5000' } } }
Scenario: With proxy
* configure driver = { type: 'chromedriver', webDriverSession: '#(session)' }
* driver 'https://example.com'
Locators
CSS Selectors
Use CSS selectors to target elements:
Feature: CSS selectors
Scenario: Common CSS patterns
* driver 'https://example.com'
# By ID
* click('#login-button')
# By class
* click('.btn-primary')
# By attribute
* click('[data-testid="submit"]')
# Descendant
* input('.form-group input[name="email"]', 'test@example.com')
# Pseudo-class
* click('ul.menu li:first-child')
XPath Expressions
Use XPath for complex element selection (prefix with /):
Feature: XPath selectors
Scenario: XPath patterns
* driver 'https://example.com'
# By text content
* click('//button[text()="Submit"]')
# Partial text match
* click('//a[contains(text(), "Learn More")]')
# Navigate table structure
* def email = text('//tr[td[text()="John"]]/td[2]')
Wildcard Locators
Find elements by visible text content using {} syntax:
| Locator | Description |
|---|---|
{a}Click Me | First <a> with exact text "Click Me" |
{}Click Me | First element (any tag) with exact text "Click Me" |
{^}Click | First element containing text "Click" |
{^span}Click | First <span> containing text "Click" |
{div:2}Click Me | Second <div> with exact text "Click Me" |
{span/a}Click Me | First <a> inside <span> with exact text "Click Me" |
Feature: Wildcard locators
Scenario: Text-based selection
* driver 'https://example.com'
# Exact text match with tag
* click('{button}Submit')
# Partial text match
* click('{^a}Learn')
# Any tag with exact text
* waitFor('{}Success').exists
Wildcard locators select based on what users see, making tests more readable and resistant to CSS/HTML changes.
Friendly Locators
Find elements by their position relative to visible text—useful for form fields near labels:
| Method | Finds Element |
|---|---|
rightOf() | To the right of given locator |
leftOf() | To the left of given locator |
above() | Above given locator |
below() | Below given locator |
near() | Near given locator (any direction) |
Feature: Friendly locators
Scenario: Form input by label position
* driver 'https://example.com/form'
# Input field to the right of "Username" label
* rightOf('{}Username').input('john_doe')
# Checkbox to the left of "Remember me" text
* leftOf('{}Remember me').click()
# Dropdown below "Country" label
* below('{}Country').select('United States')
# Button near "Submit" text (handles varied layouts)
* near('{}Submit').click()
By default, friendly locators search for <input> elements. Override with find():
Feature: Friendly locator with find
Scenario: Find specific element type
* driver 'https://example.com'
# Find a span instead of input
* rightOf('{}Label').find('span').click()
# Find by text content
* rightOf('{}Label').find('{}Click Me').click()
Tree Walking
Navigate the DOM structure from a known element:
| Property | Returns |
|---|---|
parent | Parent element |
children | Array of child elements |
firstChild | First child element |
lastChild | Last child element |
previousSibling | Previous sibling element |
nextSibling | Next sibling element |
Feature: Tree walking
Scenario: Navigate DOM structure
* driver 'https://example.com'
* def row = locate('//td[text()="John"]')
# Click the button in the same row
* row.parent.locate('button').click()
# Get all cells in the row
* def cells = row.parent.children
* match cells.length == 4
Locator Lookup Pattern
Maintain locators in a JSON file for reusability across tests:
{
"login": {
"username": "#username",
"password": "#password",
"submit": "[data-testid='login-btn']"
},
"dashboard": {
"welcome": "{}Welcome",
"logout": "{a}Logout"
}
}
Feature: Locator lookup
Background:
* call read('locators.json')
Scenario: Use named locators
* driver 'https://example.com/login'
* input(login.username, 'john')
* input(login.password, 'secret')
* click(login.submit)
* waitFor(dashboard.welcome).exists
This pattern provides the benefits of centralized locators without the complexity of traditional Page Object Models.
Element Interactions
Click and Input
Basic element interactions for filling forms and clicking buttons:
Feature: Basic interactions
Scenario: Form interaction
* driver 'https://example.com/login'
# input(locator, value) - types text into a field
* input('#username', 'john@example.com')
* input('#password', 'secret123')
# click(locator) - clicks a button or link
* click('button[type="submit"]')
# text(locator) - gets the visible text from an element
* match text('#welcome') contains 'Welcome'
Special Keys
Use the Key object for special keystrokes:
Feature: Special keys
Scenario: Keyboard input
* driver 'https://example.com'
# Enter key
* input('#search', 'karate testing' + Key.ENTER)
# Key combinations
* input('#editor', Key.CONTROL + 'a')
# Tab through form
* input('#field1', 'value')
* input('#field1', Key.TAB)
# Escape to close modal
* input('body', Key.ESCAPE)
Delayed Input
Add delays between keystrokes for JavaScript-heavy forms:
Feature: Delayed input
Scenario: Slow typing for autocomplete
* driver 'https://example.com'
# 100ms delay between each character
* input('#search', 'new york', 100)
# Or with array syntax
* input('#search', ['n', 'e', 'w', Key.SPACE, 'y', 'o', 'r', 'k'], 100)
Select Dropdowns
For native HTML <select> elements:
Feature: Select dropdown
Scenario: Dropdown selection
* driver 'https://example.com/form'
# By visible text
* select('select[name="country"]', '{}United States')
# By value attribute
* select('select[name="state"]', 'CA')
# By index
* select('select[name="city"]', 2)
JavaScript-Powered Dropdowns
Modern dropdowns (Bootstrap, Material UI, etc.) require mouse interactions because they're not native HTML <select> elements:
Feature: JavaScript dropdown
Scenario: Bootstrap dropdown
* driver 'https://example.com'
# First click opens the dropdown menu
* mouse('.dropdown-toggle').click()
# Second click selects an item - use mouse() for JS-rendered elements
* mouse('{a}Option One').click()
For iterating through all dropdown items (useful for testing each option):
Feature: Loop through dropdown
Scenario: Select each option
* driver 'https://example.com'
# Get all dropdown items as an array
* def items = locateAll('a.dropdown-item')
# Define a function that opens dropdown, clicks item, waits
* def selectItem = function(item) { mouse('.dropdown-toggle').click(); item.mouse().click(); delay(500) }
# Loop through each item
* items.forEach(selectItem)
File Uploads
Multiple approaches for file upload:
Feature: File upload
Scenario: Native file input
* configure driver = { type: 'chrome' }
* driver 'https://example.com/upload'
# Chrome-native file input
* driver.inputFile('#file-upload', 'classpath:test-data/document.pdf')
# Multiple files
* driver.inputFile('#file-upload', ['classpath:file1.txt', 'classpath:file2.jpg'])
* click('#upload-button')
For cross-browser compatibility, use multipart upload:
Feature: Multipart upload
Scenario: HTTP-based upload
Given url 'https://example.com/upload'
And multipart file file = { read: 'classpath:document.pdf', filename: 'document.pdf', contentType: 'application/pdf' }
When method post
Then status 200
Mouse Actions
Complex mouse interactions:
Feature: Mouse actions
Scenario: Mouse operations
* driver 'https://example.com'
# Hover
* mouse('.menu-trigger').move()
* waitFor('.submenu').exists
# Click at coordinates
* mouse(100, 200).click()
# Double-click
* mouse('#item').doubleClick()
# Right-click (context menu)
* mouse('#file').rightClick()
# Drag and drop
* mouse('.draggable').down()
* mouse('.drop-zone').move().up()
Scroll
Scroll elements into view:
Feature: Scrolling
Scenario: Scroll to element
* driver 'https://example.com'
* scroll('#footer')
# Chain with click
* scroll('#hidden-button').click()
Waiting Strategies
Karate provides multiple wait strategies to handle dynamic content. Choose the right one for your use case:
waitFor()
Wait for an element to appear. Essential for pages with AJAX or lazy-loaded content:
Feature: Wait for element
Scenario: Wait for dynamic content
* driver 'https://example.com'
# Click triggers an async operation
* click('#load-data')
# waitFor() blocks until element exists, then returns the element
* waitFor('#results').exists
# Now safe to check text content
* match text('#results') contains 'Loaded'
waitForUrl()
Wait for the browser URL to change. Use after click actions that trigger page navigation:
Feature: Wait for URL
Scenario: Wait after navigation
* driver 'https://example.com'
# This click navigates to another page
* click('#next-page')
# waitForUrl uses "contains" match - no need for full URL
* waitForUrl('/page-2')
# URL now guaranteed to contain '/page-2'
* match driver.url contains '/page-2'
waitForText()
Wait for specific text content to appear in an element. Useful for status messages or loading states:
Feature: Wait for text
Scenario: Wait for status message
* driver 'https://example.com'
# Submit triggers processing
* click('#submit')
# Wait until element contains the text "Complete" (uses "contains" match)
* waitForText('#status', 'Complete')
waitForEnabled()
Wait for an element to become enabled. Common for form validation where submit is disabled until valid:
Feature: Wait for enabled
Scenario: Wait for button to enable
* driver 'https://example.com'
# Toggle checkbox to accept terms
* input('#terms', Key.SPACE)
# Button becomes enabled after checkbox is checked - wait then click
* waitForEnabled('#submit').click()
waitForResultCount()
Wait for a specific number of elements to exist. Perfect for data tables that load incrementally:
Feature: Wait for results
Scenario: Wait for table rows
* driver 'https://example.com'
# Search triggers loading of results
* click('#search')
# Wait until exactly 5 result rows exist, returns the elements
* def rows = waitForResultCount('.result-row', 5)
* match rows.length == 5
waitForAny()
Wait for any one of multiple possible elements to appear. Useful when different outcomes are possible:
Feature: Wait for any
Scenario: Handle conditional UI
* driver 'https://example.com'
# Don't know if we'll get success or error - wait for either
* retry(5, 10000).waitForAny('#success', '#error')
# optional() safely clicks if element exists, does nothing if not
* optional('#success').click()
* optional('#error').click()
waitUntil()
Wait for a JavaScript condition to become true. The most flexible wait - use for custom conditions:
Feature: Wait until condition
Scenario: Wait for page ready
* driver 'https://example.com'
# Wait for document to fully load (JS runs in browser context)
* waitUntil("document.readyState == 'complete'")
# With locator: _ is the element. Wait for progress bar to reach 100%
* waitUntil('#progress', "_.style.width == '100%'")
# Wait for element to not be disabled (! means "not")
* waitUntil('#submit', '!_.disabled')
retry()
Configure retry behavior for subsequent actions. Unlike waitFor(), retry() re-attempts the action itself:
Feature: Retry configuration
Scenario: Custom retry settings
* driver 'https://example.com'
# retry() with no args: use default (3 attempts, 3 second intervals)
* retry().click('#slow-button')
# retry(count): custom number of attempts
* retry(5).click('#very-slow-button')
# retry(count, interval): custom attempts AND wait time between them
* retry(5, 10000).click('#extremely-slow-button')
Default retry settings: 3 attempts with 3000ms intervals. Configure globally in karate-config.js:
// Change default retry for all scenarios
karate.configure('retry', { count: 5, interval: 2000 });
Chaining
Chain wait methods with actions for fluent, readable tests:
Feature: Method chaining
Scenario: Fluent chains
* driver 'https://example.com'
# Wait then click
* waitFor('#button').click()
# Retry with wait
* retry(5, 10000).waitForEnabled('#submit').click()
# Scroll then input
* scroll('#hidden-field').input('value')
# Mouse chain
* mouse('#menu').move()
* waitFor('.submenu').exists
Browser JavaScript
script()
Execute JavaScript in the browser:
Feature: Browser JavaScript
Scenario: Execute script
* driver 'https://example.com'
# Simple evaluation
* def result = script('1 + 2')
* match result == 3
# DOM manipulation
* script("document.querySelector('#hidden').style.display = 'block'")
# Get element property
* match script('#myDiv', '_.innerHTML') contains 'Hello'
# Trigger event
* waitFor('#input').script("_.dispatchEvent(new Event('change'))")
scriptAll()
Execute JavaScript on all matching elements:
Feature: Script all elements
Scenario: Extract table data
* driver 'https://example.com'
* def texts = scriptAll('table td', '_.textContent')
* match texts contains 'Expected Value'
scriptAll() with Filter
Filter results using a JavaScript predicate:
Feature: Filter script results
Scenario: Get specific cells
* driver 'https://example.com'
# Filter for cells containing "data"
* def filtered = scriptAll('td', '_.textContent', function(x){ return x.contains('data') })
* match filtered == ['data1', 'data2']
locateAll() with Filter
Filter located elements:
Feature: Filter located elements
Scenario: Find elements by attribute pattern
* driver 'https://example.com'
* def filter = function(el){ return el.attribute('data-id').startsWith('user_') }
* def userElements = locateAll('[data-id]', filter)
* userElements[0].click()
Function Composition
Create reusable JavaScript functions:
Feature: Function composition
Background:
# Reusable function to extract text from elements
* def getTexts = function(locator){ return scriptAll(locator, '_.textContent') }
Scenario: Use composed function
* driver 'https://example.com'
* def menuItems = getTexts('.menu-item')
* match menuItems contains 'Home'
Looping
Looping Over Elements
Iterate through located elements:
Feature: Loop over elements
Scenario: Click all items
* driver 'https://example.com'
* def buttons = locateAll('.action-button')
* buttons.forEach(btn => btn.click())
Loop Until
Repeat an action until a condition is met:
Feature: Loop until condition
Scenario: Delete all rows
* driver 'https://example.com'
* def deleteRow =
"""
function() {
if (!exists('.data-row')) return true;
click('.delete-button');
delay(500);
}
"""
* waitUntil(deleteRow)
Pages and Frames
Page Navigation
Control browser navigation:
Feature: Page navigation
Scenario: Navigate browser history
* driver 'https://example.com/page1'
* click('#next')
* waitForUrl('/page2')
# Go back
* driver.back()
* match driver.url contains '/page1'
# Go forward
* driver.forward()
# Refresh
* driver.refresh()
# Hard reload (clears cache)
* reload()
Multiple Windows and Tabs
Switch between browser windows:
Feature: Multiple windows
Scenario: Handle new tab
* driver 'https://example.com'
* click('#open-new-tab')
# Switch by index
* switchPage(1)
* match driver.title contains 'New Page'
# Switch back
* switchPage(0)
# Switch by title (contains match)
* switchPage('New Page')
# Close current tab
* close()
Working with Iframes
Switch context to an iframe:
Feature: Iframe handling
Scenario: Interact with iframe content
* driver 'https://example.com'
# Switch by selector
* switchFrame('#editor-iframe')
* input('#content', 'Hello from iframe')
# Switch back to main page
* switchFrame(null)
# Switch by index
* switchFrame(0)
Dialogs
Handle JavaScript dialogs (alert, confirm, prompt):
Feature: Dialog handling
Scenario: Handle dialogs
* driver 'https://example.com'
* click('#show-alert')
# Get dialog text
* match driver.dialogText == 'Are you sure?'
# Accept dialog
* dialog(true)
# Or cancel
* dialog(false)
# Enter text in prompt and accept
* dialog(true, 'User input')
Cookies
Manage browser cookies:
Feature: Cookie management
Scenario: Cookie operations
* driver 'https://example.com'
# Set cookie
* cookie({ name: 'session', value: 'abc123', domain: '.example.com' })
# Get specific cookie
* def myCookie = cookie('session')
* match myCookie.value == 'abc123'
# Get all cookies
* def allCookies = driver.cookies
# Delete specific cookie
* deleteCookie('session')
# Clear all cookies
* clearCookies()
* match driver.cookies == '#[0]'
Screenshots and Visual Testing
Capturing Screenshots
Take screenshots during tests:
Feature: Screenshots
Scenario: Capture screenshots
* driver 'https://example.com'
# Full page screenshot (auto-embedded in report)
* screenshot()
# Named screenshot
* screenshot('homepage')
# Element screenshot
* screenshot('#main-content')
# Get bytes without auto-embed
* def bytes = screenshot(false)
* def file = karate.write(bytes, 'custom.png')
Full Page Screenshots
Capture the entire scrollable page (Chrome only):
Feature: Full page screenshot
Scenario: Capture scrollable content
* configure driver = { type: 'chrome' }
* driver 'https://example.com/long-page'
* def bytes = driver.screenshotFull()
* karate.write(bytes, 'full-page.png')
PDF Generation
Generate PDF from the current page (Chrome only):
Feature: PDF generation
Scenario: Save page as PDF
* configure driver = { type: 'chrome' }
* driver 'https://example.com/report'
* def pdfBytes = pdf({ orientation: 'landscape' })
* karate.write(pdfBytes, 'report.pdf')
Visual Regression Testing
Compare screenshots against baselines:
Feature: Visual regression
Scenario: Compare with baseline
* driver 'https://example.com'
* def current = screenshot('#product-image', true)
* compareImage { baseline: 'classpath:baselines/product.png', latest: current, failureThreshold: 2 }
See Image Comparison for detailed visual testing options.
Intercepting HTTP Requests
Intercept browser HTTP requests and route them to Karate mock servers. This enables:
- Simulating API failures and edge cases
- Replacing slow backend calls with fast mocks
- Testing without external dependencies
driver.intercept()
Set up request interception (Chrome only). Routes matching URLs to a Karate mock feature:
Feature: HTTP interception
Scenario: Mock API responses
* configure driver = { type: 'chrome' }
# Start with blank page to set up interception before any requests
* driver 'about:blank'
# Intercept any URL matching pattern and route to mock feature
# patterns: array of URL patterns (* = wildcard)
# mock: path to Karate mock feature file
* driver.intercept({ patterns: [{ urlPattern: '*api.example.com/*' }], mock: 'mock-api.feature' })
# Now navigate - requests will be intercepted
* driver 'https://example.com'
* click('#load-data')
# The mock feature returned this response
* waitForText('#result', 'Mocked Response')
Mock Feature Setup
Create a mock feature file to handle intercepted requests. Each Scenario matches different URL patterns:
@ignore
Feature: API mock
Background:
# Enable CORS - browser needs this for cross-origin requests
* configure cors = true
Scenario: pathMatches('/users/{id}')
# pathParams.id contains the {id} from URL, e.g., /users/123 -> id = "123"
* def response = { id: '#(pathParams.id)', name: 'Mock User' }
Scenario: pathMatches('/data')
# Return data from a JSON file
* def response = read('mock-data.json')
Scenario:
# Catch-all for unmatched requests - return 404
* def responseStatus = 404
Inspecting Intercepted Requests
Access request data from the mock:
Feature: Inspect requests
Scenario: Verify intercepted requests
* configure driver = { type: 'chrome' }
* driver 'about:blank'
* def mock = driver.intercept({ patterns: [{ urlPattern: '*/api/*' }], mock: 'tracking-mock.feature' })
* driver 'https://example.com'
* click('#submit')
* delay(1000)
# Get saved requests from mock
* def requests = mock.get('savedRequests')
* match requests[0].path == '/api/submit'
driver.intercept()works only withtype: 'chrome'- Playwright supports
urlPatternsonly - Call
intercept()once per scenario (one mock feature, multiple routes)
Hybrid Tests
Combine API and UI testing in a single flow. Karate's unique strength is seamlessly mixing HTTP API calls with browser automation:
Feature: Hybrid API and UI test
Background:
# Set up API base URL for HTTP calls
* url 'https://api.example.com'
Scenario: Create via API, verify in UI
# STEP 1: Create test data via REST API (faster than UI)
Given path 'orders'
And request { product: 'Widget', quantity: 2, customerId: 123 }
When method post
Then status 201
# Save the order ID from API response
* def orderId = response.id
# STEP 2: Switch to browser and verify the order appears in UI
* configure driver = { type: 'chrome' }
* driver 'https://example.com/orders/' + orderId
* match text('.order-status') == 'Pending'
* match text('.quantity') == '2'
Pre-authenticated Sessions
Skip the login UI by getting auth credentials via API and injecting cookies. This dramatically speeds up tests:
Feature: Skip login with API auth
Background:
# STEP 1: Get auth token via API (no browser needed)
* url 'https://api.example.com'
* path 'auth/login'
* request { username: 'test', password: 'secret' }
* method post
# Save the token from API response
* def authToken = response.token
Scenario: Access protected page directly
* configure driver = { type: 'chrome' }
# STEP 2: Start browser on blank page (needed to set cookies)
* driver 'about:blank'
# STEP 3: Set the auth cookie in the browser
* cookie({ name: 'auth_token', value: authToken, domain: '.example.com' })
# STEP 4: Now navigate to protected page - already logged in!
* driver 'https://example.com/dashboard'
* match text('#welcome') contains 'Welcome'
Pre-authenticating via API can save 5-10 seconds per test by avoiding login UI interactions.
Mobile Testing
Device Emulation
Emulate mobile devices in Chrome:
Feature: Mobile emulation
Scenario: iPhone emulation
* configure driver = { type: 'chrome' }
* driver.emulateDevice(375, 812, 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)')
* driver 'https://example.com'
* waitFor('.mobile-menu').exists
Custom Viewport
Test responsive layouts:
Feature: Responsive testing
Scenario: Test breakpoints
* configure driver = { type: 'chrome' }
* driver 'https://example.com'
# Desktop
* driver.dimensions = { width: 1920, height: 1080 }
* waitFor('.desktop-nav').exists
# Tablet
* driver.dimensions = { width: 768, height: 1024 }
* waitFor('.tablet-menu').exists
# Mobile
* driver.dimensions = { width: 375, height: 667 }
* waitFor('.mobile-hamburger').exists
Appium Integration
Test native mobile apps with Appium:
Feature: Appium mobile test
Scenario: Android app test
* configure driver = { type: 'android', webDriverPath: '/wd/hub' }
* driver { webDriverSession: { desiredCapabilities: { app: 'com.example.myapp' } } }
* click('@login-button')
* input('#username', 'test')
| Locator Prefix | Platform | Description |
|---|---|---|
| (none) | android/ios | Name |
@ | android/ios | Accessibility ID |
# | android/ios | ID |
: | ios | iOS predicate string |
^ | ios | iOS class chain |
- | android | Android UIAutomator |
Screen Recording
Record test execution (Appium only):
Feature: Screen recording
Scenario: Record mobile test
* configure driver = { type: 'android' }
* driver.startRecordingScreen()
# ... test steps ...
* driver.saveRecordingScreen('test-recording.mp4', true)
You can also use driver.stopRecordingScreen() for more control, and both methods accept recording options as JSON input.
hideKeyboard
Dismiss the mobile keyboard (Appium only):
Feature: Hide keyboard
Scenario: Dismiss keyboard after input
* input('#search', 'test query')
* driver.hideKeyboard()
* click('#submit')
Docker and CI
karate-chrome Container
Run tests with the official Karate Chrome container:
docker run --name karate --rm -p 9222:9222 -p 5900:5900 \
-e KARATE_SOCAT_START=true \
--security-opt seccomp=chrome.json \
karatelabs/karate-chrome
Connect from your test:
Feature: Docker Chrome
Scenario: Connect to container
* configure driver = { type: 'chrome', start: false, showDriverLog: true }
* driver 'https://example.com'
Features of karate-chrome:
- Chrome in full mode (non-headless)
- VNC server on port 5900 (password:
karate) - Video recording saved to
/tmp/karate.mp4 - DevTools Protocol on port 9222
Connect to localhost:5900 with a VNC client to watch tests run in real-time. On Mac: open vnc://localhost:5900
For more Docker options, see the Docker wiki.
DockerTarget
Configure Docker-based testing in karate-config.js:
function fn() {
var config = {};
if (karate.env == 'ci') {
karate.configure('driverTarget', {
docker: 'karatelabs/karate-chrome',
secComp: 'src/test/resources/chrome.json',
vncPort: 5900
});
}
return config;
}
Custom Target
Implement the Target interface for complex CI requirements:
public class CustomTarget implements Target {
@Override
public Map<String, Object> start(ScenarioRuntime sr) {
// Start browser/container, return driver config
return Map.of("type", "chrome", "start", false);
}
@Override
public Map<String, Object> stop(ScenarioRuntime sr) {
// Cleanup, return video path if recorded
return Map.of("videoFile", "/tmp/test.mp4");
}
}
If the machine running Karate is not the same as the target host (e.g., a sibling Docker container), configure DockerTarget with the remoteHost and/or useDockerHost properties.
See karate-devicefarm-demo for AWS DeviceFarm integration example.
Distributed Testing
For running tests across multiple machines or Docker containers, refer to the Distributed Testing wiki.
Selenium Grid
Connect to Selenium Grid or cloud providers:
Feature: Selenium Grid
Scenario: BrowserStack example
* def session = { capabilities: { browserName: 'chrome', 'bstack:options': { os: 'Windows', osVersion: '11' } } }
* configure driver = { type: 'chromedriver', start: false, webDriverUrl: 'https://user:key@hub.browserstack.com/wd/hub', webDriverSession: '#(session)' }
* driver 'https://example.com'
Debugging
VS Code Extension
The Karate extension for VS Code provides step-through debugging for UI tests. You can pause execution, inspect variables, and even step backwards to re-run steps.
Watch the debugging demo video for a walkthrough.
karate.stop()
Pause test execution to inspect browser state:
Feature: Debug pause
Scenario: Pause for inspection
* driver 'https://example.com'
* input('#username', 'admin')
# Pause here - open localhost:9000 to continue
* karate.stop(9000)
* click('#submit')
When paused, the console shows:
*** waiting for socket, type the command below:
curl http://localhost:9000
Open the URL in a browser or use curl to continue execution.
Always remove karate.stop() calls before committing tests!
highlight()
Visually highlight elements during debugging:
Feature: Visual debugging
Scenario: Highlight elements
* configure driver = { type: 'chrome', highlight: true, highlightDuration: 2000 }
* driver 'https://example.com'
# Manual highlight
* highlight('#important-element')
* delay(2000)
* click('#important-element')
highlightAll()
Highlight all matching elements:
Feature: Highlight all
Scenario: Highlight multiple elements
* driver 'https://example.com'
* highlightAll('input')
* delay(2000)
timeout()
Temporarily change the HTTP read timeout for slow-loading pages:
Feature: Timeout adjustment
Scenario: Wait for slow page
* configure driver = { type: 'chrome' }
# Wait up to 3 minutes for page load
* timeout(3 * 60 * 1000)
* driver 'https://example.com/slow-page'
# Reset to defaults
* timeout()
driver.scriptAwait()
Wait for a JavaScript promise to resolve (Chrome only):
Feature: Await promise
Scenario: Wait for async operation
* configure driver = { type: 'chrome' }
* driver 'https://example.com'
# Wait for accessibility audit to complete
* def result = driver.scriptAwait('axe.run()')
* match result.violations == '#[0]'
Console Logs
Access browser console output:
Feature: Console logs
Scenario: Check for errors
* driver 'https://example.com'
* click('#trigger-action')
* def logs = driver.logs
* match logs !contains 'ERROR'
Code Reuse
Shared Features
Create reusable feature files for common flows:
@ignore
Feature: Login flow
Scenario:
* configure driver = { type: 'chrome' }
* driver baseUrl + '/login'
* input('#username', username)
* input('#password', password)
* click('#login-button')
* waitForUrl('/dashboard')
Feature: User workflow
Background:
* def baseUrl = 'https://example.com'
* def username = 'testuser'
* def password = 'secret'
* call read('login.feature')
Scenario: Complete user action
* click('#create-new')
* input('#name', 'Test Item')
* click('#save')
Browser instances cannot be shared across scenarios with callonce. Use call in shared scope instead.
JavaScript Function Reuse
When reusing functions defined before driver initialization, use karate.get('driver'):
Feature: Function reuse
Background:
* def deleteAllRows =
"""
function() {
var driver = karate.get('driver');
while (driver.exists('.data-row')) {
driver.click('.delete-button');
java.lang.Thread.sleep(500);
}
}
"""
Scenario: Clean up data
* configure driver = { type: 'chrome' }
* driver 'https://example.com/admin'
* deleteAllRows()
Java API
Driver Java API
Start a driver programmatically from Java:
import com.intuit.karate.driver.Driver;
Driver driver = Driver.start("chrome");
driver.setUrl("https://example.com");
driver.click("button");
driver.waitForText("#result", "Success");
driver.quit();
Chrome Java API
Use Chrome directly for common tasks like HTML-to-PDF conversion or full-page screenshots:
import com.intuit.karate.FileUtils;
import com.intuit.karate.driver.chrome.Chrome;
import java.io.File;
import java.util.Collections;
public class ChromeExample {
public static void main(String[] args) {
Chrome chrome = Chrome.startHeadless();
chrome.setLocation("https://example.com");
// Generate PDF
byte[] pdf = chrome.pdf(Collections.EMPTY_MAP);
FileUtils.writeToFile(new File("page.pdf"), pdf);
// Screenshot (visible viewport)
byte[] screenshot = chrome.screenshot();
FileUtils.writeToFile(new File("screenshot.png"), screenshot);
// Full page screenshot (entire scrollable area)
byte[] fullPage = chrome.screenshotFull();
FileUtils.writeToFile(new File("fullpage.png"), fullPage);
chrome.quit();
}
}
For custom Chrome options:
Map<String, Object> options = new HashMap<>();
options.put("executable", "/path/to/chrome");
options.put("headless", true);
options.put("maxPayloadSize", 8388608); // 8MB for large outputs
options.put("useFrameAggregation", true); // Enable if server sends multiple frames
Chrome chrome = Chrome.start(options);
The pdf() method accepts a Map of options documented in Chrome DevTools Page.printToPDF.
Driver Command Reference
Navigation
| Command | Description |
|---|---|
driver 'url' | Navigate to URL (initializes driver on first call) |
driver.url = 'url' | Navigate to URL |
driver.back() | Go back in history |
driver.forward() | Go forward in history |
driver.refresh() | Reload page (keeps cache) |
reload() | Hard reload (clears cache) |
maximize() | Maximize window |
minimize() | Minimize window |
fullscreen() | Enter fullscreen |
close() | Close current tab/page |
driver.quit() | Close browser |
Element Actions
| Command | Description |
|---|---|
click(locator) | Click element |
input(locator, value) | Type into element |
clear(locator) | Clear input field |
submit() | Submit form (chain before click for page load) |
focus(locator) | Focus element |
scroll(locator) | Scroll element into view |
select(locator, value) | Select dropdown option |
mouse(locator) | Get Mouse instance for advanced actions |
highlight(locator) | Visually highlight element |
highlightAll(locator) | Highlight all matching elements |
Element State
| Command | Description |
|---|---|
text(locator) | Get text content |
html(locator) | Get outer HTML |
value(locator) | Get input value |
value(locator, val) | Set input value |
attribute(locator, name) | Get HTML attribute value |
property(locator, name) | Get DOM property value |
enabled(locator) | Check if enabled |
exists(locator) | Check if exists (boolean) |
optional(locator) | Get element or no-op wrapper |
locate(locator) | Get Element instance |
locateAll(locator) | Get all matching Elements |
locateAll(locator, filter) | Get matching Elements with filter function |
position(locator) | Get absolute element position and size |
position(locator, true) | Get viewport-relative position and size |
Waiting
| Command | Description |
|---|---|
waitFor(locator) | Wait for element to exist |
waitForUrl(partial) | Wait for URL to contain string |
waitForText(locator, text) | Wait for text to appear |
waitForEnabled(locator) | Wait for element to be enabled |
waitForResultCount(locator, n) | Wait for n matching elements |
waitForAny(loc1, loc2, ...) | Wait for any element |
waitUntil(jsExpression) | Wait for JS condition |
retry() | Apply retry to next action |
delay(ms) | Sleep (avoid if possible) |
timeout(ms) | Set HTTP read timeout for slow pages |
Chrome-Only Methods
| Command | Description |
|---|---|
driver.screenshotFull() | Capture entire scrollable page |
driver.scriptAwait(js) | Wait for JS promise to resolve |
driver.inputFile(locator, path) | Upload file to input element |
driver.emulateDevice(w, h, ua) | Emulate mobile device |
driver.intercept(config) | Intercept HTTP requests |
Driver Properties
| Property | Description |
|---|---|
driver.url | Current URL |
driver.title | Page title |
driver.dimensions | Window size (get/set) |
driver.cookies | All cookies |
driver.logs | Browser console logs |
driver.dialogText | Current dialog text |
driver.sessionId | WebDriver session ID |
Next Steps
- Visual testing: Image Comparison
- Mock servers: Test Doubles
- Performance testing: Performance Testing
- Reusable features: Calling Features
- More examples: Examples and Demos