Appium Service
Handling the Appium server is out of the scope of the actual WebdriverIO project. This service helps you to run the Appium server seamlessly when running tests with the WDIO testrunner. It starts the Appium Server in a child process.
This package also includes a CLI command (npx start-appium-inspector) to quickly start the Appium server and open the Appium Inspector in your browser. See the CLI Command section for usage instructions.
Additionally, this package also includes a BETA feature - the Native Mobile Selector Performance Optimizer - which helps identify and optimize slow XPath selectors in your mobile tests. It tracks selector performance during test execution, suggests optimized alternatives, and validates them by testing optimized selectors during the run. At the end, you'll receive a comprehensive report showing which selectors should be replaced in your code for better performance. See the Native Mobile Selector Performance Optimizer section for details.
Installation
The easiest way is to keep @wdio/appium-service as a devDependency in your package.json, via:
npm install @wdio/appium-service --save-dev
Instructions on how to install WebdriverIO can be found here.
Configuration
In order to use the service you need to add appium to your service array:
// wdio.conf.js
export const config = {
// ...
port: 4723, // default appium port
services: ['appium'],
// ...
};
Options
The following options can be added to the wdio.conf.js file. To define options for the service you need to add the service to the services list in the following way:
// wdio.conf.js
export const config = {
// ...
port: 4723, // default appium port
services: [
['appium', {
// Appium service options here
// ...
}]
],
// ...
};
logPath
The path where all logs from the Appium server should be stored.
Type: String
Example:
export const config = {
// ...
services: [
['appium', {
logPath : './'
}]
],
// ...
}
command
To use your installation of Appium, e.g. globally installed, specify the command which should be started.
Type: String
Example:
export const config = {
// ...
services: [
['appium', {
command : 'appium'
}]
],
// ...
}
args
Map of arguments for the Appium server, passed directly to appium.
See the documentation for possible arguments.
The arguments are supplied in lower camel case. For instance, debugLogSpacing: true transforms into --debug-log-spacing, or they can be supplied as outlined in the Appium documentation.
Type: Object
Default: {}
Example:
export const config = {
// ...
services: [
['appium', {
args: {
// ...
debugLogSpacing: true,
platformName: 'iOS'
// ...
}
}]
],
// ...
}
Note: The utilization of aliases is discouraged and unsupported. Instead, please use the full property name in lower camel case.
CLI Command
This package includes a CLI command to quickly start the Appium server and open the Appium Inspector in your browser. This makes it easier to work with Appium when using WebdriverIO.
Usage
npx start-appium-inspector [options]
The command will:
- Check if the Appium Inspector plugin is installed (required for the Inspector to work)
- Automatically start the Appium server with the Inspector plugin enabled
- Open the Appium Inspector at
http://localhost:{port}/inspectorin your default browser - Handle cleanup when you press
Ctrl+C
Prerequisites
Make sure you have Appium installed with the drivers you need:
# Install Appium globally
npm install -g appium
# Install drivers (examples)
appium driver install uiautomator2 # For Android
appium driver install xcuitest # For iOS
Or install Appium locally in your project:
npm install --save-dev appium
Important: The Appium Inspector plugin must be installed for this CLI command to work. The command will automatically check if the plugin is installed before starting the server. If it's not installed, you'll see an error with instructions.
Install the Appium Inspector plugin:
# Add it as a local dependency
npm install --D appium-inspector-plugin
# Add it globally, depending on how you installed it before
appium plugin install inspector
For more information about installing and using the Appium Inspector plugin, see the Appium Inspector documentation.
Examples
Start with default port (4723):
npx start-appium-inspector
Start with a custom port:
npx start-appium-inspector --port=8080
Pass additional Appium server arguments:
npx start-appium-inspector --port=4723 --base-path=/wd/hub --relaxed-security
The command accepts all standard Appium server arguments. For a complete list of available arguments, see the Appium documentation.
Appium Inspector
The CLI automatically opens the Appium Inspector web application at http://localhost:{port}/inspector, which provides a GUI interface for inspecting and interacting with your mobile apps. The Inspector is served directly from the Appium server when the Inspector plugin is enabled. For more information about the Appium Inspector, visit the Appium Inspector GitHub repository.
Note:
- The Appium Inspector requires CORS to be enabled on the Appium server. The CLI automatically adds the
--allow-corsflag to ensure compatibility. - The CLI uses the
--use-plugins=inspectorflag to enable the Appium Inspector plugin. Before running the command, make sure you have installed the Appium Inspector plugin (see Prerequisites above).
Native Mobile Selector Performance Optimizer
⚠️ BETA Feature - This feature is currently in beta. All feedback is welcome!
The Native Mobile Selector Performance Optimizer helps identify and optimize slow XPath selectors in your mobile tests. During test execution, it:
- Tracks all XPath selector performance and measures execution times
- Analyzes XPath selectors and suggests optimized alternatives (iOS class chain, accessibility ID, etc.)
- Validates optimized selectors during the test run to ensure they work correctly
- Generates a comprehensive report at the end showing which selectors need to be replaced in your code
Important: This feature does not replace selectors in your code automatically. Instead, it provides a report with recommendations. You need to manually update your code based on the report findings. The feature only replaces selectors during test execution for validation purposes.
⚠️ Performance Impact: Enabling this feature adds significant overhead to your test execution time as it requires fetching and parsing the page source for each selector analysis. Do not enable this feature (constantly) in your CI/CD pipeline as it will slow down your tests. Recommended workflow:
- Enable the feature and run your tests
- Review the generated performance report
- Update your code with the optimized selectors from the report
- Create a PR with the optimizations
- Merge the PR
- Disable the feature and run tests normally
Note: Currently optimized for iOS only. Android support is coming in a future release.
Configuration
To enable the Native Mobile Selector Performance Optimizer, add trackSelectorPerformance to your Appium service configuration:
// wdio.conf.js
export const config = {
// ...
services: [
['appium', {
trackSelectorPerformance: {
pageObjectPaths: ['./tests/pageobjects']
}
}]
],
// ...
};
Options
enableCliReport
Enable or disable the CLI report output to the terminal. When enabled, a formatted performance report is printed to the terminal after test execution.
Note: The JSON report is always generated when trackSelectorPerformance is configured. This option only controls whether the report is also printed to the terminal.
Type: boolean
Default: false
Example:
export const config = {
// ...
services: [
['appium', {
trackSelectorPerformance: {
pageObjectPaths: ['./tests/pageobjects'],
enableCliReport: true // Enable terminal output
}
}]
],
// ...
}
enableMarkdownReport
Enable markdown report file generation. When enabled, a markdown file with the same content as the CLI report is written to the logs folder (the same directory as the JSON report).
Note: The JSON report is always generated when trackSelectorPerformance is configured. This option only controls whether the report is also printed to the terminal.
Type: boolean
Default: false
Example:
export const config = {
// ...
services: [
['appium', {
trackSelectorPerformance: {
pageObjectPaths: ['./tests/pageobjects'],
enableMarkdownReport: true // Generate a markdown report file
}
}]
],
// ...
}
reportPath
Path where the performance report files (JSON, and optionally markdown) should be saved. If not provided, falls back to config.outputDir, then appium service logPath. If none are set, an error will be thrown.
Type: string
Example:
export const config = {
// ...
services: [
['appium', {
trackSelectorPerformance: {
pageObjectPaths: ['./tests/pageobjects'],
reportPath: './reports/selector-performance'
}
}]
],
// ...
}
maxLineLength
Maximum line length for terminal and markdown report output. Lines longer than this will be wrapped at word boundaries.
Type: number
Default: 100
Example:
export const config = {
// ...
services: [
['appium', {
trackSelectorPerformance: {
pageObjectPaths: ['./tests/pageobjects'],
maxLineLength: 120
}
}]
],
// ...
}
pageObjectPaths
Paths to directories containing page objects or helper files where selectors may be defined. The service will search these directories to find selector locations and show file paths (e.g., "📍 Found at: TabBar.ts:3") in the report.
This option is required. The service will throw an error if it is not provided.
Type: string[]
Example:
export const config = {
// ...
services: [
['appium', {
trackSelectorPerformance: {
// Single directory
pageObjectPaths: ['./tests/pageobjects']
// Or multiple directories
// pageObjectPaths: ['./tests/pageobjects', './tests/pages', './tests/helpers']
}
}]
],
// ...
}
When pageObjectPaths is configured, the report will show file locations with line numbers:
📍 Found at: tests/screenobjects/components/TabBar.ts:3
Complete Example
export const config = {
// ...
services: [
['appium', {
trackSelectorPerformance: {
usePageSource: true,
enableCliReport: true,
enableMarkdownReport: true,
reportPath: './reports/selector-performance',
maxLineLength: 100,
pageObjectPaths: ['./tests/pageobjects', './tests/helpers']
}
}]
],
// ...
}
Using with Cloud Services (BrowserStack, Sauce Labs, etc.)
The Mobile Selector Performance Optimizer works independently of the local Appium server. This means you can use it with cloud-based testing services like BrowserStack, Sauce Labs, or any other Appium cloud provider.
When cloud capabilities are detected, the @wdio/appium-service automatically skips starting a local Appium server (since the cloud provider manages Appium for you), but the MSPO feature continues to work normally. It hooks into WebdriverIO's command lifecycle to track selector performance, regardless of where the Appium server is running.
Example configuration with BrowserStack:
// wdio.conf.js
export const config = {
// ...
services: [
['browserstack', {
// BrowserStack service options
}],
['appium', {
// No need to configure Appium server options - it won't start locally
// Just configure the MSPO feature:
trackSelectorPerformance: {
pageObjectPaths: ['./tests/pageobjects'],
enableCliReport: true,
enableMarkdownReport: true,
reportPath: './reports/selector-performance'
}
}]
],
// ...
};
Example configuration with Sauce Labs:
// wdio.conf.js
export const config = {
// ...
services: [
['sauce', {
// Sauce Labs service options
}],
['appium', {
trackSelectorPerformance: {
pageObjectPaths: ['./tests/pageobjects'],
enableCliReport: true,
enableMarkdownReport: true,
reportPath: './reports/selector-performance'
}
}]
],
// ...
};
What happens:
- The Appium launcher detects cloud capabilities and logs:
Could not identify any capability that indicates a local Appium session, skipping Appium launch - MSPO tracks all selector performance during test execution on the cloud device
- Each worker writes its performance data locally
- After all tests complete, the aggregator combines data from all workers and generates the final report
Expected output: The same comprehensive performance report (JSON, CLI, and/or Markdown) is generated locally, containing all selector performance data collected from your cloud-based iOS test runs. See the Report Output section for sample output.
Logging
The Mobile Selector Performance Optimizer logs via @wdio/logger with the namespace @wdio/appium-service:selector-optimizer. This means that all output respects WebdriverIO logLevel and per-logger overrides.
Log Level Hierarchy (from most verbose to silent):
trace→ Most verbose, includes all log messagesdebug→ Includes debug, info, warn, and error messagesinfo→ Includes info, warn, and error messages (default)warn→ Includes only warn and error messageserror→ Includes only error messagessilent→ No log output
When you set a log level, all levels at or above that level will be shown. For example, setting logLevel: 'info' will show info, warn, and error messages, but not debug or trace messages.
To silence the optimizer logs entirely, set:
export const config = {
// ...
logLevel: 'silent', // silences all @wdio/logger output, including the optimizer
// ...
}
How It Works
During Test Execution
-
Tracking: The service tracks all element-finding commands (
$,$$,custom$,custom$$) and measures their execution time. -
Analysis: When XPath selectors are detected, the service analyzes them and suggests optimized alternatives (iOS class chain, accessibility ID, etc.).
-
Validation: The service automatically tests optimized selectors during the test run to:
- Verify the optimized selector works correctly
- Measure actual performance improvements
- Ensure the suggestion is valid before you update your code
-
Data Collection: All performance data is collected along with test context (test file, suite name, test name) for accurate reporting.
After Test Execution
- Report Generation: At the end of the test run, a comprehensive performance report is generated showing:
- Top optimizations with the biggest performance gains
- Quick wins (shared selectors with high impact)
- All optimizations grouped by test file
- Performance metrics and improvement percentages
- Exact selector replacements needed in your code
Recommended Workflow
Step 1: Initial Analysis Run
// Enable the feature in your config
services: [
['appium', {
trackSelectorPerformance: {
pageObjectPaths: ['./tests/pageobjects'],
enableCliReport: true, // Enable CLI report output to terminal
enableMarkdownReport: true // Enable markdown report file generation
}
}]
]
Run your tests and review the generated report.
Step 2: Review the Report
- Check the "Top 10 Most Impactful Optimizations" section for the biggest wins
- Review "Quick Wins" for shared selectors used across multiple tests
- Look at "All Actions Required" grouped by test file to see what needs updating
Step 3: Update Your Code
- Manually replace XPath selectors in your code with the optimized alternatives from the report
- Example: Replace
//XCUIElementTypeButton[@name="Submit"]with-ios class chain:**/XCUIElementTypeButton[\name == "Submit"`]`
Step 4: Create Pull Request
- Commit your optimized selectors
- Create a PR with a clear description of the optimizations
Step 5: Merge and Disable
- After merging the PR, disable the feature:
services: [
['appium', {
// trackSelectorPerformance removed to disable the feature
}]
]
- Your tests will now run faster without the overhead of the optimizer
⚠️ Important: Do not keep this feature enabled constantly in your CI/CD pipeline. Use it periodically (e.g., weekly/monthly) to identify new optimization opportunities, then disable it for normal test runs.
Report Output
Note: The performance report is only generated when enableReporter is true (which is the default). When enableReporter is false, no report is generated and no JSON file is created.
When enabled, the service generates a detailed performance report that includes:
- Top 10 Most Impactful Optimizations: Selectors with the biggest performance improvements, showing original selector, optimized selector, and improvement metrics
- Quick Wins: Shared selectors used across multiple tests with high impact - optimize once, benefit everywhere
- All Actions Required: Complete list of optimizations grouped by test file, making it easy to update code systematically
- Performance Metrics: Duration improvements in milliseconds and percentages for each optimization
The report is saved as a JSON file in the specified reportPath (or default location) and also displayed in the terminal at the end of the test run.
Sample Report Output
Click to expand sample terminal report output
═══════════════════════════════════════════════════════════════════════════════
📊 Mobile Selector Performance Optimizer Report
═══════════════════════════════════════════════════════════════════════════════
Device: iPhone 16 Pro
Run Time: 07:46:00 → 07:53:15 (7m 14s)
Analyzed: 67 unique selectors (50 optimizable, 17 not recommended)
Total Potential Savings: 10.98s per test run (2.5% of total run time)
Average Improvement per Selector: 30.3% faster
📈 Summary
───────────────────────────────────────────────────────────────────────────────
🔴 High (>50% gain): 2 → Fix immediately
🟠 Medium (20-50% gain): 47 → Recommended
🟡 Low (10-20% gain): 1 → Minor optimization
⚠️ Slower in Testing: 17 → See warnings below
🎯 File-Based Fixes
───────────────────────────────────────────────────────────────────────────────
Update these specific lines for immediate impact:
📁 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/components/NativeAlert.ts
L8: $('//XCUIElementTypeAlert') → $("-ios predicate string:type == 'XCUIElementTyp...")
⚡ 410.0ms/use × 14 uses = 5.74s total
└─ File total: 5.74s saved (1 selector)
📁 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts
L5: $('//*[@name="Drag-drop-screen"]') → $("~Drag-drop-screen")
⚡ 61.0ms/use × 3 uses = 183.1ms total
L8: $('//*[@name="drag-l1"]') → $("~drag-l1") [73.6ms]
L9: $('//*[@name="drag-c1"]') → $("~drag-c1") [70.1ms]
L10: $('//*[@name="drag-r1"]') → $("~drag-r1") [73.0ms]
L11: $('//*[@name="drag-l2"]') → $("~drag-l2") [71.7ms]
L12: $('//*[@name="drag-c2"]') → $("~drag-c2") [53.9ms]
L13: $('//*[@name="drag-r2"]') → $("~drag-r2") [51.3ms]
L14: $('//*[@name="drag-l3"]') → $("~drag-l3") [59.3ms]
L15: $('//*[@name="drag-c3"]') → $("~drag-c3") [58.1ms]
L16: $('//*[@name="drag-r3"]') → $("~drag-r3") [50.1ms]
L26: $('//*[@name="renew"]') → $("~renew") [36.3ms]
L27: $('//*[@name="button-Retry"]') → $("~button-Retry")
⚡ 347.1ms/use × 2 uses = 694.3ms total
└─ File total: 1.47s saved (12 selectors)
📁 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/FormsScreen.ts
L13: $('//*[@name="text-input"]') → $("~text-input") [25.4ms]
L14: $('//*[@name="input-text-result"]') → $("~input-text-result")
⚡ 32.7ms/use × 2 uses = 65.3ms total
L15: $('//*[@name="switch"]') → $("~switch")
⚡ 36.5ms/use × 5 uses = 182.4ms total
L18: $('//*[@name="dropdown-chevron"]') → $("~dropdown-chevron")
⚡ 27.4ms/use × 3 uses = 82.3ms total
L19: $('//*[@name="button-Active"]') → $("~button-Active")
⚡ 57.3ms/use × 4 uses = 229.3ms total
L20: $('//*[@name="button-Inactive"]') → $("~button-Inactive")
⚡ 47.7ms/use × 2 uses = 95.4ms total
L72: $('//*[@name="Dropdown"]//XCUIElementTypeTextFie...') → $("~text_input")
⚡ 31.8ms/use × 3 uses = 95.4ms total
└─ File total: 775.4ms saved (7 selectors)
📁 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/LoginScreen.ts
L13: $('//*[@name="button-login-container"]') → $("~button-login-container")
⚡ 24.1ms/use × 3 uses = 72.4ms total
L14: $('//*[@name="button-sign-up-container"]') → $("~button-sign-up-container")
[23.6ms]
L15: $('//*[@name="button-LOGIN"]') → $("~button-LOGIN")
⚡ 56.6ms/use × 2 uses = 113.3ms total
L16: $('//*[@name="button-SIGN UP"]') → $("~button-SIGN UP")
⚡ 53.3ms/use × 2 uses = 106.6ms total
L17: $('//*[@name="input-email"]') → $("~input-email")
⚡ 55.4ms/use × 2 uses = 110.8ms total
L18: $('//*[@name="input-password"]') → $("~input-password")
⚡ 35.6ms/use × 2 uses = 71.3ms total
L19: $('//*[@name="input-repeat-password"]') → $("~input-repeat-password") [41.7ms]
L20: $('//*[@name="button-biometric"]') → $("~button-biometric")
⚡ 29.2ms/use × 4 uses = 116.7ms total
└─ File total: 656.3ms saved (8 selectors)
📁 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/components/Picker.ts
L3: $('//XCUIElementTypePickerWheel') → $("-ios predicate string:type ==
'XCUIElementTyp...")
⚡ 46.2ms/use × 6 uses = 277.4ms total
L4: $('//*[@name="done_button"]') → $("~done_button")
⚡ 48.2ms/use × 3 uses = 144.7ms total
└─ File total: 422.1ms saved (2 selectors)
📁 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/components/TabBar.ts
L15: $('//*[@name="Forms"]') → $("~Forms")
⚡ 29.5ms/use × 6 uses = 177.0ms total
L19: $('//*[@name="Swipe"]') → $("~Swipe")
⚡ 35.1ms/use × 3 uses = 105.3ms total
L23: $('//*[@name="Drag"]') → $("~Drag")
⚡ 33.2ms/use × 2 uses = 66.4ms total
└─ File total: 348.8ms saved (3 selectors)
📁 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/SwipeScreen.ts
L10: $('//*[@name="WebdriverIO logo"]') → $("~WebdriverIO logo")
⚡ 86.9ms/use × 2 uses = 173.9ms total
└─ File total: 173.9ms saved (1 selector)
📁 tests/specs/app.biometric.login.spec.ts
L64: $('//XCUIElementTypeStaticText[@name="LOGIN"]/an...') → $("~button-LOGIN") [24.0ms]
L66: $('//XCUIElementTypeStaticText[@name="LOGIN"]/pa...') → $("~button-LOGIN") [22.0ms]
L67: $('//XCUIElementTypeStaticText[@name="LOGIN"]/.....') → $("~button-LOGIN") [21.6ms]
L68: $('//XCUIElementTypeOther[@name="button-LOGIN"]/...') → $("~button-biometric")
[21.2ms]
L69: $('//XCUIElementTypeOther[@name="button-biometri...') → $("~button-LOGIN") [23.6ms]
└─ File total: 112.4ms saved (5 selectors)
📁 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/HomeScreen.ts
L5: $('//*[@name="Home-screen"]') → $("~Home-screen")
⚡ 27.4ms/use × 2 uses = 54.8ms total
└─ File total: 54.8ms saved (1 selector)
🔍 Workspace-Wide Optimizations
───────────────────────────────────────────────────────────────────────────────
Source file unknown. Search your IDE (Cmd+Shift+F) for these selectors:
$('//*[@name="Carousel"]') → $("~Carousel")
⚡ 57.0ms/use × 10 uses = 570.5ms total
$('//*[@name="OK"]') → $("~OK")
⚡ 48.5ms/use × 4 uses = 193.9ms total
$('//*[@name="__CAROUSEL_ITEM_0__"]') → $("~__CAROUSEL_ITEM_0__")
⚡ 51.1ms/use × 2 uses = 102.2ms total
$('//*[@name="Cancel"]') → $("~Cancel")
⚡ 38.9ms/use × 2 uses = 77.7ms total
$('//*[@name="__CAROUSEL_ITEM_5__"]') → $("~__CAROUSEL_ITEM_5__")
⚡ 61.3ms
$('//*[@name="__CAROUSEL_ITEM_4__"]') → $("~__CAROUSEL_ITEM_4__")
⚡ 54.9ms
$('//*[@name="__CAROUSEL_ITEM_3__"]') → $("~__CAROUSEL_ITEM_3__")
⚡ 51.8ms
$('//*[@name="__CAROUSEL_ITEM_2__"]') → $("~__CAROUSEL_ITEM_2__")
⚡ 41.4ms
$('//*[@name="__CAROUSEL_ITEM_1__"]') → $("~__CAROUSEL_ITEM_1__")
⚡ 37.2ms
$('//*[@name="Ask me later"]') → $("~Ask me later")
⚡ 29.4ms
⚠️ Performance Warnings
───────────────────────────────────────────────────────────────────────────────
Native selectors were SLOWER than XPath for these cases.
This can happen due to app-specific optimizations, element hierarchy,
caching effects, or Appium/driver version differences.
Recommendation: Keep using XPath for these selectors.
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/components/TabBar.ts:3
XPath: $('//*[@name="Home"]') → 841ms
Native: $('~Home') → 1052ms
❌ Native was 212ms slower (25%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/components/TabBar.ts:11
XPath: $('//*[@name="Login"]') → 902ms
Native: $('~Login') → 1068ms
❌ Native was 167ms slower (18%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/LoginScreen.ts:4
XPath: $('//*[@name="Login-screen"]') → 118ms
Native: $('~Login-screen') → 431ms
❌ Native was 313ms slower (265%)
📍 tests/specs/app.biometric.login.spec.ts:65
XPath: $('//XCUIElementTypeStaticText[@name="LOGIN...') → 96ms
Native: $('~button-LOGIN') → 106ms
❌ Native was 10ms slower (11%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/FormsScreen.ts:4
XPath: $('//*[@name="Forms-screen"]') → 126ms
Native: $('~Forms-screen') → 507ms
❌ Native was 381ms slower (303%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/SwipeScreen.ts:2
XPath: $('//*[@name="Swipe-screen"]') → 158ms
Native: $('~Swipe-screen') → 1134ms
❌ Native was 976ms slower (619%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts:17
XPath: $('//*[@name="drop-l1"]') → 124ms
Native: $('~drop-l1') → 582ms
❌ Native was 458ms slower (370%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts:18
XPath: $('//*[@name="drop-c1"]') → 122ms
Native: $('~drop-c1') → 491ms
❌ Native was 369ms slower (303%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts:19
XPath: $('//*[@name="drop-r1"]') → 122ms
Native: $('~drop-r1') → 452ms
❌ Native was 330ms slower (271%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts:20
XPath: $('//*[@name="drop-l2"]') → 114ms
Native: $('~drop-l2') → 420ms
❌ Native was 306ms slower (268%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts:21
XPath: $('//*[@name="drop-c2"]') → 100ms
Native: $('~drop-c2') → 371ms
❌ Native was 270ms slower (269%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts:22
XPath: $('//*[@name="drop-r2"]') → 93ms
Native: $('~drop-r2') → 334ms
❌ Native was 240ms slower (257%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts:23
XPath: $('//*[@name="drop-l3"]') → 92ms
Native: $('~drop-l3') → 279ms
❌ Native was 187ms slower (204%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts:24
XPath: $('//*[@name="drop-c3"]') → 87ms
Native: $('~drop-c3') → 237ms
❌ Native was 150ms slower (172%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/DragScreen.ts:25
XPath: $('//*[@name="drop-r3"]') → 80ms
Native: $('~drop-r3') → 198ms
❌ Native was 118ms slower (148%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/components/TabBar.ts:7
XPath: $('//*[@name="Webview"]') → 948ms
Native: $('~Webview') → 1060ms
❌ Native was 111ms slower (12%)
📍 /Users/wimselles/Git/wdio/appium-boilerplate/tests/screenobjects/WebviewScreen.ts:8
XPath: $('*//XCUIElementTypeWebView') → 102ms
Native: $('-ios predicate string:type == 'XCUIEleme...') → 153ms
❌ Native was 51ms slower (50%)
💡 Why Change?
───────────────────────────────────────────────────────────────────────────────
• Speed: Native selectors bypass expensive XML tree traversal
• Stability: Less affected by UI hierarchy changes
• Priority: ~accessibilityId > -ios predicate string > -ios class chain > //xpath
• Docs: https://webdriver.io/docs/selectors#mobile-selectors
═══════════════════════════════════════════════════════════════════════════════
───────────────────────────────────────────────────────────────────────────────
📝 Mobile Selector Performance Optimizer - Markdown Report
───────────────────────────────────────────────────────────────────────────────
📁 Markdown report written to: /Users/wimselles/Git/wdio/appium-boilerplate/logs/mobile-selector-performance-optimizer-report-iphone_16_pro-1769237599894.md
───────────────────────────────────────────────────────────────────────────────
Understanding Performance Results
While native selectors (like accessibility IDs) are generally faster than XPath, you may occasionally see cases in your report where the suggested selector performed slower during testing. This is normal and can happen for a few reasons:
-
Timing variations: Mobile devices aren't perfectly consistent. Background processes, screen animations, or momentary CPU load can affect individual measurements. A selector tested during a busy moment may appear slower than one tested when the device was idle.
-
App-specific behavior: Some apps have unique UI structures where certain selector strategies work better than others. The "typical" performance ranking doesn't apply universally to every element in every app.
-
Measurement represents a single snapshot: Each selector is tested once during your test run. This captures real performance but includes normal variation. A selector showing 10-15% slower results may actually perform the same or better on average.
What to do with "slower" results: The report flags these cases in the "Performance Warnings" section so you can make informed decisions. For selectors showing significantly slower native performance (50%+), it's reasonable to keep using XPath. For borderline cases, the difference is likely negligible in practice, and native selectors are typically more stable and less brittle than XPath since they don't depend on the exact UI hierarchy, which can change between app versions.
The optimizer helps you find clear wins, and most selectors will show genuine improvements. The warnings simply ensure you have complete information rather than blindly replacing every selector.
Platform Support
- ✅ iOS: Fully supported and optimized
- ⚠️ Android: Currently disabled (support coming in a future release)
- ⚠️ MultiRemote: Not supported yet (feature is automatically disabled for MultiRemote sessions)
When running on Android or with MultiRemote, the service will log a warning message indicating it's disabled and skip optimization.
For more information on WebdriverIO see the homepage.