URL: https://testingbot.com/
# Cross Browser Testing on 6100+ Real Browsers & Devices
Run automated, manual, visual and AI-assisted tests on 6100+ real browsers and physical iOS and Android devices, with parallel execution and CI/CD integrations.
Trusted by some of the world's most innovative companies
[Start free](https://testingbot.com/users/sign_up)[Request a demo](https://testingbot.com/demo)
No credit card required
1+bn
Tests Executed
180k
Active Users
6100+
Browsers & Devices
testingbot.com/members
Live
Running
14
Queued
47
Avg queue
1.2s
Today
8,142
Test sessionslast 60s
PassRunQueue
Parallel workers62/100 active
EU region
3.7×
Faster than self-hosted
grids and dev machines
Boost your tests
About TestingBot
## All-in-one cross-browser, mobile and AI testing platform
TestingBot runs your Selenium, Appium, Playwright, Cypress, Puppeteer, Espresso, XCUITest and Maestro tests on 6100+ real browsers and physical iOS and Android devices.
Automated, manual, visual and AI-assisted testing with parallel execution and CI/CD integrations.
### Web Testing
Test on 6100+ real browsers across Windows, macOS and Linux.
[
#### Live / Manual
Interactive browser testing
](https://testingbot.com/features/manual-browser-testing)[
#### Automated
Selenium, Playwright, Puppeteer & Cypress
](https://testingbot.com/features)[
#### AI Testing
AI-driven testing & MCP integration
](https://testingbot.com/features/ai-testing)[
#### Visual Testing
Screenshot comparison of websites
](https://testingbot.com/features/screenshot-testing)
### Mobile App Testing
Test on real physical iOS and Android devices.
[
#### Live / Manual
Interactive device testing
](https://testingbot.com/features/manual-mobile-testing)[
#### Automated
Appium, Espresso, XCUITest & Maestro
](https://testingbot.com/real-device-testing)[
#### AI Testing
AI-driven testing on real devices
](https://testingbot.com/features/ai-testing)[
#### Visual Testing
App screenshot comparison
](https://testingbot.com/features/screenshot-testing)

## Cross-Browser Testing Made Simple
Get one-click access to real browsers running on macOS, Windows and Linux platforms. Switch platforms, locations and screen resolutions instantly.
Test across all versions of Internet Explorer (IE8 to IE11), Edge, Chrome, Safari, Firefox and Opera.
Chrome
Safari
Firefox
Edge
[Start Testing](https://testingbot.com/features/manual-browser-testing)
## Why teams choose TestingBot
### Improve test coverage
Cross-browser, real device and visual testing on 6100+ browser and device combinations.
### Boost productivity with AI
AI-powered test creation and execution. Write tests in natural language, let AI do the rest.
### Accelerate testing cycles
Run tests in parallel across multiple browsers and devices. Ship faster with confidence.
## Works with your favorite testing frameworks
Native support for all major web and mobile testing frameworks. Get started in minutes.
### Web Testing
[ Selenium ](https://testingbot.com/features/automation/selenium)[ Playwright ](https://testingbot.com/features/automation/playwright)[ Cypress ](https://testingbot.com/features/automation/cypress)[ Puppeteer ](https://testingbot.com/features/automation/puppeteer)[WebdriverIO](https://testingbot.com/support/web-automate/selenium/nodejs/webdriverio)[ RobotFramework ](https://testingbot.com/support/web-automate/selenium/robotframework)
### Mobile Testing
[ Appium ](https://testingbot.com/features/automation/appium)[ Espresso ](https://testingbot.com/features/automation/espresso)[ XCUITest ](https://testingbot.com/features/automation/xcuitest)[ Maestro ](https://testingbot.com/features/automation/maestro)[Flutter](https://testingbot.com/support/app-automate/appium/flutter)[React Native](https://testingbot.com/features/automation/appium)
### Languages & SDKs
[Java](https://testingbot.com/support/web-automate/selenium/java)[Python](https://testingbot.com/support/web-automate/selenium/python)[NodeJS](https://testingbot.com/support/web-automate/selenium/nodejs)[C# / .NET](https://testingbot.com/support/web-automate/selenium/csharp)[Ruby](https://testingbot.com/support/web-automate/selenium/ruby)[php PHP](https://testingbot.com/support/web-automate/selenium/php)
[View all documentation](https://testingbot.com/support)
Real Device Cloud
## Test on real iOS & Android devices
Access hundreds of real physical devices for manual and automated testing. iPhones, iPads, Samsung Galaxy, Pixel, Smart TVs and more.
[

Appium ](https://testingbot.com/features/automation/appium)[

Espresso ](https://testingbot.com/features/automation/espresso)[

XCUITest ](https://testingbot.com/features/automation/xcuitest)[

Maestro ](https://testingbot.com/features/automation/maestro)
[Explore device cloud](https://testingbot.com/real-device-testing)

AI-Powered Testing
## AI Test Agent
Create and run tests without writing code. Simply describe what you want to test in plain language.
TestingBot AI understands your application and generates reliable test steps automatically.
[Try AI Testing](https://testingbot.com/features/ai-testing)


Accessibility Testing
## WCAG Compliance Testing
Identify and fix accessibility issues across your websites. Ensure WCAG compliance and deliver an inclusive experience for all users.
Meet legal requirements like the European Accessibility Act (EAA), reach a broader audience and improve SEO with accessibility best practices.
WCAG 2.1 Compliance
Scheduled Testing
Detailed Reports
Severity Classification
[Explore Accessibility Testing](https://testingbot.com/features/accessibility-testing)
## Trusted by teams worldwide
Join thousands of companies shipping better software with TestingBot
1B+
Tests Executed
180,000+
Active Users
6,100+
Browsers & Devices
106
Countries
GDPR
Compliant
SAML SSO
Single Sign-On
Star Level 1
Certified
99.99%
Uptime SLA
## Seamlessly integrate into your workflow
Connect TestingBot with your CI/CD pipeline, issue trackers and communication tools.
[ Jenkins ](https://testingbot.com/support/integrations/ci-cd/jenkins)[ GitHub ](https://testingbot.com/support/integrations/ci-cd/github-actions)[ GitLab ](https://testingbot.com/support/integrations/ci-cd/gitlab)[ CircleCI ](https://testingbot.com/support/integrations/ci-cd/circleci)[ Travis CI ](https://testingbot.com/support/integrations/ci-cd/travis-ci)[ Slack ](https://testingbot.com/support/integrations/slack)[ Jira ](https://testingbot.com/support/integrations/jira)[Azure](https://testingbot.com/support/integrations/ci-cd/azure)[ Bitbucket ](https://testingbot.com/support/integrations/ci-cd/bitbucket)[ TeamCity ](https://testingbot.com/support/integrations/ci-cd/teamcity)[ Bamboo ](hhttps://testingbot.com/support/integrations/ci-cd/bamboo)[ App Center ](https://testingbot.com/support/integrations/appcenter)
[View all integrations](https://testingbot.com/integrations)
FAQ
## Frequently Asked Questions
What is cross-browser testing?
Cross-browser testing verifies that a website or web application works correctly across different browsers (Chrome, Safari, Firefox, Edge, Internet Explorer), browser versions, operating systems and screen sizes. It catches rendering inconsistencies, JavaScript compatibility issues and CSS bugs before users encounter them. TestingBot runs your tests on 6100+ real browser and OS combinations in parallel.
How do I test a website on multiple browsers?
Use a cross-browser testing platform like TestingBot. Sign up, point your Selenium, Playwright, Cypress or Puppeteer test scripts at the TestingBot grid by changing the WebDriver URL, and your tests run in parallel across thousands of real browsers and devices. You can also start live, interactive sessions on any browser without writing code.
Which test automation frameworks does TestingBot support?
TestingBot natively supports Selenium, Appium, Playwright, Cypress, Puppeteer, Espresso, XCUITest and Maestro. SDKs are available for Java, JavaScript, TypeScript, Python, Ruby, C#, PHP and Go. AI assistants like Claude Code and Cursor can also access the TestingBot REST API via a [Model Context Protocol (MCP) server](https://testingbot.com/support/ai/mcp) to inspect test runs and debug failures from your editor.
Does TestingBot support real iOS and Android devices?
Yes. TestingBot operates a cloud of physical iOS and Android devices, including iPhones (from iPhone 6s to iPhone 17 Pro), iPads, Samsung Galaxy phones, Pixel devices and tablets. You can run automated Appium, Espresso, XCUITest or Maestro tests on real hardware, or take manual control via the [live testing interface](https://testingbot.com/features/manual-mobile-testing).
How is TestingBot different from BrowserStack, Sauce Labs and LambdaTest?
TestingBot is more affordable than BrowserStack and Sauce Labs (typically 30 to 40 percent cheaper), includes real device testing in every paid plan, hosts data in the European Union for GDPR compliance, and ships features faster, Apple Silicon support arrived more than ten months before Sauce Labs. See the dedicated comparisons: [BrowserStack alternative](https://testingbot.com/browserstack-alternative), [Sauce Labs alternative](https://testingbot.com/saucelabs-alternative), [LambdaTest alternative](https://testingbot.com/lambdatest-alternative).
Does TestingBot support AI-driven testing?
Yes. TestingBot offers [codeless AI testing](https://testingbot.com/support/ai/codeless), describe in plain language what you want to test, and an AI agent creates and runs the test on the browsers you choose, reporting back any failures. TestingBot also exposes its REST API via a [Model Context Protocol (MCP) server](https://testingbot.com/support/ai/mcp) so AI assistants like Claude Code and Cursor can inspect test runs, fetch logs and debug failures from your editor. See the full [AI testing overview](https://testingbot.com/support/ai) for integrations with Magnitude, Stagehand and Notte.
Where is TestingBot data hosted?
TestingBot is built and hosted in the European Union. All test data, video recordings and logs are stored on EU-based infrastructure, making TestingBot a strong choice for GDPR-compliant testing programs and for teams with European data-residency requirements.
Is there a free trial?
Yes. TestingBot offers a free plan and a free trial of paid features. No credit card is required to start. During the trial you can run automated tests, live manual sessions, screenshot tests and visual comparisons on browsers and real devices.
No credit card required
## Ready to get started?
Join thousands of teams shipping better software with TestingBot
[Start Free Trial](https://testingbot.com/users/sign_up)[View pricing](https://testingbot.com/pricing)
Free 14-day trial
5-minute setup
24/7 support
---
URL: https://testingbot.com/features
# All-in-One Cross Browser & Mobile Testing Platform
TestingBot is a cloud testing platform that runs your Selenium, Appium, Playwright, Cypress, Puppeteer, Espresso, XCUITest and Maestro tests on 6100+ real desktop browsers and physical iOS and Android devices. Test in parallel, debug with video and screenshot recording and integrate with any CI/CD system.
[Start free](https://testingbot.com/users/sign_up)[Request a demo](https://testingbot.com/demo)
Browsers & devices6100+
Uptime SLA99.99%
testingbot.com/members
Live
14Running
8+Frameworks
6100+Browsers
Chrome 146
macOS
Safari 26
macOS
Firefox 132
Win 11
Edge 146
Win 11
Chrome dev
Linux
Safari TP
Sequoia
iPhone 17
iOS 18
iPad Pro
iPadOS
Galaxy S26
Android
Pixel 10
Android
Galaxy S25
Android
IE 11
Win 10
Test framework launchers EU region

Selenium

Appium

Cypress

Puppeteer

Playwright

Espresso

XCUITest

Maestro
Tests in parallel
## Trusted by some of the world's most innovative companies
## All-in-one cross browser testing platform
The TestingBot cloud brings you the power to test your website and mobile app on [+6100 browsers & devices](https://testingbot.com/support/web-automate/browsers).
Test on the latest Chrome, Firefox, Edge and Safari, plus legacy versions like Internet Explorer 11, all running on real Windows, macOS and Linux machines.
Let us handle your testing infrastructure while you focus on development.
Run tests effortlessly on our maintained cloud of desktop and mobile platforms.
[Start free](https://testingbot.com/users/sign_up)
Live cloud grid
uptime 99.99% · ready


















6100+combinations
Built for scale
## 6100+ real desktop and mobile browser combinations
Test on the widest range of desktop and mobile devices in our real device cloud. We handle setup, procurement and maintenance, so you can focus entirely on testing.
### Windows, macOS & Linux
One-click access to every macOS, Linux and Windows version. From Yosemite to macOS Tahoe, and Windows 7 to Windows 11. We've got you covered.
### Every browser, all versions
Edge, Safari, Firefox, Chrome, Opera and IE, constantly updated and instantly available.
### Security
Single-Use VMs and real devices, reset after each use.
### Real Android and iOS devices
Test across real iPhone, iPads, Google, OnePlus, Xiaomi and Samsung phones and tabs.
### No setup and maintenance
Instantly access 6100+ devices and browsers. Experience zero setup or maintenance hassles, and focus only on testing.
### Performance
Our infrastructure is built for performance to eliminate test flakiness and latency.
Automation frameworks
## Test Automation Frameworks
TestingBot supports these popular Test Automation Frameworks:
-
[](https://testingbot.com/features/automation/selenium)
### [Selenium](https://testingbot.com/features/automation/selenium)
Popular Browser Automation Framework, using WebDriver, which supports all major browsers.
Best for: enterprise-grade browser automation across every major language.
[Try with Selenium](https://testingbot.com/features/automation/selenium)
-
[](https://testingbot.com/features/automation/appium)
### [Appium](https://testingbot.com/features/automation/appium)
Mobile Automation Framework, using WebDriver, runs on physical devices and simulators/emulators.
Best for: native mobile app automation on real iOS & Android devices.
[Try with Appium](https://testingbot.com/features/automation/appium)
-
[](https://testingbot.com/features/automation/cypress)
### [Cypress](https://testingbot.com/features/automation/cypress)
Javascript based E2E testing on Chrome and Firefox.
Best for: fast JavaScript-only end-to-end testing.
[Try with Cypress](https://testingbot.com/features/automation/cypress)
-
[](https://testingbot.com/features/automation/puppeteer)
### [Puppeteer](https://testingbot.com/features/automation/puppeteer)
Chrome DevTools based Automation Framework by Google. Supports Chrome and Edge browsers.
Best for: Chromium DevTools-based automation and scraping.
[Try with Puppeteer](https://testingbot.com/features/automation/puppeteer)
-
[](https://testingbot.com/features/automation/playwright)
### [Playwright](https://testingbot.com/features/automation/playwright)
Similar to Puppeteer, built by Microsoft, with support for Chrome and Edge.
Best for: modern multi-browser testing in TypeScript, Python and .NET.
[Try with Playwright](https://testingbot.com/features/automation/playwright)
-
[](https://testingbot.com/features/automation/espresso)
### [Espresso](https://testingbot.com/features/automation/espresso)
Android Automation Framework, runs on physical devices and emulators.
Best for: Android-only UI testing inside the app process.
[Try with Espresso](https://testingbot.com/features/automation/espresso)
-
[](https://testingbot.com/features/automation/xcuitest)
### [XCUITest](https://testingbot.com/features/automation/xcuitest)
iOS Automation Framework, runs on physical devices and simulators.
Best for: iOS-only UI testing with Apple's native framework.
[Try with XCUITest](https://testingbot.com/features/automation/xcuitest)
-
[](https://testingbot.com/features/automation/maestro)
### [Maestro](https://testingbot.com/features/automation/maestro)
Maestro is a mobile UI testing framework, which supports testing on both iOS and Android.
Best for: declarative mobile flows without writing code.
[Try with Maestro](https://testingbot.com/features/automation/maestro)
Comparing frameworks? See [Playwright vs Selenium](https://testingbot.com/playwright-vs-selenium) for a head-to-head breakdown.
Buy vs build
## TestingBot vs a DIY in-house grid
Building and maintaining your own browser and device lab is expensive and slow. Here is how a hosted cloud compares to running the infrastructure yourself.
| Capability | TestingBot | DIY in-house grid |
| --- | --- | --- |
| Browser & device coverage |
6100+ ready to use
|
You buy and maintain
|
| Setup time |
Minutes
|
Weeks to months
|
| Maintenance burden |
None, managed for you
|
Constant patching, OS upgrades and capacity planning
|
| Real iOS devices |
iPhone, iPad, real hardware
|
Hard (Apple licensing & MDM)
|
| Parallel sessions |
Scale to hundreds in parallel
|
Capped by your hardware
|
| EU data residency |
Hosted in the EU
|
Build it yourself
|
| Video & screenshot recording |
Recorded automatically on every test
|
Build it yourself
|
| CI/CD integrations |
Jenkins, GitHub Actions, CircleCI, GitLab, Azure DevOps
|
DIY scripts and plugins
|
| Live debugging |
Built-in
|
Custom VNC tooling
|
| Support |
24/7 included
|
Internal team only
|
Live mobile testing
## Take control of a device
Interact with the remote mobile device, just as you would with a device in hand. Tap, scroll, zoom, swipe and more.
Open a real device anywhere in the world and drive it from your browser. Tap, scroll, swipe, install your APK or IPA, capture network logs and reproduce a customer issue in real time.
[Try Live Mobile Testing](https://testingbot.com/features/manual-mobile-testing)
Live device · iPhone 17 Pro · iOS 18.2
connected
Builds5
checkout-2.4.1.ipa
24.3 MB · just now
checkout-2.4.0.ipa
23.8 MB · 2 days
payment-1.2.0.ipa
18.1 MB · 5 days
profile-3.0.0.apk
32.5 MB · 1 week
admin-0.9.0.ipa
14.7 MB · 2 weeks
drop .ipa / .apk
Install on device
9:41
checkout-2.4.1
codesign · verify · launch
Checkout
v2.4.1 · staging
order #4291
Pay
€12.40
Cancel
Item added
Headphones · Pro
Promo applied
WELCOME10 · -€2
Address
Brussels, BE
Console·checkout-2.4.1
allinfowarnerr
18:45:01.123ℹAppDelegatedidFinishLaunchingWithOptions
18:45:01.187ℹLifecyclewillEnterForeground
18:45:01.234→URLSessionGET /v1/cart → 200 (123ms)
18:45:01.305→URLSessionGET /v1/promo → 200 (88ms)
18:45:01.412ℹCartloaded 3 items, total €12.40
18:45:01.498ℹFeatureFlagscheckout.v2 = on
18:45:01.567✎TextFieldsearch.text = "order #4291"
18:45:01.612→URLSessionPOST /v1/search → 200 (74ms)
18:45:01.701⚠ImageCacheevicted 12 entries (32.4 MB)
18:45:01.789✓AuthServicetoken refreshed (exp 14:00)
18:45:01.834👆UItap PayButton (40,520)
18:45:01.892→URLSessionPOST /v1/checkout → 201 (212ms)
18:45:01.945✓Paymentauth ok · stripe\_id=ch\_3Q9…
18:45:02.012ℹAnalyticslogEvent("checkout\_done")
18:45:02.078→URLSessionGET /v1/orders → 200 (96ms)
18:45:02.134ℹRoutingpush OrderConfirmationVC
18:45:02.201✓Memorystable · 124 MB
18:45:01.123ℹAppDelegatedidFinishLaunchingWithOptions
18:45:01.187ℹLifecyclewillEnterForeground
18:45:01.234→URLSessionGET /v1/cart → 200 (123ms)
18:45:01.305→URLSessionGET /v1/promo → 200 (88ms)
18:45:01.412ℹCartloaded 3 items, total €12.40
18:45:01.498ℹFeatureFlagscheckout.v2 = on
18:45:01.567✎TextFieldsearch.text = "order #4291"
18:45:01.612→URLSessionPOST /v1/search → 200 (74ms)
18:45:01.701⚠ImageCacheevicted 12 entries (32.4 MB)
18:45:01.789✓AuthServicetoken refreshed (exp 14:00)
18:45:01.834👆UItap PayButton (40,520)
18:45:01.892→URLSessionPOST /v1/checkout → 201 (212ms)
18:45:01.945✓Paymentauth ok · stripe\_id=ch\_3Q9…
18:45:02.012ℹAnalyticslogEvent("checkout\_done")
18:45:02.078→URLSessionGET /v1/orders → 200 (96ms)
18:45:02.134ℹRoutingpush OrderConfirmationVC
18:45:02.201✓Memorystable · 124 MB
18:45:01.123ℹAppDelegatedidFinishLaunchingWithOptions
18:45:01.187ℹLifecyclewillEnterForeground
18:45:01.234→URLSessionGET /v1/cart → 200 (123ms)
18:45:01.305→URLSessionGET /v1/promo → 200 (88ms)
18:45:01.412ℹCartloaded 3 items, total €12.40
18:45:01.498ℹFeatureFlagscheckout.v2 = on
18:45:01.567✎TextFieldsearch.text = "order #4291"
18:45:01.612→URLSessionPOST /v1/search → 200 (74ms)
18:45:01.701⚠ImageCacheevicted 12 entries (32.4 MB)
18:45:01.789✓AuthServicetoken refreshed (exp 14:00)
18:45:01.834👆UItap PayButton (40,520)
18:45:01.892→URLSessionPOST /v1/checkout → 201 (212ms)
18:45:01.945✓Paymentauth ok · stripe\_id=ch\_3Q9…
18:45:02.012ℹAnalyticslogEvent("checkout\_done")
18:45:02.078→URLSessionGET /v1/orders → 200 (96ms)
18:45:02.134ℹRoutingpush OrderConfirmationVC
18:45:02.201✓Memorystable · 124 MB
18:45:01.123ℹAppDelegatedidFinishLaunchingWithOptions
18:45:01.187ℹLifecyclewillEnterForeground
18:45:01.234→URLSessionGET /v1/cart → 200 (123ms)
18:45:01.305→URLSessionGET /v1/promo → 200 (88ms)
18:45:01.412ℹCartloaded 3 items, total €12.40
18:45:01.498ℹFeatureFlagscheckout.v2 = on
18:45:01.567✎TextFieldsearch.text = "order #4291"
18:45:01.612→URLSessionPOST /v1/search → 200 (74ms)
18:45:01.701⚠ImageCacheevicted 12 entries (32.4 MB)
18:45:01.789✓AuthServicetoken refreshed (exp 14:00)
18:45:01.834👆UItap PayButton (40,520)
18:45:01.892→URLSessionPOST /v1/checkout → 201 (212ms)
18:45:01.945✓Paymentauth ok · stripe\_id=ch\_3Q9…
18:45:02.012ℹAnalyticslogEvent("checkout\_done")
18:45:02.078→URLSessionGET /v1/orders → 200 (96ms)
18:45:02.134ℹRoutingpush OrderConfirmationVC
18:45:02.201✓Memorystable · 124 MB
● streaming 17 lines/s · syslog
Developer experience
## Test in your favorite language
Use the SDK of your choice. TestingBot integrates with Jest, Mocha, PyTest, NUnit, RSpec and other popular test frameworks, plus every major CI/CD system.
hub.testingbot.com Copy
JavaScript Python Java Ruby C# PHP
// Run a Selenium test on the TestingBot gridconstwd=require('selenium-webdriver');constdriver=awaitnewwd.Builder().usingServer('https://hub.testingbot.com/wd/hub').withCapabilities({'tb:options': {name:'Checkout flow'},browserName:'chrome',browserVersion:'latest',platformName:'Windows 11'}).build();awaitdriver.get('https://example.com');awaitdriver.quit();
# Run a Selenium test on the TestingBot gridfromseleniumimportwebdriveroptions=webdriver.ChromeOptions()options.set\_capability('tb:options', {'name':'Checkout flow'})options.set\_capability('browserVersion','latest')options.set\_capability('platformName','Windows 11')driver=webdriver.Remote(command\_executor='https://hub.testingbot.com/wd/hub',options=options)driver.get('https://example.com')driver.quit()
// Run a Selenium test on the TestingBot gridDesiredCapabilitiescaps=newDesiredCapabilities();caps.setCapability("browserName","chrome");caps.setCapability("browserVersion","latest");caps.setCapability("platformName","Windows 11");WebDriverdriver=newRemoteWebDriver(newURL("https://hub.testingbot.com/wd/hub"),caps);driver.get("https://example.com");driver.quit();
# Run a Selenium test on the TestingBot gridrequire'selenium-webdriver'caps=Selenium::WebDriver::Remote::Capabilities.chrome(browserVersion:'latest',platformName:'Windows 11','tb:options'=\>{name:'Checkout flow'})driver=Selenium::WebDriver.for(:remote,url:'https://hub.testingbot.com/wd/hub',capabilities:caps)driver.get'https://example.com'driver.quit
// Run a Selenium test on the TestingBot gridvaroptions=newChromeOptions();options.AddAdditionalOption("browserVersion","latest");options.AddAdditionalOption("platformName","Windows 11");IWebDriverdriver=newRemoteWebDriver(newUri("https://hub.testingbot.com/wd/hub"),options);driver.Navigate().GoToUrl("https://example.com");driver.Quit();
// Run a Selenium test on the TestingBot grid$capabilities=DesiredCapabilities::chrome();$capabilities-\>setCapability('browserVersion','latest');$capabilities-\>setCapability('platformName','Windows 11');$driver=RemoteWebDriver::create('https://hub.testingbot.com/wd/hub',$capabilities);$driver-\>get('https://example.com');$driver-\>quit();
Connected· [View documentation](https://testingbot.com/support/web-automate/selenium/nodejs) [View documentation](https://testingbot.com/support/web-automate/selenium/python) [View documentation](https://testingbot.com/support/web-automate/selenium/java) [View documentation](https://testingbot.com/support/web-automate/selenium/ruby) [View documentation](https://testingbot.com/support/web-automate/selenium/csharp) [View documentation](https://testingbot.com/support/web-automate/selenium/php)

## Test on real mobile and desktop devices
TestingBot only runs real browsers on real Windows, macOS and Linux machines, never simulators. Tests on TestingBot match what your customers actually see, so your results are always accurate.
Test on the latest Chrome, Firefox, Edge and Safari, plus legacy versions like Internet Explorer 11, all running on real Windows, macOS and Linux machines.
Test across all versions of Internet Explorer (IE8 to IE11), Edge, Chrome, Safari, Firefox and Opera.
[Start free](https://testingbot.com/users/sign_up)
Secure tunnel
## Test Staged Websites
Use the secure TestingBot Tunnel to easily test your staging and development environments across all browsers, ensuring seamless access behind firewalls or private networks. Test data and websites from behind your firewall on the TestingBot cloud.

[Read more](https://testingbot.com/support/tunnel)
FAQ
## Frequently asked questions about TestingBot features
What testing frameworks does TestingBot support?
TestingBot natively supports **Selenium, Appium, Playwright, Cypress, Puppeteer, Espresso, XCUITest and Maestro**. SDKs ship for Java, JavaScript, TypeScript, Python, Ruby, C#, PHP and Go. AI assistants like Claude Code and Cursor can also drive TestingBot through a [Model Context Protocol (MCP) server](https://testingbot.com/support/ai/mcp).
Can I run automated tests in parallel?
Yes. Run automated Selenium, Appium, Playwright, Cypress, Puppeteer, Espresso, XCUITest and Maestro tests in parallel, from a handful of concurrent sessions on the free plan up to hundreds of parallel sessions on Enterprise. Parallel execution typically reduces total test runtime by 10× to 50× compared to a single-machine grid.
Does TestingBot support real iOS and Android devices?
Yes. TestingBot operates a cloud of physical iOS and Android devices, including iPhones (from iPhone 6s to the latest iPhone), iPads, Samsung Galaxy phones, Pixel devices and tablets. You can run automated Appium, Espresso, XCUITest or Maestro tests on real hardware, or take manual control via the [live testing interface](https://testingbot.com/features/manual-mobile-testing).
How do I test internal or staging applications with TestingBot?
Use the [TestingBot Tunnel](https://testingbot.com/support/tunnel) to expose a staging or internal application to the TestingBot cloud over a secure connection. The tunnel is encrypted end-to-end, requires no firewall changes, and lets every browser and device in the cloud reach hostnames behind your VPN.
What CI/CD integrations does TestingBot offer?
TestingBot ships first-party plugins and integrations for Jenkins, GitHub Actions, GitLab CI, CircleCI, Azure DevOps, Bamboo, TeamCity and Travis CI. The platform also exposes a REST API and webhook system, so any CI/CD tool can launch tests, fetch results and react to failures.
Can I do manual or live browser testing on TestingBot?
Yes. [Live browser testing](https://testingbot.com/features/manual-browser-testing) and [live mobile testing](https://testingbot.com/features/manual-mobile-testing) let you remote-control any real browser, device, or simulator from inside your own browser: type, click, scroll, pinch and inspect. There is no SDK to install, and sessions launch in seconds.
Is TestingBot GDPR-compliant and secure?
TestingBot is built and hosted in the European Union, GDPR-compliant by design and CSA STAR Level 1 registered. The infrastructure is single-tenant per test session: virtual machines and devices are wiped after every test.
### Sign up for a Free Trial
Start testing your apps with TestingBot.
[Start a free trial](https://testingbot.com/users/sign_up)
---
URL: https://testingbot.com/real-device-testing
Real Device Cloud · iOS & Android
# Real Device Testing on iOS & Android
Real device testing means running your automated and manual tests on physical phones and tablets, not emulators. TestingBot provides a cloud of real iOS and Android devices, hosted in a secure EU datacenter, ready to run your tests in seconds.
- Real iOS & Android devices
- EU-hosted, GDPR-compliant
- [Appium](https://testingbot.com/features/automation/appium), [XCUITest](https://testingbot.com/features/automation/xcuitest), [Espresso](https://testingbot.com/features/automation/espresso), [Maestro](https://testingbot.com/features/automation/maestro)
- Pristine, never jailbroken
[Start free](https://testingbot.com/users/sign_up)[Request a demo](https://testingbot.com/demo)
9:41
RUNNING
it('logs in') { await tap(...); expect(user) ✓ login OK// session setit('opens cart') { scroll('list'); ✓ cart visibleit('checkout') { type('4242'); ✓ pay OK
10:24
APPIUM
9:42
XCUITEST
42ms
81ms
17ms
…
11:08
ESPRESSO
9:43
MAESTRO
login.yaml8
browse.yaml5
add\_to\_cart.yaml7
checkout.yaml12
profile.yaml4
logout.yaml3
14:02
UPLOAD APK
1
2
3
Trusted by some of the world's most innovative companies
Built for scale
## Reliable Device Farm
Get instant access to a fast, reliable device infrastructure.
### 24/7 device availability
Physical iOS and Android devices, available at any time of the day.
### Zero setup & Maintenance
Focus on testing instead of worrying about setup and maintenance of a mobile device farm.
### Security
Devices located in a datacenter in Europe. Each test runs on a secure and pristine device.
### Real Android and iOS devices
Test across real iPhone, iPads, Google, OnePlus, Xiaomi and Samsung phones & tabs.
### Variety of devices
Test on any device — flagship or budget — with support for the newest and legacy iOS and Android versions.
### Performance
Our infrastructure is built for performance to eliminate test flakiness and latency.
Automation frameworks
## Test Automation Frameworks
Works instantly with the most popular mobile automation frameworks:
-
[](https://testingbot.com/features/automation/appium)
### [Appium](https://testingbot.com/features/automation/appium)
Mobile Automation Framework, using WebDriver, runs on physical devices and simulators/emulators.
[Try with Appium](https://testingbot.com/features/automation/appium)
-
[](https://testingbot.com/features/automation/espresso)
### [Espresso](https://testingbot.com/features/automation/espresso)
Android Automation Framework, runs on physical devices and emulators.
[Try with Espresso](https://testingbot.com/features/automation/espresso)
-
[](https://testingbot.com/features/automation/xcuitest)
### [XCUITest](https://testingbot.com/features/automation/xcuitest)
iOS Automation Framework, runs on physical devices and simulators.
[Try with XCUITest](https://testingbot.com/features/automation/xcuitest)
-
[](https://testingbot.com/features/automation/maestro)
### [Maestro](https://testingbot.com/features/automation/maestro)
Maestro is a mobile UI testing framework, which supports testing on both iOS and Android.
[Try with Maestro](https://testingbot.com/features/automation/maestro)
Speed up your suite
## Cut suite time from hours to minutes
Drastically shorten your total app test duration by running tests simultaneously. TestingBot provides a variety of devices, ready to run your tests in parallel.
[Start free](https://testingbot.com/users/sign_up)


Debug faster
## Video, screenshots & logs
Every mobile test comes with access to generated logs, videos and screenshots.
[Start free](https://testingbot.com/users/sign_up)
Upload & test in seconds
## TestingBot Storage
Upload your native mobile app, as an .apk or .ipa, to [TestingBot Storage](https://testingbot.com/support/api#upload) and start testing immediately.
[Start free](https://testingbot.com/users/sign_up)

How we compare
## Real Device Testing: Quick Comparison
How TestingBot's real device cloud compares to other major providers.
| Capability | TestingBot | BrowserStack | SauceLabs |
| --- | --- | --- | --- |
| Real iOS & Android devices |
|
|
|
| EU-hosted devices (data residency) |
Yes
|
US primary
|
US primary
|
| GDPR + CSA STAR certified |
|
|
|
| Appium / XCUITest / Espresso / Maestro |
|
|
No Maestro
|
| Free for open source |
Yes
|
|
|
| Dedicated private device cloud |
|
|
|
| Secure tunnel for private/staging apps |
|
|
|
| Transparent flat-rate pricing |
Yes
|
Custom quote
|
Custom quote
|
See more on the [SauceLabs alternative](https://testingbot.com/saucelabs-alternative), [BrowserStack alternative](https://testingbot.com/browserstack-alternative) and [LambdaTest alternative](https://testingbot.com/lambdatest-alternative) pages.
Real-world conditions
## Test native device features
Access and modify device features to test your app in various real-world conditions.
### Network Simulation
Test for scenarios like connectivity, fluctuating network & many more.
### Localization testing
Test your apps across different timezones, languages and geolocations.
### Dark Mode
Test your app's UI with a dark background to ensure a smooth user experience.
### App Orientation
Test your app in the landscape or portrait mode. Switch between orientations to test your app's response.
### Audio
Record and hear in realtime the audio generated on the device.
### Settings
Control various settings on the device.
Private device cloud
## Your very own Mobile Devices
Choose your own iOS and Android devices. We will purchase these devices, install and configure them in our secure datacenter.
These devices will be only available to you, 24x7.
Private devices can be customised to your needs. Have us install specific profiles and apps, or change specific settings of the device. You decide.
[Learn more](https://testingbot.com/enterprise/private-device-cloud)

Secure Tunnel
## Test Staged Websites
Use the secure TestingBot Tunnel to easily test your staging and development environments across all browsers, ensuring seamless access behind firewalls or private networks.
Test data and websites from behind your firewall on the TestingBot cloud.

[Read more](https://testingbot.com/support/tunnel)
FAQ
## Frequently Asked Questions
Common questions about real device testing on TestingBot.
What is real device testing?
Real device testing is the practice of running automated or manual tests on physical smartphones and tablets, as opposed to emulators or simulators. It surfaces hardware-specific issues like CPU/RAM constraints, battery behaviour, real network conditions and device-specific rendering quirks that emulators or simulators cannot reproduce.
Should I use a real device or an emulator?
Use emulators for fast feedback during early development and unit-style flows. Use real devices for release validation, performance testing, biometric flows, camera/sensor logic, push notifications and final acceptance testing. Most teams run both: emulators in CI on every commit, real devices on the release branch.
Where are TestingBot's real devices located?
All TestingBot devices are hosted in a secure datacenter in Belgium, Europe. Every device is wiped and re-imaged after each test session. None are jailbroken or rooted.
Which test frameworks does the real device cloud support?
Appium (iOS and Android), XCUITest (iOS), Espresso (Android), Maestro (iOS and Android) and any WebDriver-compatible framework. You can also drive devices manually from your browser, or use TestingBot's codeless AI test creator to generate tests from plain English.
Can I test apps that are not yet on the App Store or Google Play?
Yes. Upload an .ipa or .apk build to TestingBot Storage and install it on any real device. You can also test apps already published to the App Store or Google Play, or connect to staging environments behind a corporate firewall using TestingBot Tunnel.
What's the difference between the public and private device cloud?
Public-cloud devices are shared across customers and are wiped between sessions. Private-cloud devices are dedicated phones we purchase, configure and host exclusively for your account, with optional persistence between tests, custom profiles and pre-installed apps.
How does TestingBot compare to BrowserStack and SauceLabs for real device testing?
TestingBot offers EU-hosted real devices (BrowserStack and SauceLabs are primarily US-hosted), GDPR and CSA STAR compliance, transparent flat-rate pricing, free for open source, and identical Appium/XCUITest/Espresso APIs. Customers usually migrate for the EU data residency, faster device allocation and direct support from the engineering team.
Looking for hands-on details? See the [live mobile app testing](https://testingbot.com/features/manual-mobile-testing) page or browse our [supported devices](https://testingbot.com/support/app-automate/devices).
Start in seconds
### Sign up for a Free Trial
Start testing your apps with TestingBot.
[Start a free trial](https://testingbot.com/users/sign_up)[Request a demo](https://testingbot.com/demo)
Resources you may like
[](https://testingbot.com/resources/articles/appium-flutter-app-testing)
### [Testing Flutter Apps with Appium](https://testingbot.com/resources/articles/appium-flutter-app-testing)
Learn how to do automated mobile app testing with Flutter and Appium.
[Read more](https://testingbot.com/resources/articles/appium-flutter-app-testing)
[](https://testingbot.com/resources/articles/localized-testing-mobile-app)
### [How to do localisation testing for mobile apps](https://testingbot.com/resources/articles/localized-testing-mobile-app)
Find out how to perform localisation testing with mobile apps. Change your language or locale with Appium, XCUITest and Espresso.
[Read more](https://testingbot.com/resources/articles/localized-testing-mobile-app)
[](https://testingbot.com/resources/articles/dark-mode-testing)
### [Dark Mode Testing with Appium](https://testingbot.com/resources/articles/dark-mode-testing)
Perform dark mode automated testing on iOS and Android with Appium.
[Read more](https://testingbot.com/resources/articles/dark-mode-testing)
---
URL: https://testingbot.com/playwright-vs-selenium
Framework comparison · 2026
# Playwright vs Selenium
Playwright and Selenium are the two dominant browser automation frameworks. This guide compares them head-to-head — speed, language support, browser coverage, debugging, and which to pick — with code samples and a verdict you can act on in five minutes.
[Run both on TestingBot](https://testingbot.com/users/sign_up)[Jump to comparison](https://testingbot.com#comparison)
Browsers & devices6100+
Cloud parallelism100×
Uptime SLA99.99%
Playwright·login.spec.ts
// playwrighttest('login',async({page}) => {awaitpage.goto('/login');awaitpage.fill('#user','jane');awaitpage.fill('#pwd','••••');awaitpage.click('#go');awaitexpect(page)
.toHaveURL(/dash/);
});
chromium · firefox · webkit ✓ 1.2s
Selenium·test\_login.py
# seleniumdeftest\_login(d):
d.get('/login')
d.find('#user').send('jane')
d.find('#pwd').send('••••')
d.find('#go').click()
Wait(d,10).until(url\_contains('/dash'))
chrome · safari · edge · ie11 ✓ 1.6s
Same TestingBot grid · 6100+ browsers & devices
parallel:10x
## Trusted by some of the world's most innovative companies
30-second answer
## Which one should you pick?
Pick Playwright if…
You're starting a new web project, your team writes JavaScript or TypeScript, and you want fast tests, auto-waiting and built-in tracing out of the box.
Pick Selenium if…
Your team writes Java, C# or Ruby, you need legacy browser support (Internet Explorer, older Safari), or you have an existing Selenium suite and a strong Page Object library.
Pick both if…
You have separate frontend and backend test suites, you're migrating gradually, or you want Playwright for new code and Selenium for the long-tail of legacy browsers. TestingBot runs both on the same grid.
Background
## What are Playwright and Selenium?
Two frameworks, two philosophies. The same goal: drive a real browser to automate end-to-end testing.

### Playwright
Released 2020 · Microsoft · Apache 2.0
Playwright is an end-to-end testing framework built by Microsoft that controls Chromium, Firefox and WebKit through a single API. It was designed by the team that originally built Puppeteer at Google, with cross-browser support and modern web app needs in mind.
It uses the browser's DevTools protocol (or its own bridge for Firefox/WebKit) instead of WebDriver, which lets it auto-wait for elements, intercept network traffic and capture rich traces by default.
- TypeScript / JavaScript / Python / Java / C# bindings
- Auto-wait, network interception, fixtures, traces
- Chromium, Firefox, WebKit · no IE / legacy Safari

### Selenium
Released 2004 · Open Source · Apache 2.0
Selenium is the original browser automation framework and the foundation of the W3C WebDriver standard. Selenium WebDriver drives any browser through a vendor-supplied driver (chromedriver, geckodriver, safaridriver, edgedriver, IEDriverServer) using the same wire protocol.
Twenty years of ecosystem maturity means deep IDE integration, mature Page Object patterns, and bindings for every major language. Selenium 4 added Chrome DevTools support, BiDi events and bidirectional protocols.
- Java / Python / C# / Ruby / JavaScript / Kotlin
- W3C WebDriver standard — works with every browser
- Chrome, Firefox, Safari, Edge, IE 11 — full coverage
Head to head
## Playwright vs Selenium: side-by-side comparison
Across the dimensions that matter for picking, migrating or running both in CI.
| Dimension | Playwright | Selenium |
| --- | --- | --- |
| First release | 2020 | 2004 |
| Maintained by | Microsoft | Open-source community + W3C |
| Languages | TS, JS, Python, Java, C# | Java, Python, C#, Ruby, JS, Kotlin |
| Browsers | Chromium, Firefox, WebKit | Chrome, Firefox, Safari, Edge, IE 11 |
| Mobile testing | Mobile emulation only | Real iOS + Android via Appium |
| Wire protocol | CDP + custom (Firefox/WebKit) | W3C WebDriver standard |
| Auto-waiting | Built in everywhere | Manual waits or WebDriverWait |
| Network interception | First-class API | BiDi (Selenium 4) or proxy |
| Multiple tabs / origins | Native — same context | Window switching API |
| Test runner | Built-in (@playwright/test) | Bring your own (JUnit, pytest, Mocha…) |
| Parallel execution | Built-in worker pool | Selenium Grid or test-runner level |
| Trace / replay | Built-in Trace Viewer | Screenshots + manual logs |
| Debugging | Inspector + UI mode | IDE + driver logs |
| Speed (typical suite) | ≈30–40% faster on Chromium | Mature, predictable |
| Ecosystem maturity | Growing fast since 2020 | 20 years of integrations |
| Page Object pattern | Supported, less common | Industry standard |
| Free for open source on TestingBot | ✓ | ✓ |
Speed numbers from public benchmarks; vary by application. Both frameworks run on TestingBot's cloud with identical APIs to local execution.
The same test in both
## Filling a form, asserting the result
A login flow with auto-wait (Playwright) versus an explicit wait (Selenium). Both pass on the same TestingBot grid.
Playwright + TypeScriptlogin.spec.ts
// playwright.config.ts → set TestingBot endpointimport{test,expect}from'@playwright/test';test('login redirects to dashboard',async({page}) => {awaitpage.goto('https://app.example.com/login');// auto-wait — no explicit wait neededawaitpage.getByLabel('Username').fill('jane@example.com');awaitpage.getByLabel('Password').fill('••••••••');awaitpage.getByRole('button', {name:'Sign in'}).click();awaitexpect(page).toHaveURL(/\/dashboard/);awaitexpect(page.getByRole('heading', {name:/welcome, jane/i})).toBeVisible();
});
Selenium + Pythontest\_login.py
# Driver points at TestingBot remote URLfromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasECdeftest\_login\_redirects\_to\_dashboard(driver):
driver.get('https://app.example.com/login')
driver.find_element(By.NAME,'username').send_keys('jane@example.com')
driver.find_element(By.NAME,'password').send_keys('••••••••')
driver.find_element(By.CSS_SELECTOR,'button[type=submit]').click()# explicit waitWebDriverWait(driver,10).until(EC.url_contains('/dashboard'))
welcome = driver.find_element(By.TAG_NAME,'h1')assert'Welcome, Jane'inwelcome.text
Both tests are pointed at the same [Playwright](https://testingbot.com/features/automation/playwright) / [Selenium](https://testingbot.com/features/automation/selenium) endpoint on TestingBot's cloud. Same browsers, same parallelism, same dashboard.
Decision matrix
## When to choose which

### Choose Playwright when
- You're starting a green-field test suite, no migration cost to weigh.
- Your stack is JavaScript / TypeScript-first and you want the test runner that ships with the framework.
- You need network mocking, multi-tab flows, file uploads or geolocation overrides without bolt-on tools.
- You want trace-based debugging; record, save, scrub through a failed run frame-by-frame.
- Your target browsers are Chromium, Firefox and WebKit. No Internet Explorer 11 or older Safari requirement.
[Run Playwright on TestingBot](https://testingbot.com/features/automation/playwright)

### Choose Selenium when
- Your team writes Java, C# or Ruby — Playwright's bindings exist but the JS-first ergonomics get diluted.
- You need to test on Internet Explorer 11, older Safari, or unusual browser builds where only WebDriver works.
- You have a mature Page Object library, custom locators, or BDD framework (Cucumber, SpecFlow) you don't want to throw away.
- You're standardising across web AND real mobile devices. Selenium's API is the closest thing to a unified WebDriver story (with Appium for mobile).
- Stability and ecosystem maturity outweigh fewer features. Selenium has been battle-tested for 20 years.
[Run Selenium on TestingBot](https://testingbot.com/features/automation/selenium)
TestingBot supports both
## Stop choosing — run both on the same grid
Point Playwright at the TestingBot endpoint, point Selenium at the same one, and your tests share the same 6100+ browsers and devices, the same dashboard, the same parallel slots and the same EU data residency.
- Same auth, same project, same billing
- Side-by-side test history for both frameworks
- Free for open source — both frameworks
[Start free](https://testingbot.com/users/sign_up)[Request a demo](https://testingbot.com/demo)
testingbot.com / endpoint
// Playwright
use: { wsEndpoint: 'wss://cloud.testingbot.com/playwright' }
# Selenium
webdriver.Remote(
command\_executor='https://hub.testingbot.com/wd/hub'
)
FAQ
## Frequently Asked Questions
The questions teams ask before picking — or migrating between — these frameworks.
Is Playwright faster than Selenium?
Generally yes, on Chromium-based browsers. Typical suites run 30–40% faster on Playwright thanks to the DevTools protocol bypassing WebDriver's HTTP round-trips. The gap closes for Firefox and Safari (where Playwright uses its own bridges) and disappears once your tests are network-bound. Selenium 4's BiDi support narrows the gap further. For most teams the speed difference is real but not the deciding factor.
Should I migrate from Selenium to Playwright?
Only if the cost is justified. If your existing Selenium suite is stable, in a non-JavaScript language, and uses Page Objects extensively, migrating is rarely worth the effort. If your suite is flaky, JS-based and lacks tracing, Playwright's auto-wait and Trace Viewer alone usually pay for the migration. Pragmatic teams keep both: Selenium for what's already working, Playwright for new test suites.
Does Playwright support all browsers Selenium does?
No. Playwright supports Chromium (and Chrome/Edge by extension), Firefox and WebKit (which is close to Safari but not identical). Selenium drives the actual browser builds, including Internet Explorer 11, older Safari versions and rare browsers, using vendor-shipped drivers. If you need to test on IE 11 or a specific legacy Safari, Selenium is your only option.
Can Playwright test mobile apps?
Not natively. Playwright offers mobile browser emulation (you can simulate a mobile viewport and user agent in Chromium), but it cannot drive native iOS or Android apps. For real mobile app testing you need [Appium](https://testingbot.com/features/automation/appium), [XCUITest](https://testingbot.com/features/automation/xcuitest), [Espresso](https://testingbot.com/features/automation/espresso) or [Maestro](https://testingbot.com/features/automation/maestro). Selenium can drive mobile via Appium, which is built on the WebDriver protocol. TestingBot supports all of these on real iOS and Android devices.
Which is better for cross-browser testing?
For modern Chromium / Firefox / WebKit, Playwright's single API gives you faster, more reliable cross-browser runs out of the box. For comprehensive coverage including legacy browsers (IE 11, older Safari, Edge Legacy), Selenium is still the only option that drives the real browser binaries. Either way, running across many real browsers in parallel benefits hugely from using TestingBot's cloud. Both frameworks integrate with TestingBot's cloud with a one-line config change.
Do I need to learn JavaScript to use Playwright?
No, but Playwright's ergonomics are JS/TS-first. The Python, Java and C# bindings are first-class but lag the JS API on new features by weeks. If your team already writes Python or Java, Playwright is still a strong choice, but Selenium's binding parity makes it the more natural fit for non-JS stacks.
Can I run Playwright and Selenium on TestingBot?
Yes, both run on the same TestingBot cloud. Playwright connects via the wsEndpoint config to `wss://cloud.testingbot.com/playwright`, Selenium connects to `https://hub.testingbot.com/wd/hub` via `webdriver.Remote`. Tests show up in the same dashboard, share parallel slots, and benefit from the same EU data residency, video recording, live debugging and CI/CD integrations. Both are free for open source projects.
Going deeper? See the dedicated [Playwright](https://testingbot.com/features/automation/playwright) and [Selenium](https://testingbot.com/features/automation/selenium) pages.
### Sign up for a Free Trial
Run Playwright, Selenium, or both, on TestingBot's cloud.
[Start a free trial](https://testingbot.com/users/sign_up)
Resources you may like
[](https://testingbot.com/resources/articles/playwright-visual-regression-testing)
### [Visual Testing with Playwright](https://testingbot.com/resources/articles/playwright-visual-regression-testing)
Playwright provides automated browser testing. It offers a built-in feature to perform visual regression testing for your website.
[Read more](https://testingbot.com/resources/articles/playwright-visual-regression-testing)
[](https://testingbot.com/resources/articles/generative-ai-selenium)
### [Selenium and Generative AI](https://testingbot.com/resources/articles/generative-ai-selenium)
Generate realistic looking test data to be used with your Selenium Automated Tests.
[Read more](https://testingbot.com/resources/articles/generative-ai-selenium)
[](https://testingbot.com/resources/articles/playwright-recorder)
### [Record tests with Playwright](https://testingbot.com/resources/articles/playwright-recorder)
Learn how to use a Playwright Recorder to easily record tests.
[Read more](https://testingbot.com/resources/articles/playwright-recorder)
---
URL: https://testingbot.com/pricing
# Plans for Every Team Size
From solo testers to large enterprises: get instant access to **6100+ real browsers and devices**. No setup. No maintenance. Just testing.
- Trusted by 100,000+ developers
- GDPR & CSA STAR
## Subscription plans
### Live
Manual testing of websites and apps
$20/month
Billed annually
Or $30 month-to-month
1 User 2 Users 4 Users 10 Users 25 Users
-
**Unlimited** Live Testing
-
**Unlimited** Visual Testing
-
Real iOS & Android devices
-
24x7 Active Support via Email, Chat & Slack
[Upgrade Plan](https://testingbot.com/pricing/1)
### Automated
Automated web and app testing
$50/month
Billed annually
Or $70 month-to-month
1 Parallel Test - 1,000 minutes 2 Parallel Tests - 2,000 minutes 4 Parallel Tests - 4,000 minutes 8 Parallel Tests - 6,000 minutes 16 Parallel Tests - 10,000 minutes 24 Parallel Tests - 20,000 minutes
-
**1000** minutes Automated / month
-
**Unlimited** Live testing
-
**Unlimited** Visual Testing
-
Real iOS & Android devices
-
24x7 Active Support via Email, Chat & Slack
[Upgrade Plan](https://testingbot.com/pricing/6)
MOST POPULAR
### Automated Pro
Unlimited web and app testing
$90/month
Billed annually
Or $120 month-to-month
1 Parallel Test - Unlimited 2 Parallel Tests - Unlimited 4 Parallel Tests - Unlimited 8 Parallel Tests - Unlimited 16 Parallel Tests - Unlimited 24 Parallel Tests - Unlimited
-
**Unlimited** Live Testing
-
**Unlimited** Visual Testing
-
**Unlimited** Automated
-
Real iOS & Android devices
-
24x7 Active Support via Email, Chat & Slack
[Upgrade Plan](https://testingbot.com/pricing/12)
### Enterprise
Enterprise-grade testing for large teams
For Premium features & Enterprise-grade security
10+ Parallel Tests
-
**Unlimited** Testing
-
Single Sign On
-
Priority Support
-
[other Enterprise Features](https://testingbot.com/enterprise)
[Contact us](https://testingbot.com/contact/new)
## Pay As You Go
Need testing minutes without a monthly commitment? Purchase credits once and use them whenever you need.
One-Time Purchase
### Pay As You Go
Buy testing minutes once, use them whenever you need. No subscription required.
1,000 minutes - $602,000 minutes - $1205,000 minutes - $25015,000 minutes - $600
$60
One-time payment
[Buy Now](https://testingbot.com/payg/checkout/starter)
#### Your plan includes
Testing Minutes
1,000
Parallel Sessions
1
Cost per Minute
$0.06
Validity
Never Expires
- All desktop browsers
- iOS & Android devices + sim/emu
- Video recordings included
- 24/7 Support
- Local testing supported
- Automated, Manual, Visual, AI testing
## Compare plans
Detailed Plan Comparison
Feature
Live$20/monthBilled annually
[Upgrade Plan](https://testingbot.com/pricing/1)
Automated$50/monthBilled annually
[Upgrade Plan](https://testingbot.com/pricing/6)
Pro$90/monthBilled annually
[Upgrade Plan](https://testingbot.com/pricing/12)
EnterpriseFor Premium features & Enterprise-grade security
[Contact us](https://testingbot.com/contact/new)
### General
FeatureLiveAutomatedProEnterprise
Testing Minutes
Unlimited
Starts at 1000 minutes / month
Unlimited
Unlimited
Users
—
Unlimited
Unlimited
Unlimited
Desktop Browsers
Mobile Devices
Visual Testing
Automated Testing
—
AI Testing
Run tests without writing any code. The AI Agent will create and run tests for you.
—
Accessibility Testing
—
Test Analytics
—
Single-Use Machines
TestingBot provides single-use machines: you always start secure, with a clean machine.
REST API Access
Customizations
—
—
—
High Volume Testing
—
—
—
### Local & network
FeatureLiveAutomatedProEnterprise
Test Local Websites
High Availability Mode
—
—
—
Mobile DevTools
Access to Native DevTools on real browsers and devices.
GPS Geolocation Testing from 100+ countries
Geolocation Testing
Test your application from over 15 geographic locations around the world.
Network Throttling
Simulate various network conditions, such as 3G, 4G and offline modes, to test your application's performance under different connectivity scenarios.
### Integrations
FeatureLiveAutomatedProEnterprise
Slack
JIRA
GitHub
[CI/CD](https://testingbot.com/support/integrations/ci-cd)
### Administration & security
FeatureLiveAutomatedProEnterprise
2-Factor Authentication (2FA)
SAML 2.0 Single sign-on (SSO)
—
—
Analytics per team & team-member
—
—
—
Auto User Provisioning
—
—
—
IP Whitelisting (inbound & outbound)
—
—
—
### Support
FeatureLiveAutomatedProEnterprise
24x7 Availability
Email Support
Priority Support
—
—
—
## All plans include
-
**Over [6100+ real desktop browsers](https://testingbot.com/support/web-automate/browsers) and [physical mobile devices](https://testingbot.com/support/app-automate/devices)**
-
**Real Browsers on Windows, macOS and Linux**
-
**Range of screen resolutions**
-
**Videos and screenshots of your tests**
-
**Team management**
-
**Local testing**
-
**Android/iOS Native App Testing**
-
**Tech support and Documentation**
-
**Unlimited Screenshots & Manual tests**
## Frequently asked questions
### Do I need a credit card to register?
No, you do not need a credit card. Our 14-day free plan lets you try the service without paying anything. When you are ready to upgrade you can pay by credit card or wire transfer.
### Can I cancel my subscription at any time?
Yes. Subscriptions can be cancelled at any time. If you cancel mid-month you will be charged for that month.
### What is a parallel test?
Parallel tests are how many tests you can run at the same time. More parallel tests means a shorter total test run. Use our [Parallel Test Calculator](https://testingbot.com/support/parallel-calculator) to estimate the right number for your suite.
### Do you have a free open-source plan?
Yes, open-source projects can apply for a free TestingBot plan. [Sign up for our open-source plan](https://testingbot.com/open-source).
### What happens if I exceed my monthly testing time?
Once you reach your monthly testing time you can no longer run tests until your next billing month, or you can upgrade to a larger plan.
### What forms of payment do you accept?
We accept credit cards and wire transfers. EU customers also have access to additional payment options. [Contact us](https://testingbot.com/contact/new) for wire transfer.
### Can I get a private cloud or on-prem deployment?
Our Enterprise plan supports private cloud. We do not yet support on-premise deployments. [See enterprise features](https://testingbot.com/enterprise) or contact us for a custom quote.
### Do you offer high-volume packages?
Yes, for very large test suites we offer custom high-volume packages with discounted per-minute rates. [Contact us](https://testingbot.com/contact/new) with your expected monthly volume.
## Need help finding the right solution for your needs?
Contact us and we'll help you find the right plan for your needs.
[Contact us](https://testingbot.com/contact/new)
---
URL: https://testingbot.com/enterprise
Enterprise plan
# Test at enterprise scale, secured for the way you ship.
TestingBot powers cross-browser, mobile and AI-driven testing for global engineering teams. Single-use firewalled VMs, SSO, auto-provisioning and a private device cloud, on EU-hosted infrastructure with a 99.99% uptime SLA.
[Contact Sales](https://testingbot.com/contact/new)[Book a demo](https://testingbot.com/demo)
Browsers & devices6100+
Uptime SLA99.99%
Years in business14
Enterprise features
1. Priority SLA
2. Security
3. Data privacy
4. Private devices
Priority support · 24/7 SLA
Response\< 1 hour
Uptime SLA99.99 %
ChannelsSlack · Chat · Email
SK
Customer Success Manager
INC-2917 · acknowledged in 2 min
custom SLA · dedicated CSM · custom legal terms
Your network10.0.0.0/16
TestingBot grid
TLS 1.3IP whitelistedTunnel up
Data residency · EU
session-2917.mp4
12.4 MB
console.log
238 KB
screenshots/\*.png
4.1 MB
encrypted at rest · per-test retention
Private device cloud
Reserved iPhone 17iOS 26
Reserved iPad ProiPadOS 18
Reserved Galaxy S26Android 16
dedicated to your team · never shared
Custom SLA · 24/7 priority support · dedicated CSM Encrypted tunnel · IP whitelisting · inbound + outbound EU-hosted · GDPR · per-test retention windows Reserved exclusively for your team, never shared
Trusted by engineering teams worldwide
Built for security & scale
## Everything large engineering teams need from a testing cloud.
Enterprise-grade controls layered on top of every TestingBot session: security, identity, governance and uninterrupted scale.
-
### Single-Use VMs
Every test runs on a fresh, isolated, firewalled VM that is wiped immediately after the session. Customize the OS or pre-installed software per run.
-
### Network Security
IP whitelisting (inbound and outbound) and the encrypted TestingBot Tunnel keep test traffic between your private network and our grid fully secured.
-
### Data Control & Privacy
Disable logging or recording per test. Pick exact data retention windows per asset type. All test artefacts stay on EU-hosted infrastructure.
-
### SSO & 2FA
SAML 2.0 SSO with Okta, Azure AD, OneLogin and any custom SAML provider. Enforce 2FA across your entire organisation.
-
### Auto-provisioning
Onboard and offboard team members through your Identity Provider. Roles, teams and product access stay in sync automatically.
-
### Sub-teams & Quotas
Group testers by squad, project or geography. Allocate parallel session quotas, enforce 2FA and share Tunnels per sub-team with granular usage analytics.
Compliance & assurance
## Built and operated to standards procurement teams already trust.
Independent attestations, EU data residency and a public Trust Center keep your security review on the fast track.
[Trust Center](https://trust.testingbot.com)
- 
CSA STAR Level 1
Cloud Security Alliance
- 
GDPR
EU data protection
- 
CCPA
California privacy
- ISO
ISO 27001
Aligned controls
- SOC 2
SOC 2 aligned
Type II practices
-
EU-hosted
Data stays in the EU
Dedicated devices
## Private Device Cloud
With Private Device Cloud, we take care of the setup, configuration and maintenance of dedicated devices exclusively for your company.
- Unlike our Public Device Cloud, these devices are reserved only for your team, ensuring full control and privacy.
- Select your preferred devices, and we will make them available in our data center for seamless testing.
- Run both automated and manual tests on your private iOS and Android devices with full control and security.
[Learn more](https://testingbot.com/enterprise/private-device-cloud)

Side by side
## How TestingBot for Enterprise compares.
Everything in Standard, plus the security, governance and support large teams need.
| Feature | Standard Plans | Enterprise Plans Recommended |
| --- | --- | --- |
| Security & Compliance |
| IP Whitelisting (inbound & outbound) | – | |
| Single-Use VMs + Firewalled | | |
| 2FA enforcement | – | |
| Administration |
| SAML 2.0 Single sign-on (SSO) | – | |
| Sub-teams, parallel sessions per team | – | |
| Analytics per team & team-member | – | |
| Support |
| Dedicated Support person & Customer Success Manager | – | |
| Custom SLA | – | |
| 24/7 priority support via phone and e-mail | – | |
| Custom Legal Terms | – | |
| Product |
| Customize OS / Pre-installed Software | – | |
| Early access to beta features | – | |
| Unlimited Users | – | |
### Ready to roll out TestingBot across your organisation?
Talk to our enterprise team about SSO, sub-teams, dedicated devices and a deployment plan that fits your security review.
[Contact Sales](https://testingbot.com/contact/new)[Book a demo](https://testingbot.com/demo)
---
URL: https://testingbot.com/enterprise/private-device-cloud
Dedicated devices
# Private Real Device Cloud, dedicated to your team.
Pick the exact iOS and Android devices you want and we provision them in our datacenter. Reserved 24/7 for your team, customised to your testing needs and reachable on real carrier networks.
[Contact Sales](https://testingbot.com/contact/new)[Book a demo](https://testingbot.com/demo)
Devices on offerAny iOS / Android
Availability24/7

Trusted by engineering teams worldwide
What dedicated devices unlock
## The exact device matrix your customers use: at your fingertips, around the clock.
We buy, configure and host the iOS and Android devices you choose. Reserved exclusively for your team and customised to fit how you actually test.
-
### Pick the exact devices
Choose the iPhone, iPad, Pixel, Galaxy or any other device that matches your customer base. We source it, image it and install it for you.
-
### Reserved for your team
Your devices are never shared with other customers. No waiting in queues, guaranteed availability.
-
### Real carrier networks
Equip devices with a physical SIM or eSIM to test scenarios Wi-Fi cannot reach: SMS flows, phone calls, mobile-data behaviour and roaming.
-
### Custom device profiles
We pre-install your apps, profiles, certificates and beta builds. Keep persistent state across runs when you need it.
-
### Available 24 / 7
Your dedicated devices are reachable around the clock from any region for both automated runs and manual live sessions.
-
### Secure & EU-hosted
Devices live in our locked, EU-hosted datacenter. Network access stays inside your tunnel; logs and recordings stay on EU infrastructure.

How we host your devices
## Real devices, in our secure EU datacenter.
We purchase the iOS and Android devices you specify, install and configure them in our EU-hosted datacenter, and connect them to our cloud so your team can run automated and manual tests around the clock.
- Your model list, sourced and provisioned by us, including legacy or hard-to-find devices.
- Custom OS settings, certificates, profiles, beta apps and persistent state, set up once and re-applied automatically.
- Devices stay reachable 24/7.
Carrier connectivity
## Test on real carrier networks with SIM and eSIM.
Equip your dedicated devices with physical SIMs or eSIMs and exercise the full set of mobile-network behaviours your customers actually depend on.
- Send and receive real SMS, including 2FA codes, magic links and OTP flows.
- Place and receive real phone calls; verify in-app calling, voicemail and call-state UI.
- Validate mobile-data, low-signal and roaming behaviours that Wi-Fi can't reproduce.

Frequently asked
## Private Device Cloud: questions teams ask us.
Everything procurement, security and engineering tend to want to know before rolling out dedicated devices.
What is a Private Device Cloud?
A Private Device Cloud is a set of real iOS and Android devices that TestingBot purchases, hosts and maintains in our EU datacenter exclusively for your team. The devices are reachable 24/7 over the same Selenium and Appium endpoints you already use, but no other customer can ever run a test on them.
Can we choose the exact device models we want?
Yes. You pick the exact iPhone, iPad, Pixel, Galaxy or other models you need, including specific OS versions, regional variants and legacy devices that have left our public cloud. If we don't already stock the model, we will source it for you.
Do dedicated devices support real carrier networks?
Yes. We can equip your devices with physical SIMs or eSIMs from your preferred carriers, so you can test SMS, phone calls, mobile data, low-signal and roaming behaviour just like an end user would experience them.
Can we customise OS, profiles or pre-installed apps?
We pre-install your apps and beta builds, deploy custom configuration profiles, certificates and entitlements, and adjust device-level settings to match your needs. By default we wipe state between tests, but on private devices we can keep persistent state when that's useful.
Where are the devices hosted and how are they secured?
All Private Device Cloud devices are hosted in our EU-based datacenter. Devices are physically locked, network access is restricted, and logs, screenshots and video recordings stay on EU infrastructure. We are GDPR compliant and CSA STAR Level 1 registered.
How is the Private Device Cloud priced?
Pricing is custom and depends on the number of devices, models, carrier connectivity and the support tier you need. Talk to our enterprise team, we will put together a quote that fits your scope and timeline.
### Tell us which devices you need.
Share the device matrix, the testing scenarios and the timeline. Our enterprise team will draft a Private Device Cloud setup that fits.
[Contact Sales](https://testingbot.com/contact/new)[Book a demo](https://testingbot.com/demo)
---
URL: https://testingbot.com/partners
# Partner with TestingBot
Join our partner ecosystem to expand your business opportunities and enhance your product capabilities with TestingBot's next generation testing platform.
[Technology Partners](https://testingbot.com/partners/technology-partners)[Reseller Partners](https://testingbot.com/partners/reseller-partners)
-
1+bn
Tests Executed
-
180k
Active Users
-
5,500+
Browsers & Devices

        
        
## Partner Programs
Choose the partnership that best fits your business needs and goals.
### Technology Partners
TestingBot platform integrates with tools that developers use daily. Partner with us to enhance your product capabilities, reduce interruptions for developers and expand customer reach.
- API integration support
- Co-marketing opportunities
- Technical support and documentation
[Learn More](https://testingbot.com/partners/technology-partners)
### Reseller Partners
Expand your business by partnering with TestingBot. Join our Partner Program and earn lucrative commissions, receive dedicated support and gain recognition for your sales success.
- Competitive commission rates
- Dedicated partner support
- Sales and marketing materials
[Learn More](https://testingbot.com/partners/reseller-partners)
### Ready to Partner with Us?
Join TestingBot's growing partner ecosystem and unlock new opportunities for your business.
[See existing integrations](https://testingbot.com/integrations)
---
URL: https://testingbot.com/partners/technology-partners
# Technology Partners
TestingBot platform integrates with tools that developers use daily. Partner with us to enhance your product capabilities, reduce interruptions for developers and expand customer reach.
[Apply now](https://testingbot.com#apply)[All Partner Programs](https://testingbot.com/partners)

        
        
## Partner Benefits
Join our technology partner program and unlock powerful integrations with TestingBot's comprehensive testing platform.
### API Integration Support
Get comprehensive technical documentation, SDKs and dedicated engineering support for seamless integration.
### Co-Marketing Opportunities
Joint marketing initiatives, case studies, webinars and placement in our partner directory to expand your reach.
### Technical Documentation
Access to comprehensive technical resources, integration guides and best practices documentation.
### Partner Portal Access
Dedicated partner portal with resources, training materials and integration tracking tools.
### Priority Support
Dedicated technical support channel with priority response times for integration issues.
### Partner Recognition
Featured placement in our partner ecosystem, case studies and recognition at industry events.
## Integration Opportunities
Partner with TestingBot to create powerful integrations that enhance developer workflows and provide seamless testing experiences.
-
**Integrate into your product:** Embed our testing capabilities into your app. Offer automated testing to your customers through TestingBot's whitelabel automated testing offerings, or embed realtime live testing in your product.
-
**Developer Advocate:** Help developers use TestingBot's platform. We supply you with documentation, tools and free extended trial credits.
-
**Integrate your product with TestingBot:** Create an integration between TestingBot and your product.

## Apply to Become a Technology Partner
Ready to integrate with TestingBot? Fill out the form below and our partnership team will be in touch.
Company Name \*
Email Address \*
Tell us about your integration \*
Submit Application
---
URL: https://testingbot.com/partners/reseller-partners
# Reseller Partners
Expand your business by partnering with TestingBot. Resellers in the Partner Program enjoy lucrative commissions, dedicated support and recognition for their sales excellence.
[Apply now](https://testingbot.com#apply)[All Partner Programs](https://testingbot.com/partners)

        
        
## Reseller Benefits
Join our reseller program and unlock revenue opportunities with comprehensive support and competitive commission structures.
### Competitive Commissions
Earn attractive commission rates on all TestingBot subscriptions you refer, with performance-based bonuses for top performers.
### Dedicated Support
Get priority access to our partner success team for sales support, technical questions, and customer assistance.
### Sales & Marketing Materials
Access to professional sales collateral, presentations, case studies, and co-branded marketing materials.
### Training & Certification
Comprehensive product training, sales certification programs, and ongoing education to maximize your success.
### Partner Portal
Access to a comprehensive partner portal with commission tracking, customer management, and resource library.
### Partner Recognition
Top performers receive recognition awards, conference invitations, and exclusive networking opportunities.
## Who We're Looking For
We partner with organizations that share our commitment to quality and customer success. There is no partnership fee.
### Ideal Partner Profile
-
Software consultancies and development agencies
-
QA and testing service providers
-
DevOps and infrastructure consultants
-
Technology solution providers
## Apply to Become a Reseller Partner
Ready to join our reseller program? Fill out the form below and our partnerships team will review your application.
Company Name \*
Email Address \*
Tell us about your business \*
Submit Application
---
URL: https://testingbot.com/open-source
# Free Testing for Open-Source Projects
We think it's important to support open-source projects. That's why TestingBot offers an Open-Source plan which is **completely free** and offers **Unlimited Testing** with **2 concurrent tests**.
## Sign up for an Open Source account
Please [Sign Up](https://testingbot.com/users/sign_up) first for a free trial.
Once logged in, you will be able to submit a request on this page to convert your account to an open-source account.
### Browser/OS Combinations
Run your Automated tests on [+6100 Browser/device combinations](https://testingbot.com/support/web-automate/browsers). Make sure your project works on all major browsers and devices.
### CI Testing
Using [Travis-CI](https://testingbot.com/support/integrations/ci-cd/travis-ci), [Circle CI](https://testingbot.com/support/integrations/ci-cd/circleci) or any other Continuous Integration system to build and test your project?
It's easy to use TestingBot in combination with any CI system. Simply modify your tests to use TestingBot's Selenium grid.
### Video/Screenshots/Meta-Data
With every test you have access to the video showing the test, screenshots and additional meta-data (log files).
We provide all these assets to make sure you can easily debug any potential problems during your tests.
### Status Badges & Matrix
We provide [status badges](https://testingbot.com/support/other/status-badges) to display your test results on your project's website.
Together with status badges, we also offer a badge that displays on which browsers your test succeeded/failed: [Browser Matrix](https://testingbot.com/support/other/browser-matrix) .
---
URL: https://testingbot.com/about
# About Us
Founded in 2011, TestingBot aims to provide an easy-to-use platform for cross-browser and mobile testing. By leveraging the power of cloud computing, we can scale effortlessly to meet your testing needs.
We offer flexible plans tailored for both startups and large enterprises, enabling you to test your website across hundreds of real browser and mobile device combinations. Together with **excellent customer service** , we're confident you will enjoy testing your websites with TestingBot.

## Trusted by some of the world's most innovative companies
## Helping developers and teams test. Reliably and at scale.
TestingBot provides a powerful online testing grid with a wide range of real browsers and physical mobile devices.
Our entire infrastructure is hosted in multiple European data centers, located in Belgium, Germany and Finland, ensuring optimal security, performance and reliability.
At TestingBot, we take pride in delivering a stable, fast and secure testing environment for our customers.
+6 million
Tests each month
99.99%
Uptime
12,000
New users annually
## What drives us
The principles that shape how we build, host and support the TestingBot platform.
### Security
Security is a first-class feature at TestingBot. We guarantee single-use machines and wiped devices for each test.
[Visit our Trust Center](https://trust.testingbot.com)
### European infrastructure
Our platform runs from our own infrastructure across Europe. No third-party cloud providers, no data leaving the EU. Your tests and test data stay safe.
### Customer-first support
Real engineers answer every support ticket, fast. We've been building TestingBot since 2011 and we still treat every customer like our first.
## Our journey
TestingBot was founded in 2011 to solve a personal need: the founder wanted an easy way to run automated tests using Selenium on a website. Automated web testing was a tedious task, and the only solution was to set up a Selenium Grid on your own server.
We wanted to change that and started TestingBot to provide a simple solution for automated web testing. Over the years, we've continuously expanded our platform with new features. Explore the timeline below for a complete overview of our journey.
1.
2011
🚀 TestingBot is released to the public as one of the first cloud-based Selenium grids.
2.
2012
Realtime manual testing was added for Desktop browsers.
3.
2013
🖥️ macOS based testing was released: the first company providing test access to remote Safari browsers.
4.
2014
iOS Simulators & Android Emulators are added for mobile testing.
5.
2015
TestingBot Tunnel is released: test websites running on your own computer or private network.
6.
2016
📱 We make our real device farm available to the public. Test your website on physical iOS and Android devices.
7.
2017
🗺️ This year we added geolocation testing, headless testing and various integrations with Slack, TeamCity etc.
8.
2018
We launch codeless automation. Import recorded tests from Selenium IDE and run the tests at specific intervals.
9.
2019
Live App Testing is launched: test your mobile app manually on (physical) mobile devices.
10.
2020
📈 Introducing Test Analytics: keep track of your test analytics. Release Cypress automation support, run your Cypress tests on TestingBot.
11.
2021
We've added Puppeteer Playwright testing to our features. Connect your Puppeteer scripts to the remote TestingBot browser grid.
12.
2022
Android Espresso and XCUITest support. Run tests against native apps on TestingBot's device grid.
13.
2023
Connect your Playwright scripts to the remote TestingBot browser grid.
14.
2024
🔎 Visual Regression Testing is launched: compare snapshots of your website or mobile app against baselines.
15.
2025
✨ Introducing AI-based testing: describe in natural language what you would like to test. TestingBot will monitor your website for failures.
16.
2026
🎭 Maestro mobile testing support: run Maestro flows against iOS simulators, Android emulators and physical devices on TestingBot's device cloud.
## Our team
[
### Jochen
Founder ](https://testingbot.com/about/jochen-d)

#### Laura
Support

#### Sander
Marketing
## Want to join our team?
Interested in working on exciting technologies?
[We're hiring](https://testingbot.com/careers)
---
URL: https://testingbot.com/about/jochen-d

# Jochen Delabie
Founder & CEO
Moerbeke-Waas, Belgium
Founder of TestingBot. Building cloud testing infrastructure since 2011.
Jochen Delabie founded TestingBot in 2011 to make cross-browser and mobile testing
approachable for development teams. Over the past 15+ years he has worked on test
automation, infrastructure and developer tooling around Selenium, Appium and
Playwright. He writes regularly on the TestingBot blog about browser automation,
mobile testing and AI-driven testing workflows.
## Areas of expertise
Cross browser testingSelenium WebDriverAppiumPlaywrightAI-driven testingMobile app testingTest automationContinuous integration
## Recent blog posts by Jochen
[ ](https://testingbot.com/blog/maestro-physical-device-testing)
Jan 19, 2026
### [Maestro Physical Device Testing](https://testingbot.com/blog/maestro-physical-device-testing)
Maestro testing in parallel on physical Android and iOS devices.
[Read more](https://testingbot.com/blog/maestro-physical-device-testing)
[ ](https://testingbot.com/blog/tvos-automated-testing)
Oct 15, 2025
### [tvOS Physical Device Testing](https://testingbot.com/blog/tvos-automated-testing)
Run automated tvOS tests on physical AppleTV devices.
[Read more](https://testingbot.com/blog/tvos-automated-testing)
[ ](https://testingbot.com/blog/macos-tahoe-beta-testing)
Jun 10, 2025
### [MacOS Tahoe Cloud Testing](https://testingbot.com/blog/macos-tahoe-beta-testing)
Test your websites and apps on Apple's upcoming macOS Tahoe (macOS 26).
[Read more](https://testingbot.com/blog/macos-tahoe-beta-testing)
[ ](https://testingbot.com/blog/chromeos-testing)
Jun 10, 2025
### [ChromeOS Cloud Testing](https://testingbot.com/blog/chromeos-testing)
ChromeOS Manual and Automated testing in the cloud.
[Read more](https://testingbot.com/blog/chromeos-testing)
[ ](https://testingbot.com/blog/maestro-cloud-testing)
Jun 2, 2025
### [Maestro testing in the cloud with TestingBot](https://testingbot.com/blog/maestro-cloud-testing)
Run Maestro tests in the cloud with TestingBot.
[Read more](https://testingbot.com/blog/maestro-cloud-testing)
[ ](https://testingbot.com/blog/accessibility-web-testing)
May 26, 2025
### [Accessibility testing with TestingBot](https://testingbot.com/blog/accessibility-web-testing)
TestingBot offers automated accessibility testing for your websites.
[Read more](https://testingbot.com/blog/accessibility-web-testing)
[ ](https://testingbot.com/blog/shopify-store-monitoring)
Feb 27, 2025
### [Monitor your Shopify Store](https://testingbot.com/blog/shopify-store-monitoring)
Protect your Shopify Store from downtime: automated monitoring of Shopify Stores with real browsers.
[Read more](https://testingbot.com/blog/shopify-store-monitoring)
[ ](https://testingbot.com/blog/testingbot-ai-agent)
Feb 18, 2025
### [Generative AI test agent](https://testingbot.com/blog/testingbot-ai-agent)
TestingBot has launched an AI Test Agent that will help you to create automated tests
[Read more](https://testingbot.com/blog/testingbot-ai-agent)
[ ](https://testingbot.com/blog/testing-holiday-season)
Nov 19, 2024
### [Automated Testing with TestingBot during the Holiday Season](https://testingbot.com/blog/testing-holiday-season)
Augment your testing this holiday season with TestingBot.
[Read more](https://testingbot.com/blog/testing-holiday-season)
[ ](https://testingbot.com/blog/selenium-20-years)
Oct 29, 2024
### [Selenium 20th birthday](https://testingbot.com/blog/selenium-20-years)
Celebrate Selenium's 20th birthday. An overview of the years.
[Read more](https://testingbot.com/blog/selenium-20-years)
## Articles by Jochen
Sep 18, 2025
### [Top Web Development Tools in 2025](https://testingbot.com/resources/articles/web-development-tools)
A top 10 list of the most popular and useful web development tools.
Jun 22, 2025
### [Most popular Screen Resolutions](https://testingbot.com/resources/articles/common-screen-resolutions)
An overview of the most popular screen resolutions in 2025 for desktop and mobile.
May 26, 2025
### [Selenium Python Tutorial](https://testingbot.com/resources/articles/python-selenium-web-automation-test)
A quick introduction in using Python with Selenium WebDriver
May 22, 2025
### [How to Change Browser Settings](https://testingbot.com/resources/articles/how-to-change-browser-settings)
Find out how to change browser settings on Chrome, Firefox and Microsoft Edge.
May 17, 2025
### [What is isolation testing?](https://testingbot.com/resources/articles/isolation-test)
Isolation Testing means verifying the functionality of a specific part of software on its own, free from outside influences.
May 7, 2025
### [Why is accessibility testing important in web design?](https://testingbot.com/resources/articles/why-accessibility-testing)
Find out why it's important to think about accessibility when designing your webpages.
Mar 14, 2025
### [Protect your Shopify store](https://testingbot.com/resources/articles/shopify-store-monitoring)
Learn how to monitor your Shopify store with the TestingBot App.
Mar 13, 2025
### [Top 15 AI Testing Tools](https://testingbot.com/resources/articles/top-15-ai-testing-tools)
Learn about the top 15 AI testing tools.
Dec 18, 2024
### [Run Maestro tests in the cloud](https://testingbot.com/resources/articles/maestro-cloud-testing)
Maestro cloud testing allows you to run Maestro flows on remote iOS and Android devices.
Nov 29, 2024
### [Handling Exceptions with Selenium Webdriver](https://testingbot.com/resources/articles/exception-handling-with-selenium)
Learn about the different types of Selenium Exceptions and how to prevent these.
## Get in touch with Jochen
Questions about cross-browser testing, mobile automation, or working with TestingBot? Reach out.
[Contact us](https://testingbot.com/contact/new)
---
URL: https://testingbot.com/contact/new
Talk to TestingBot
# Contact TestingBot
If you need support, have questions, or are interested in purchasing a plan, feel free to reach out, we'll get back to you promptly.
Name
Work email
How can we help?
The more context you share, the faster we can route your message to the right person.
By sending this message you agree to our [privacy policy](https://testingbot.com/privacy). We use your details only to reply to your request.
Send message
Need a faster path?
Skip the form for these common tasks.
[Book a 15-minute demo Live walkthrough by an engineer](https://testingbot.com/demo)[Browse documentation Setup guides for every framework](https://testingbot.com/support)[Check system status Live uptime & incidents](https://status.testingbot.com)[Join our Slack community Get help from devs and our team](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
Other ways to reach us
[Phone (US toll-free) +1 (855) 410-0024](tel:+18554100024)[Email info@testingbot.com](mailto:info@testingbot.com)
Address
TestingBotVlinderhof 18 9180 Moerbeke BelgiumVAT BE 0842.864.474
## Frequently Asked Questions
Quick answers about contacting our team, demos, trials and security.
How quickly will TestingBot respond to my message?
Our team replies to most contact form messages within a few hours during European business hours, and within 24 hours otherwise. Paid customers get priority support around the clock.
Should I contact sales or support?
Use this form for sales, billing, partnership and general questions. For technical issues with a running test or your account, signed-in users can also reach support directly from the dashboard.
Can I try TestingBot before talking to sales?
Yes. You can sign up for a free trial that includes 60 minutes of automated and manual testing on real browsers and devices, with no credit card required.
Where is TestingBot hosted and is my data safe?
TestingBot is built and hosted in the European Union (Belgium). All test data, video recordings and logs stay on EU infrastructure. We are GDPR compliant and a CSA STAR Level 1 registered cloud provider.
Can I schedule a live demo?
Yes. Use the Book a Demo button on this page to pick a 15-minute slot. We will walk you through automated, manual and AI-driven testing on real browsers and devices.
Do you offer enterprise plans, dedicated devices or on-premise?
Yes. We offer enterprise plans with dedicated browsers and devices, single sign-on, custom contracts and a dedicated private cloud option. Mention your needs in the message and our enterprise team will reach out.
---
URL: https://testingbot.com/careers
# Careers
Join our team in maintaining, optimizing and improving our testing cloud.

## Open Positions
- [Technical Content Writer](https://testingbot.com#writer)
## Technical Content Writer
TestingBot is looking for a technical content writer, with experience in Selenium, Cypress, Puppeteer and Playwright.
You will be adding content to our [Blog](https://testingbot.com/blog) and [Resources](https://testingbot.com/resources) section.
### Responsibilities
- Write articles, tutorials and courses about Selenium, Cypress, Puppeteer and Playwright.
- Research interesting topics to write about, related to the online testing ecosystem.
- Add code examples, screenshots and tips & tricks to the articles you write.
### Key Skills
- English writer
- Previous content writing experience
- Faultless grammar and spelling
- IT technical knowledge an advantage, especially if you have knowledge about either of these topics: Selenium, Cypress, Puppeteer or Playwright
- Excellent attention to detail
### Benefits
- Competitive salary
- Flexible schedule
- Work from home (remotely)
Interested? Please send us a brief introduction and resume.
[Apply now](https://testingbot.com/contact/new)
---
URL: https://testingbot.com/terms
# Terms of Service
PLEASE READ CAREFULLY.
THESE TERMS AND CONDITIONS ("THE AGREEMENT") IS A LEGALLY BINDING AGREEMENT BETWEEN YOU ("YOU" OR "YOUR") AND TESTINGBOT. ("TESTINGBOT", "WE" OR "OUR"). BY CHECKING THE APPROPRIATE BOX INDICATING THAT YOU AGREE DURING REGISTRATION OR OTHERWISE ACCESSING THE TESTINGBOT WEBSITE, SERVICES, SOFTWARE OR MATERIALS (COLLECTIVELY THE "SERVICE"), YOU ARE INDICATING THAT YOU HAVE READ THIS AGREEMENT, THAT YOU UNDERSTAND IT, AND THAT YOU CONSENT TO BE BOUND BY ALL OF ITS TERMS AND CONDITIONS. IF YOU DO NOT AGREE WITH ALL OF THE FOLLOWING TERMS AND CONDITIONS, PLEASE DO NOT CONTINUE THE REGISTRATION PROCESS OR USE THE SERVICE.
IF YOU WISH TO USE TESTINGBOT AS AN EMPLOYEE, CONTRACTOR, OR AGENT OF A CORPORATION, PARTNERSHIP OR SIMILAR ENTITY, THEN YOU MUST BE AUTHORIZED TO SIGN FOR AND BIND THE ENTITY IN ORDER TO ACCEPT THE TERMS OF THIS AGREEMENT. THE LICENSES GRANTED AND SERVICES PROVIDED UNDER THIS AGREEMENT ARE EXPRESSLY CONDITIONED UPON ACCEPTANCE BY SUCH AUTHORIZED PERSONNEL.
This End User License Agreement ("Agreement") is entered into by and between TestingBot and the customer (individual or entity) that has procured the TestingBot products or services for use as an end user ("Customer"). "Effective Date" means the date on which accepts the terms and conditions of this Agreement. TestingBot reserves the right, at its sole discretion, to modify or replace this Agreement, or change, suspend, or discontinue any TestingBot Product(s) or Service Descriptions (as those terms are defined below), including without limitation, the availability of any feature, database, or content) at any time by posting a notice on the TestingBot Product(s) or by sending you an email. It is your responsibility to check this Agreement periodically for changes. Your continued use of any TestingBot Product following the posting of any changes to this Agreement constitutes acceptance of those changes.
## 1. Provision of Products
Welcome to TestingBot.com. We provide our customers with online software-as-a-service/platform-as-a-service products.
We use Selenium, an open-sourced browser testing framework to test websites in multiple browsers in an automated way.
We offer several plans according to the needs of the customer. Each plan is provided on a subscription basis for a set term designated at the time of purchase (each, as “Subscription Term”). Customer shall purchase and TestingBot shall provide the specific product and plan as specified at the time of purchase.
Some elements of the Service are available to any User, while other elements ("Subscription Services") are only available to Users who have purchased a subscription from TestingBot according to these terms and conditions. TestingBot may at any time, in its sole discretion, change a freely available element of the Service to a Subscription Service, and vice versa. TestingBot may, it its sole discretion, offer Users a one-time, free trial period during which you may use Subscription Service(s) for free. The time period of any such free trial period may vary from time to time, and will be indicated at the time you register as a User.
Please note that TestingBot does not provide any internet connectivity services, nor does TestingBot provide any computer equipment for Users. You are entirely responsible for securing and paying for any such services and equipment, and for ensuring that such services and equipment meet the minimum requirements for the use of the Services.
General Restrictions; Acceptable Use. Customer shall not: (a) rent, lease, copy, provide access to or sublicense any TestingBot Product to a third party; (b) use any TestingBot Product to provide or incorporate any TestingBot Product into any product or service provided to a third party, (c) reverse engineer, decompile, disassemble, or otherwise seek to obtain the source code or non-public APIs to TestingBot Products, including the Video Playback, except to the extent such restriction is prohibited by applicable law (and only upon advance notice to TestingBot), (d) modify any TestingBot Product or any Documentation, or create any derivative product from any of the foregoing, (e) remove or obscure any proprietary or other notices contained in any TestingBot Product (including any reports or data printed from TestingBot Products) or (f) publicly disseminate information regarding the performance of TestingBot Products.
## 2. Accounts/Customer Data
You may establish one (1) User account (the "Account") per email address you supply to TestingBot. You may not share the Account with anyone. You are liable for all uses of the Account.
If you would like to have several accounts for co-workers or friends, please consider using a sub-account.
Storage of Customer Data. TestingBot does not provide an archiving service. TestingBot agrees only that it shall not intentionally delete any Customer Data which is less than 30 days old from TestingBot Products prior to termination of Customer’s applicable Subscription except as directed by Customer or its Permitted Users. TestingBot expressly disclaims all other obligations with respect to storage. If specified at the time of purchase, TestingBot may charge Customer a fee for storing the Customer Data.
## 3. Registration
During the registration process prior to using the Service, you may be requested to provide certain information (e.g. your birthdate, gender, zip code, name, e-mail address, country or territory of residence, and for paid services, credit card and billing information, such as your telephone number), and you agree to provide true, accurate, current and complete information about yourself as prompted by such process (such information being "Personal Information"). If you open an Account, and any of your Personal Information changes (e.g., new credit card number, expiration date, new address or email address, new phone number, etc.), you must update that information in your Account.
## 4. Subscription Services
If you elect to subscribe to Subscription Services, the fee therefor will be the price specified at the time of purchase. You hereby agree to pay all charges incurred by the Account, including applicable taxes, in accordance with billing terms for access to the Service that are in effect at the time that the fee or charge becomes payable. Your right to access the Service is subject to any limits established by TestingBot or by your credit card issuer, billing service, or other payment methods authorized by TestingBot. TestingBot may, in its sole and absolute discretion, suspend or terminate your access to the Service and disable or delete the Account, if: (i) payment cannot be charged to your credit or debit card; or (ii) your charge is returned to TestingBot for any reason. If at any time you have a balance due on the Account, you agree that TestingBot can charge these unpaid fees to your credit card or debit them from your debit card, as applicable. You agree to reimburse TestingBot for all costs and expenses incurred by TestingBot in collecting payment due hereunder, including without limitation all bank or service charges, and any reasonable attorneys' fees.
By subscribing to any Subscription Service you are representing to us that you are authorized to use the credit card you submit for payment. You agree to take such steps as are appropriate to ensure such compliance and will indemnify and hold TestingBot and TestingBot's affiliates and distribution and syndication partners harmless from any breach of this Agreement.
Your subscription will last for the period chosen in the subscription process (the "Term"). Unless notifying us of your decision to terminate your subscription, your subscription will automatically be renewed at the end of each Term according to the then-current billing terms. TestingBot reserves the right to change the amount of, or basis for determining, any fees it charges, and to institute new fees, effective immediately upon notice to you. You may cancel your automatic membership renewal by contacting info@testingbot.com . Cancellation of your subscription will take effect upon the end of the then-current Term, at which time you will no longer have access to any Subscription Services.
Our payment provider (Stripe or FastSpring) will automatically renew your paid subscription. Payment details such as your credit card number are not saved on this website.
## 5. TestingBot Materials
The Service may require that you install plugins to report back test results to TestingBot. That software, and all new versions, updates, upgrades, bug fixes, modifications and derivative works that TestingBot may make available from time to time are referred to collectively as the "Software." All Software, designs, text, images, photographs, illustrations, audio clips, video clips, artwork, graphic materials, other copyrightable elements, and the selection and arrangements thereof, and all trademarks, service marks, trade dress, trade names, logos and designs and all intellectual property rights therein, provided by TestingBot via the Service (collectively the "Materials") are the property of TestingBot and/or its business partners, affiliates, assigns, licensors or other respective owners and are protected by intellectual property laws, including without limitation, U.S. and international patent, copyright and trademark laws. TestingBot may include in some Materials or in conjunction with your User Supplied Content third party advertisements that appear or are played when those Materials or User Supplied Content are viewed. You hereby consent to the display and playback of these advertisements in conjunction with your User Supplied Content. To the extent you elect to use or access TestingBot Materials or User-Supplied Content from other Users, you also consent to the display of advertisements in those Materials and that User Supplied Content. Content generated during testing like logs, screenshots and videos will stay available for 30 days. After this period, TestingBot will remove the content.
## 6. License from TestingBot
Subject to all of the terms and conditions of this Agreement, TestingBot hereby grants you a personal, limited, non-exclusive, non-transferable, revocable right to (a) view or play back Materials on, your compatible computer or device.
All rights not expressly granted to you are reserved. For the avoidance of doubt, your use of the Service and the Materials is under license and will terminate if this Agreement terminates, regardless of whether the relevant Service or Materials is free or you have paid for it; you will not obtain any ownership interest in any Materials through this Agreement or otherwise.
## 7. Usage Restrictions
You agree not to use, download, reproduce, modify, create derivative works from, display, perform, publish, distribute, disseminate, transmit, broadcast or circulate any Materials to any third party, except as expressly authorized by TestingBot in connection with the Service. The source code and/or object code to the Materials contains confidential and trade secret information belonging to TestingBot and its licensors, and you shall at all times take reasonable steps to protect the confidentiality of such information. You further agree that you will not disassemble, decompile or reverse engineer the Materials (except to the extent that this restriction is expressly prohibited by law, and then only upon providing TestingBot with prior written notice and opportunity to respond). Any unauthorized or prohibited use may subject the offender to civil liability and criminal prosecution under applicable federal and state laws.
As between you and TestingBot, TestingBot owns all elements of the Account. Except as specifically permitted and enabled within the Service, you may not transfer your Account, or any component of your User-Supplied Content to any other person. Through the use of the Service you may not impair or violate the rights of TestingBot or any other party with respect to trademarks, logos, or other intellectual property or rights. You may not display or perform Materials or User Supplied Content in a manner or in a context that is, in TestingBot's sole discretion, unlawful, misleading, defamatory, infringing, libelous, disparaging, obscene, an endorsement of any unrelated product or service, an unauthorized parody, or otherwise objectionable.
## 8. Code of Conduct
You may not: (i) post any User Supplied Content that is illegal, abusive, deceptive, pornographic, obscene, defamatory, slanderous, offensive, or otherwise inappropriate or harmful to any person (including any minor) or entity; (ii) sell, rent, lease, or otherwise assign any rights to any Materials or Service to any third party; (iii) remove any proprietary notices or labels on the Materials or Service; (iv) use the Materials or Service in conjunction with any device or service designed to circumvent technological measures employed to control access to, or the rights in, any content protected by the copyright laws of any jurisdiction; (v) use the Materials or Service for any commercial or illegal purpose or to harm minors in any way; (vi) use the Materials or Service to invade the privacy of, or obtain personal information about, any User, or to obtain a list of Users, or to access or use any User's User Supplied Content in a manner not specifically authorized by such User; (vii) except as expressly permitted by TestingBot, copy, modify, erase or damage any Materials or information contained on any TestingBot or third party servers; (viii) use the Materials or Service to violate any law or legal right of any third party, including any publicity or privacy right, copyright, other intellectual property right, or any contractual, fiduciary or confidentiality obligation or duty, or to take any action that is harassing, libelous, defamatory, abusive, tortious, threatening, harmful or otherwise objectionable; (ix) use the Materials or Service to post or transmit any unsolicited advertising, campaigning or promotional materials or to send any Materials from an anonymous or false address; (x) access or use any password protected, secure or non-public areas of the Service except as specifically authorized in writing by TestingBot (unauthorized individuals attempting to access these areas of the Service may be subject to prosecution); (xi) distribute any software viruses or other computer code designed to interfere with the functionality of the Service or any computer systems, software or hardware; (xii) use any data mining, robots, or similar data gathering, spoofing, or extraction tools in connection with the Materials Service; (xiii) mislead other Users or third parties as to your identity or to the origin of a message or content; or (xiv) frame or link to the Service or Materials except as expressly permitted in writing by TestingBot.
## 9. Objectionable or Explicit Content
TestingBot has the right, but not the obligation, to monitor, modify and/or remove any Materials and/or User Supplied Content in the Service. TestingBot may monitor, modify and remove any Materials and/or User Supplied Content in its sole discretion. TestingBot is not responsible to you for any content or materials constituting all or part of any Materials, any User Supplied Content, or any other aspect of the Service that you might find objectionable. By viewing the Service, you may be exposed to content that you consider offensive. You take sole responsibility for such exposure.
## 10. Privacy
For information regarding TestingBot's use of information collected in connection with the Service, please refer to the [TestingBot Privacy Policy.](https://testingbot.com/privacy)
## 11. Active Participation; Storage and Bandwidth Limits
If you do not have a currently active subscription to the Subscription Service, a condition of your access to the Service is your "Active Participation" in the Service. Active Participation is defined as accessing the Service, including displaying Materials or sending User Supplied Content through the Service, at least once every 180 days. If you do not maintain Active Participation, TestingBot may, with or without providing you with notice, terminate your Account (or any part thereof) and/or your use of the Service, and may remove and discard any content, including any of your User Supplied Content and any related Materials within the Service. Additionally, with or without providing you with notice, we may place a maximum limit on the amount of User Supplied Content that you are allowed to upload into the Service, or the maximum bandwidth that you may utilize each day. We may change these limits in our sole discretion upon providing you with notice. If you exceed these limits, we may refuse to accept and/or remove any User Supplied Content, or we may suspend certain functions of the Service for your Account.
## 12. Changes to the Agreement
We reserve the right, at our sole discretion, to change, modify or otherwise alter these terms and conditions at any time. Such changes and/or modifications shall become effective immediately upon posting. Please review the terms and conditions periodically. If you do not agree to be bound by (or cannot comply with) the Agreement as amended, your only remedy is to cancel your TestingBot Account and to cease using the Service. You will be deemed to have accepted the Agreement as amended thirty (30) days after the amended agreement is posted if you continue to use the Service. All other terms of this Agreement will continue in effect.
## 13. Notices
TestingBot may provide you with any notices under this Agreement by means of a posting on the Service, by e-mail, or by sending a message to you through the Service.
A. A physical or electronic signature of the owner (or person authorized to act on behalf of the owner) of an exclusive right that is allegedly infringed.
B. Specific identification of the copyrighted work claimed to have been infringed, or if multiple copyrighted works are covered by a single notification, a list of each copyrighted work claimed to have been infringed.
C. Information related to the work(s) reasonably sufficient for TestingBot to promptly locate the work (e.g. title of work, location within the Service, etc.).
D. Information reasonably sufficient to permit TestingBot to directly contact the complaining party, such as a complete name and address, telephone number, and an email address.
E. A statement that the complaining party has a good TestingBot belief that use of the work(s) in the manner complained of is not authorized by the copyright owner, its agent, or the law.
F. A statement requesting that TestingBot take a specific act with respect to the alleged infringement (e.g., removal, access restricted or disabled), and
G. A statement that the information in the notification is accurate, and under penalty of perjury, that the complaining party is authorized to act on behalf of the owner of an exclusive right that is allegedly infringed.
## 14. Trademarks
Except as expressly set forth in this Agreement, all trademarks, service marks, trade names, domain names, slogans, logos, and other indicia of origin that appear on or in connection with the Service and Materials are the property of TestingBot and/or its affiliates and licensors. You may not copy, display or use any of these marks without prior written permission of the mark owner.
## 15. International
You agree to abide by U.S and other applicable export control laws and not to transfer, by electronic transmission, the Service, or otherwise, any content or software subject to restrictions under such laws to a national destination or person prohibited under such laws.
## 16. Promotions and Advertising
TestingBot and/or its business partners may present advertisements and promotional materials on or through the Service. Your participation in any TestingBot promotional event is subject to the terms and conditions associated with that event. Your correspondence or business dealings with, or participation in promotions of, any third-party advertisers on or through the Service, including payment and delivery of related goods or services, and any other terms, conditions, warranties or representations associated with such dealings, are solely between you and such third-party. You agree that neither TestingBot nor its business partners shall be responsible or liable for any loss or damage of any sort incurred as the result of any such dealings or as the result of the presence of such third parties on the Service.
## 17. The Website and Links to Third Party Sites
The Website means this web site, as accessed by any of the following domain names: testingbot.com, \*.testingbot.com and any web site owned or controlled by TestingBot (except as otherwise provided on such other site). However, the Website does not refer to any other web site linked to or from the Website, or to any third party site on which the Service may appear. The Service may present links to retailers and/or other third-party websites not owned or operated by TestingBot. Neither TestingBot nor its business partners are responsible for the availability of these outside sites or their contents. You understand and agree that neither TestingBot nor its business partners are responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with your use of or reliance on any content of any such site or goods or services available through any such site or from any such advertiser. You should direct any concerns regarding these third-party sites to those sites' administrators.
## 18. Modifications to Service and Materials
TestingBot may, without liability or obligation, in its sole discretion and at any time: (i) make modifications to, or discontinue, any Materials and/or the Service, including deleting any Downloaded Materials we may have placed on your computer, with or without providing you with notice (except as we may expressly agree to provide you with notice elsewhere in this Agreement); and/or (ii) require you to use modified Materials and/or Service (e.g. Software updates) in order to have continued access to the Service and Materials. The modifications may result in certain Materials being unavailable, or incompatible with certain devices. Other Users may similarly delete or cease offering their User Supplied Materials at any time without notice.
Upon TestingBot giving you notice, you shall promptly cease the use of, and destroy, all copies of any specified Materials, or otherwise modify your usage as directed by TestingBot. You agree that neither TestingBot nor the TestingBot Affiliates shall be liable to you or to any third party for any modification, suspension or discontinuance of the Service or Materials.
## 19. Remedies
You understand and agree that any unauthorized use of the Service or the Materials would result in irreparable injury to TestingBot and/or the TestingBot Affiliates for which money damages would be inadequate, and in such event TestingBot and the TestingBot Affiliates, shall have the right, in addition to other remedies available at law and in equity, to immediate injunctive relief against you. Nothing contained in this paragraph or elsewhere in this Agreement shall be construed to limit remedies or relief available pursuant to statutory or other claims that TestingBot and the TestingBot Affiliates may have under separate legal authority, including but not limited to, any claim for intellectual property infringement. You understand and agree that your cancellation of your Account is your sole right and remedy with respect to any dispute with TestingBot. You can cancel your Account by delivering notice to TestingBot at info@testingbot.com.
## 20. Indemnity
You agree to indemnify, and hold harmless, TestingBot, the TestingBot Affiliates, and each of their respective officers, directors, employees, agents, licensors, representatives, and third party providers from and against all claims, actions, demands, causes of action, and other proceedings, and all losses, expenses, fees, fines, damages and costs, including reasonable attorneys' fees and court costs, arising out of or relating to your breach of this Agreement or improper use of the Service or Materials. TestingBot reserves the right to assume, at its sole expense, the exclusive defense and control of any matter subject to indemnification by you, in which event you will fully cooperate with TestingBot in asserting any available defenses.
## 21. Suspension or Termination
TestingBot may, in its sole discretion, terminate this Agreement or suspend your Account at any time with or without notice to you in the event that you breach (or TestingBot reasonably suspects that you have breached) any provision of this Agreement. If TestingBot terminates this Agreement, or suspends your Account for any of the reasons set forth in this paragraph, TestingBot shall have no liability or responsibility to you, and you agree that you forfeit any amounts you have paid TestingBot. TestingBot may terminate this Agreement and your Account for any reason or no reason at the end of the then-current Term by providing notice to you.
You may cancel your Account (including Subscription Services) at any time by contacting info@testingbot.com. All fees paid and charges made prior to termination as provided herein are nonrefundable, including any advance charge or payment for the subscription term during which you terminate your subscription. Termination of your subscription shall not relieve you of any obligations to pay accrued charges.
Upon termination of this Agreement for any reason, all of your rights to use the Service and materials shall immediately terminate, and you shall immediately cease using and destroy all Materials.
## 22. Disclaimers
You understand and agree that your use of the Service and the Materials is at your own sole risk. TestingBot is not responsible or liable for any infections or contamination of your system, or delays, inaccuracies, errors, or omissions arising out of your use of the Service or Materials. The entire risk as to the quality, accuracy, adequacy, completeness, correctness and validity of any Materials rests with you. THE SERVICE AND THE MATERIALS ARE PROVIDED "AS IS" AND "AS AVAILABLE," AND WITHOUT WARRANTY BY TESTINGBOT AND THE TESTINGBOT AFFILIATES, AND, TO THE MAXIMUM EXTENT ALLOWED BY APPLICABLE LAW, TESTINGBOT AND THE TESTINGBOT AFFILIATES EXPRESSLY DISCLAIM ALL WARRANTIES, EXPRESS AND IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND ANY WARRANTY OF NONINFRINGEMENT. THERE IS NO WARRANTY THAT THE SERVICE OR THE MATERIALS WILL MEET YOUR REQUIREMENTS, OR THAT YOUR ACCESS TO THE SAME WILL BE UNINTERRUPTED OR ERROR-FREE. TESTINGBOT AND THE TestingBot AFFILIATES DO NOT WARRANT, GUARANTEE, OR MAKE ANY REPRESENTATIONS REGARDING THE USE OR THE RESULTS OF THE USE OF THE SERVICE OR THE MATERIALS WITH RESPECT TO PERFORMANCE, ACCURACY, RELIABILITY, SECURITY CAPABILITY, CURRENTNESS OR OTHERWISE. YOU WILL NOT HOLD TESTINGBOT AND/OR THE TESTINGBOT AFFILIATES RESPONSIBLE FOR ANY DAMAGES THAT RESULT FROM YOU ACCESSING THE SERVICE OR USING THE SERVICE AND/OR THE MATERIALS. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY ANY PERSON SHALL CREATE A WARRANTY IN ANY WAY WHATSOEVER RELATING TO TESTINGBOT AND/OR THE TESTINGBOT AFFILIATES.
UNDER NO CIRCUMSTANCES SHALL TESTINGBOT AND/OR THE TESTINGBOT AFFILIATES BE LIABLE FOR ANY UNAUTHORIZED USE OF THE SERVICE AND/OR THE MATERIALS.
## 23. LIMITATION OF LIABILITY
TO THE FULLEST EXTENT PERMISSIBLE PURSUANT TO APPLICABLE LAW, UNDER NO CIRCUMSTANCES SHALL TESTINGBOT AND/OR THE TESTINGBOT AFFILIATES BE LIABLE TO YOU FOR ANY CONSEQUENTIAL, INCIDENTAL, INDIRECT, SPECIAL OR SIMILAR DAMAGES (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, AND THE LIKE) ARISING OUT OF THE USE OR INABILITY TO USE THE SERVICE AND/OR THE MATERIALS, EVEN IF TESTINGBOT AND/OR THE TESTINGBOT AFFILIATES, HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU. TO THE EXTENT THAT IN A PARTICULAR CIRCUMSTANCE ANY DISCLAIMER OR LIMITATION ON DAMAGES OR LIABILITY SET FORTH HEREIN IS PROHIBITED BY APPLICABLE LAW, THEN, INSTEAD OF THE PROVISIONS HEREOF IN SUCH PARTICULAR CIRCUMSTANCE, TESTINGBOT AND/OR THE TESTINGBOT AFFILIATES, SHALL BE ENTITLED TO THE MAXIMUM DISCLAIMERS AND/OR LIMITATIONS ON DAMAGES AND LIABILITY AVAILABLE AT LAW OR IN EQUITY BY SUCH APPLICABLE LAW IN SUCH PARTICULAR CIRCUMSTANCE, AND IN NO EVENT SHALL SUCH DAMAGES OR LIABILITY EXCEED TWENTY FIVE DOLLARS ($25). Notwithstanding the foregoing, in no event shall TestingBot's liability to you for any and all claims, damages, losses, and causes of action (whether in contract, tort or otherwise) exceed the amount paid by you, if any, for accessing the Service.
## 24. Claims and Arbitration
Any action hereunder by a User must be brought, if at all, within one (1) year from the accrual of the cause of action. You agree that the all disputes and claims arising out of, or relating to, this Agreement, the Service or Materials, your use of the Service or Materials (including the arbitrability of any claim or dispute and the enforceability of this paragraph), or to any other alleged act or omission by you or TestingBot, shall be determined exclusively by final and binding arbitration. Provided however, that you agree that the terms of this paragraph do not apply to any breach or alleged breach of the ownership or license rights of TestingBot or any of its licensors with respect to any Materials and/or Service, or your (or any third party's) misuse of the Materials and/or Service, and that, in the event of any such breach of alleged breach, TestingBot and its licensors will be irreparably harmed and entitled to equitable and injunctive relief in addition to all other remedies provided by this Agreement or available at law. Except as expressly set forth above in this paragraph, you and TestingBot may litigate in court only to compel arbitration under this Agreement or to confirm, modify, vacate or enter judgment on the award rendered by the arbitrators. To the extent that you have breached or have indicated your intention to breach this Agreement in any manner which violates or may violate TestingBot's or any of its licensor's intellectual property rights, or may cause continuing or irreparable harm to TestingBot (including, but not limited to, any breach that may impact TestingBot's or its licensor's intellectual property rights, or a breach by reverse engineering), TestingBot may seek injunctive relief, or any other appropriate relief, in any court of competent jurisdiction. To the fullest extent permitted by applicable law: no arbitration under this Agreement shall be joined to an arbitration involving any other current or former licensee of TestingBot, whether through class arbitration proceedings or otherwise; no finding or stipulation of fact in any other arbitration, judicial or similar proceeding may be given preclusive or collateral estoppel effect in any arbitration hereunder (unless determined in another proceeding between you and TestingBot); and no conclusion of law in any other arbitration may be given any weight in any arbitration hereunder (unless determined in another proceeding between you and TestingBot).
## 25. Miscellaneous
This Agreement and any other terms or documents referred to herein represent your entire agreement with TestingBot with respect to your use of the Service and Materials. As used in this Agreement the term "licensor" shall not refer to the person at that time entering into the Agreement. You understand and agree that, except as expressly set forth herein, this Agreement is not intended to confer and does not confer any rights or remedies upon any person other than the parties to this Agreement. If any part of this Agreement is held invalid or unenforceable, that portion shall be construed in a manner consistent with applicable law to reflect, as nearly as possible, the original intentions of the parties, and the remaining portions shall remain in full force and effect. TestingBot's failure to pursue any available claim or defense pursuant to this Agreement or otherwise will not be a waiver of such claim or defense. The headings used in this Agreement are for convenience only, and will have no effect on the interpretation or legal enforceability of the terms herein. This Agreement is personal to you and may not be transferred, assigned or delegated to anyone. Any attempt by you to assign, transfer or delegate this Agreement shall be null and void.
Last updated: May 9, 2017
---
URL: https://testingbot.com/privacy
# Privacy Policy
TestingBot knows that you care how information about you is used and shared, and we appreciate your trust that we will do so carefully and sensibly.
By visiting testingbot.com, you are accepting the practices described in this Privacy Policy.
TestingBot knows that you care how information about you is used and shared, and we appreciate your trust that we will do so carefully and sensibly. We process personal information contained in Customer Data on behalf of our customers under the terms and conditions of our [TestingBot Customer Data Processing Addendum](https://trust.testingbot.com).
- [What's your policy about Cookies?](https://testingbot.com#cookies)
- [What personal information does TestingBot gather?](https://testingbot.com#personal)
- [Which information do you share?](https://testingbot.com#information)
- [What Data Subject Rights Do I Have?](https://testingbot.com#rights)
- [How Long Do We Retain Your Personal Data?](https://testingbot.com#duration)
- [Is my information safe on this website?](https://testingbot.com#secure)
- [Which information can I access?](https://testingbot.com#access)
- [Are children allowed to use TestingBot?](https://testingbot.com#children)
- [How can I remove all my information from TestingBot?](https://testingbot.com#delete)
- [Who is the Data Controller?](https://testingbot.com#controller)
- [What Are Our Legal Bases for Processing Personal Data?](https://testingbot.com#legal)
- [Conditions of use, notices, and revisions](https://testingbot.com#changes)
- [Implemented Technologies](https://testingbot.com#technologies)
## What's your policy about Cookies?
Please consult our [Cookie Policy](https://testingbot.com/cookie-policy).
## What personal information does TestingBot gather?
The information we learn from customers helps us personalize and continually improve your experience at testingbot.com.
During signup, you are asked for your first name and last name, together with an e-mail address.
When upgrading your plan, you may be asked for a telephone number and address by our billing provider.
Information from Other Sources: We might receive information about you from other sources and add it to our account information.
You are not required to provide any personal data to us, but if you do not provide any personal data to us, you may not be able to use certain features of our Services...
## Which information do you share?
- **Subcontractors, sub-processors and service providers:** We share your firstname, lastname and email address with our payment providers Stripe and PayPal and with our online chat widget Drift.
- A third party, in order to enforce or defend our rights, or to address financial or reputational risks.
- A third party to respond to requests relating to a criminal investigation or alleged or suspected illegal activity.
- Other recipients where we are authorised or required by law to do so.
- A purchaser or prospective purchaser of all or part of our assets or our business...
## What Data Subject Rights Do I Have?
- **Right of access:** You have the right to obtain from us confirmation as to whether your personal data is being processed...
- **Right to rectification:** You have the right to obtain from us the rectification of inaccurate personal data...
- **Right to erasure:** You have the right to ask us to erase your personal data...
- **Right to restriction of processing:** You have the right to request restriction of processing...
- **Right to data portability:** You have the right to receive your personal data in a structured, commonly used...
- **Right to object:** You have the right to object, on grounds relating to your particular situation...
- **Right to Submit Complaints:** You have a right to lodge a complaint with a supervisory authority.
## How Long Do We Retain Your Personal Data?
If you register for an account on our Services, we retain your personal data for as long as you have an account with us...
## Is my information safe on this website?
TestingBot uses SSL (HTTPs) on all parts of its service/website. SSL encrypts information between you and our website.
Payment data is handled by our payment provider (Stripe/PayPal)...
All sensitive data is encrypted in our database and in our log files. Passwords and security tokens are stripped before they are logged into our system.
We will notify serious data breaches...
We restrict access to Account-Related Information and other Personal Information...
## Are children allowed to use TestingBot?
We do not sell products for purchase by children. If you are under 18, you may use TestingBot only with the involvement of a parent or guardian.
## Which information can I access?
You can access and modify your personal information in our member area.
## How can I remove all my information from TestingBot?
Please log into our member area, click "Account" and click the "Remove account" button.
## Who is the Data Controller?
If you are using our Services, the data controller is TestingBot - Belgium, +1 (855) 410-0024...
## What Are Our Legal Bases for Processing Personal Data?
- **Contract Performance:** We process the personal data of users of our Services as necessary to perform our contractual obligations...
- **Legitimate Interests:** To provide users with a good user experience, to maintain and secure our Services...
- **Legal Obligations:** If we are subject to a lawful access request, engaged in a legal proceeding...
- **Consent:** If we are required to obtain your consent to send you marketing communications...
- **Vital Interests:** In extenuating circumstances, we may need to process your personal data to protect the vital interests...
## Conditions of use, notices, and revisions
If you choose to visit testingbot.com, your visit and any dispute over privacy is subject to this Notice...
We may periodically alter this policy and our Terms of service. Please check these pages frequently to see the most recent changes.
**Last updated: April 9, 2024**
## Implemented Technologies
If you have any questions, complaints or remarks about this Privacy Policy, you can contact our Data Protection Officer at [info@testingbot.com](mailto:info@testingbot.com).
TestingBot
Attn: Data Protection Officer
[info@testingbot.com](mailto:info@testingbot.com)
Telephone number: +1 (855) 410-0024
[Contact TestingBot](https://testingbot.com/contact/new)
---
URL: https://testingbot.com/cookie-policy
# Cookie Policy
TestingBot uses cookies to improve your user experience.
## What's a cookie?
A cookie is a small file which is placed on your computer to remember your preferences.
You can read more about cookies on [AllAboutCookies](https://allaboutcookies.org).
## How are you using cookies?
When you first visit our website, our partner Google Analytics will set a cookie to track which pages you visit.
Next to that, we will set a cookie to determine if you're logged in or not (session cookie). Once you log out, this cookie is destroyed.
We offer online chat as a way to help you with your questions. This 3rd party provider also sets cookies to enhance your user experience.
## Cookie List
Below is the list of cookies we use on TestingBot:
### Strictly Necessary Cookies
These cookies are necessary for the website to function and cannot be switched off in our systems...
#### Cookies used
| Cookie Name | Description | Expiration |
| --- | --- | --- |
| \_testingbot\_gsession | Keep track of whether you're signed in on TestingBot | Until you close your browser |
| remember\_user\_token | If you enabled "Remember me" during login, we will use this cookie to remember your session. | 14 days |
| \_\_stripe\_mid | Used by Stripe. More info on [Stripe's Cookie Policy](https://stripe.com/cookies-policy/legal). | 1 year |
### Performance Cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site...
#### Cookies used
| Cookie Name | Description | Expiration |
| --- | --- | --- |
| \_gid | Used by Google Analytics. [More info](https://www.en.advertisercommunity.com/t5/Google-Analytics-Code/Information-about-a-cookie-of-Google-Analytics-named-gid/td-p/1127468). | 30 days |
| \_ga | Used by Google Analytics to identify unique sessions. | 30 days |
### Targeting Cookies
These cookies may be set through our site by our advertising partners...
#### Cookies used
| Domain | Cookies |
| --- | --- |
| youtube.com |
- SID
- HSID
- demographics
- VISITOR\_INFO1\_LIVE
- PREF
- APISID
- SSID
- LOGIN\_INFO
- YSC
- SAPISID
|
| google.com |
- APISID
- SSID
- NID
- PREF
- SID
- SAPISID
- HSID
|
### Social Media Cookies
These cookies are set by a range of social media services that we have added to the site...
#### Cookies used
| Cookie Name | Description | Expiration |
| --- | --- | --- |
| intercom-id-[app\_id] | Anonymous visitor identifier for the online chat functionality. | 9 months |
| intercom-session-[app\_id] | Keeps track of chat sessions. Gives users access to their previous conversations and identifies them in the Messenger. | 1 week |
| intercom-device-id-[app\_id] | Identifier for each unique device that interacts with the Messenger. | 9 months |
This policy was last updated on October 8, 2025.
Please see our [Terms & Conditions](https://testingbot.com/terms) and our [Privacy Policy](https://testingbot.com/privacy) for additional information.
---
URL: https://testingbot.com/features/smart-tv-testing
# Online Test Automation On Smart TVs
OTT test automation on living room devices such as Apple TV, Amazon Fire TV and Chromecast with TestingBot. Manual and automated crossplatform testing on real Apple TV (tvOS) and Android TV devices.
[Start free](https://testingbot.com/users/sign_up)[Request a demo](https://testingbot.com/demo)

Trusted by some of the world's most innovative companies
## Automated Testing for Smart TV apps
Run Appium tests on TV apps running on Apple TV 4K, Chromecast and more.
Get access to raw video and audio generated from the Smart TV device. Test DRM-protected content, by accessing a device via manual or automated testing.
[Read SmartTV Testing Documentation](https://testingbot.com/support/app-automate/smart-tv)
### Automated Testing with Appium
Upload your Smart TV app and run automated tests with Appium on the remote device.
### Connect to a remote TV device
Access live video and audio generated from the device, through your own browser and control the remote as if it was in your hand.
### Device Coverage
Test on various devices such as Apple TV 4K, Chromecast and more.
## Frequently Asked Questions
### What competitive edge does TestingBot offer with Smart TV testing, compared to its peers?
TestingBot offers both manual and automated testing of apps on Smart TVs. View DRM-protected content, hear live audio of the device under test and manually send remote control (IR signals) to the device.
### How can I test Smart TV applications on a remote Smart TV device?
Simply upload your app to TestingBot Storage and pick a device to test on. TestingBot provides a high quality video and audio stream of the device.
### Manual and Automated Testing on AppleTV and Chromecast
Manually control a device such as Apple TV 4K or ChromeCast device, through your own browser. Run automated tests with Appium on remote smart-tv devices.
### Sign up for a Free Trial
Start testing your apps with TestingBot.
[Start a free trial](https://testingbot.com/users/sign_up)
---
URL: https://testingbot.com/features/manual-browser-testing
Live cross-browser testing
# Real browsers, streamed to your screen
Take control of any real Chrome, Safari, Firefox, Edge or Internet Explorer running on real macOS, Windows or Linux. No VM, no plugin, no install. Open a tab, click a browser, start testing in seconds.
[Start free](https://testingbot.com/users/sign_up)[Request a demo](https://testingbot.com/demo)
Combinations6100+
Platforms5
Uptime99.99%
macOS Tahoe · Safari
testingbot.com/demo
Live
Real browsers,
streamed to your tab
Buy now 96 × 20Pricing
ElementsConsoleSourcesNetwork0 err
\Real...\
\
\
Test real Edge...
\
\
` | Geographic IP location (ISO country code, e.g., "US", "DE") |
### Output Options
| CLI Option | Description |
| --- | --- |
| `--async` | Start tests and exit without waiting for results |
| `-q, --quiet` | Suppress progress output |
| `--report ` | Download report after completion: html or junit |
| `--report-output-dir ` | Directory to save reports |
| `--download-artifacts` | Download test artifacts (logs, screenshots, video) |
| `--artifacts-output-dir ` | Directory to save artifacts zip |
### Tunnel Options
| CLI Option | Description |
| --- | --- |
| `-t, --tunnel` | Start a [TestingBot Tunnel](https://testingbot.com/support/tunnel) for this test run (cannot be combined with `--async`) |
| `--tunnel-identifier ` | Identifier for the tunnel, allowing multiple tunnels in parallel |
### Advanced Options
| CLI Option | Description |
| --- | --- |
| `--ignore-checksum-check` | Skip checksum verification and always upload the app |
| `--shard-split` | Split flows into N parallel sessions for faster execution. More information in the [Shard Splitting](https://testingbot.com#shard-split) section. |
## Frequently asked questions
How do I pin a specific Maestro version on TestingBot?
Pass the `maestroVersion` option in your run configuration. See the [Set Maestro Version section](https://testingbot.com#version) for the list of supported versions and CLI / API examples.
How do I pass environment variables to Maestro flows?
Use the `env` option to inject environment variables into the Maestro run. The flows can then read them with the `${VARIABLE_NAME}` syntax inside the YAML. See the [Environment Variables section](https://testingbot.com#environment) for examples.
Can I run only a subset of my Maestro flows?
Yes. Use `flows` to whitelist specific files, or `includeTags` and `excludeTags` to filter by Maestro tag. See the [Select Specific Flows](https://testingbot.com#flows), [Include Tags](https://testingbot.com#include-tags) and [Exclude Tags](https://testingbot.com#exclude-tags) sections.
How do I split Maestro flows into parallel shards?
Pass `shardSplit` with the number of parallel sessions you want. Each shard runs a subset of the flows on its own device session. See the [Shard Split section](https://testingbot.com#shard-split) for details.
Can I change the device timezone, locale or geolocation for Maestro tests?
Yes. Use the `timezone`, `locale` and `geolocation` options to control the device environment. See the [Device Timezone](https://testingbot.com#timezone), [Device Locale](https://testingbot.com#locale) and [IP Geolocation](https://testingbot.com#geolocation) sections.
Can I test a private or staging server with Maestro on TestingBot?
Yes. Start the [TestingBot Tunnel](https://testingbot.com/support/tunnel) and pass `tunnelIdentifier` in your Maestro run config. The device will route traffic through your tunnel so it can reach internal APIs. See the [TestingBot Tunnel section](https://testingbot.com#tunnel).
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/maestro/ci-cd
# CI/CD Integration
The [TestingBot CLI](https://testingbot.com/support/app-automate/maestro#cli) integrates seamlessly with any CI/CD environment. It runs all your Maestro flows, streams the output in real-time and exits with the appropriate exit code for pipeline automation.
We recommend using `npx` to run the CLI without installing it globally. This ensures you're always using the latest version.
## Quick Start
Run Maestro tests in any CI/CD service with a single command:
npx --yes @testingbot/cli@latest maestro app.apk ./flows \
--device "Pixel 8" \
--deviceVersion "14" \
--api-key $TB_KEY \
--api-secret $TB_SECRET
This command will:
- Upload your mobile app (`.apk`, `.aab`, `.ipa` or `.app`)
- Upload and run all Maestro flow files from the `./flows` directory
- Stream test progress to your CI logs
- Exit with code `0` on success, non-zero on failure
You can pass credentials as arguments (`--api-key` and `--api-secret`) or set them as environment variables. Environment variables are the recommended approach for CI/CD.
## Environment Variables
Instead of passing credentials as command arguments, you can set them as environment variables. This is the most secure approach for CI/CD pipelines since credentials are never exposed in logs or command history.
| Variable | Description |
| --- | --- |
| `TB_KEY` | Your TestingBot API key |
| `TB_SECRET` | Your TestingBot API secret |
When these environment variables are set, you can omit the credential arguments entirely:
export TB_KEY="your_api_key"
export TB_SECRET="your_api_secret"
npx --yes @testingbot/cli@latest maestro app.apk ./flows \
--device "Pixel 8" \
--deviceVersion "14"
Never hardcode credentials in your CI configuration files. Always use your CI provider's secrets management feature to store `TB_KEY` and `TB_SECRET`.
## Exit Codes
The CLI uses standard exit codes for CI/CD integration. Your pipeline will automatically fail when tests fail:
| Exit Code | Description |
| --- | --- |
| `0` | All tests passed successfully |
| `1` | One or more tests failed, or an error occurred |
## Test Reports
Generate test reports for CI/CD result decoration and test analytics. The CLI supports both JUnit XML and HTML report formats.
### JUnit Report
Generate JUnit XML reports for integration with CI/CD test result visualization:
npx --yes @testingbot/cli@latest maestro app.apk ./flows \
--device "Pixel 8" \
--deviceVersion "14" \
--report junit \
--report-output-dir ./test-results
| Option | Description |
| --- | --- |
| `--report ` | Report format: `junit` |
| `--report-output-dir ` | Directory to save test reports (required when `--report` is used) |
JUnit reports can be used with most CI/CD platforms for:
- Test result visualization in your pipeline
- Tracking test trends over time
- Annotating pull requests with test results
- Integration with test management tools
You can also fetch reports via the [REST API](https://testingbot.com/support/app-automate/maestro/api#report) if you need to retrieve them separately.
## CI/CD Metadata
Pass repository and commit information to link test runs with your source control. This enables better traceability and integration with your development workflow.
| Option | Description |
| --- | --- |
| `--commit-sha ` | The commit SHA of this upload |
| `--pull-request-id ` | The ID of the pull request this upload originated from |
| `--repo-name ` | Repository name (e.g., GitHub repo slug) |
| `--repo-owner ` | Repository owner (e.g., GitHub organization or user) |
Example with metadata:
npx --yes @testingbot/cli@latest maestro app.apk ./flows \
--device "Pixel 8" \
--deviceVersion "14" \
--commit-sha "$COMMIT_SHA" \
--pull-request-id "$PR_NUMBER" \
--repo-owner "your-org" \
--repo-name "your-repo"
## View Result Details
A link to the current test results will be printed to your CI logs during execution.
The test details page provides detailed test result information, including:
- Real-time test execution status
- Video recordings of each test run
- Screenshots captured during tests
- Device logs and Maestro output
- Test artifacts for debugging
## GitHub Actions
Add Maestro testing to your GitHub Actions workflow. Store your credentials in **Settings → Secrets and variables → Actions** as repository secrets.
name: Maestro Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
maestro-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Build Android App
run: ./gradlew assembleDebug
- name: Run Maestro Tests
run: |
npx --yes @testingbot/cli@latest maestro \
app/build/outputs/apk/debug/app-debug.apk \
./maestro-flows \
--device "Pixel 8" \
--deviceVersion "14" \
--report junit \
--report-output-dir ./test-results \
--commit-sha ${{ github.sha }} \
--pull-request-id ${{ github.event.pull_request.number }} \
--repo-owner ${{ github.repository_owner }} \
--repo-name ${{ github.event.repository.name }}
env:
TB_KEY: ${{ secrets.TB_KEY }}
TB_SECRET: ${{ secrets.TB_SECRET }}
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: always()
with:
name: maestro-results
path: test-results/
- name: Publish Test Results
uses: dorny/test-reporter@v1
if: always()
with:
name: Maestro Test Results
path: test-results/*.xml
reporter: java-junit
Add `TB_KEY` and `TB_SECRET` as repository secrets in your GitHub repository settings. Never commit credentials to your repository.
## GitLab CI
Add Maestro testing to your `.gitlab-ci.yml`. Store your credentials in **Settings → CI/CD → Variables** and mark them as protected and masked.
stages:
- build
- test
build:
stage: build
image: gradle:8-jdk17
script:
- ./gradlew assembleDebug
artifacts:
paths:
- app/build/outputs/apk/debug/
maestro-tests:
stage: test
image: node:20
dependencies:
- build
script:
- npx --yes @testingbot/cli@latest maestro
app/build/outputs/apk/debug/app-debug.apk
./maestro-flows
--device "Pixel 8"
--deviceVersion "14"
--report junit
--report-output-dir ./test-results
--commit-sha $CI_COMMIT_SHA
--pull-request-id $CI_MERGE_REQUEST_IID
--repo-owner $CI_PROJECT_NAMESPACE
--repo-name $CI_PROJECT_NAME
variables:
TB_KEY: $TB_KEY
TB_SECRET: $TB_SECRET
artifacts:
when: always
paths:
- test-results/
reports:
junit: test-results/*.xml
Add `TB_KEY` and `TB_SECRET` as CI/CD variables in your GitLab project settings. Enable "Mask variable" to prevent them from appearing in job logs.
## Jenkins
Add Maestro testing to your Jenkins pipeline (`Jenkinsfile`). Store your credentials using **Manage Jenkins → Manage Credentials** as secret text.
pipeline {
agent any
environment {
TB_KEY = credentials('testingbot-api-key')
TB_SECRET = credentials('testingbot-api-secret')
}
stages {
stage('Build') {
steps {
sh './gradlew assembleDebug'
}
}
stage('Maestro Tests') {
steps {
sh """
npx --yes @testingbot/cli@latest maestro \\
app/build/outputs/apk/debug/app-debug.apk \\
./maestro-flows \\
--device "Pixel 8" \\
--deviceVersion "14" \\
--report junit \\
--report-output-dir test-results \\
--commit-sha ${env.GIT_COMMIT} \\
--pull-request-id ${env.CHANGE_ID ?: ''} \\
--repo-owner "your-org" \\
--repo-name "your-repo"
"""
}
post {
always {
junit 'test-results/*.xml'
archiveArtifacts artifacts: 'test-results/**', allowEmptyArchive: true
}
}
}
}
}
Create credentials of type "Secret text" in Jenkins for `TB_KEY` and `TB_SECRET`. Reference them using the `credentials()` helper in your pipeline.
## CircleCI
Add Maestro testing to your `.circleci/config.yml`. Store your credentials in **Project Settings → Environment Variables**.
version: 2.1
jobs:
maestro-tests:
docker:
- image: cimg/node:20
steps:
- checkout
- run:
name: Run Maestro Tests
command: |
npx --yes @testingbot/cli@latest maestro \
./app.apk \
./maestro-flows \
--device "Pixel 8" \
--deviceVersion "14" \
--report junit \
--report-output-dir test-results \
--commit-sha $CIRCLE_SHA1 \
--pull-request-id $CIRCLE_PR_NUMBER \
--repo-owner $CIRCLE_PROJECT_USERNAME \
--repo-name $CIRCLE_PROJECT_REPONAME
- store_test_results:
path: test-results
- store_artifacts:
path: test-results
destination: maestro-results
workflows:
test:
jobs:
- maestro-tests
Add `TB_KEY` and `TB_SECRET` as environment variables in your CircleCI project settings. CircleCI automatically masks these values in build output.
## Bitbucket Pipelines
Add Maestro testing to your `bitbucket-pipelines.yml`. Store your credentials in **Repository settings → Pipelines → Repository variables** and enable "Secured".
image: node:20
pipelines:
default:
- step:
name: Build and Test
caches:
- node
script:
- ./gradlew assembleDebug
- npx --yes @testingbot/cli@latest maestro
app/build/outputs/apk/debug/app-debug.apk
./maestro-flows
--device "Pixel 8"
--deviceVersion "14"
--report junit
--report-output-dir test-results
--commit-sha $BITBUCKET_COMMIT
--pull-request-id $BITBUCKET_PR_ID
--repo-owner $BITBUCKET_WORKSPACE
--repo-name $BITBUCKET_REPO_SLUG
artifacts:
- test-results/**
Add `TB_KEY` and `TB_SECRET` as secured repository variables in Bitbucket. Secured variables are encrypted and masked in build logs.
## Frequently asked questions
Which CI/CD systems can run Maestro tests on TestingBot?
Any CI runner that can execute a shell command. The page below has copy-paste-ready examples for [GitHub Actions](https://testingbot.com#github), [GitLab CI](https://testingbot.com#gitlab), [Jenkins](https://testingbot.com#jenkins), [CircleCI](https://testingbot.com#circleci) and [Bitbucket Pipelines](https://testingbot.com#bitbucket). The same TestingBot CLI works on any other CI runner with a shell.
How do I store TestingBot credentials in CI securely?
Set TB\_KEY and TB\_SECRET as encrypted CI secrets. GitHub Actions uses repository or environment secrets, GitLab CI uses protected variables, Jenkins uses credentials, CircleCI uses environment variables in contexts, and Bitbucket uses secured repository variables. Never commit the key or secret in plain text.
What exit codes does the TestingBot CLI return?
Exit code 0 for all flows passing, non-zero when one or more flows fail or when an infrastructure error occurs. See the [Exit Codes section](https://testingbot.com#exitcodes) for the full mapping so you can wire conditional follow-up steps in CI.
How do I publish Maestro test reports in CI?
The CLI writes JUnit-style reports that most CI runners can parse to show per-flow results. See the [Test Reports section](https://testingbot.com#reports) for the file path and configuration in each CI system.
How do I link CI metadata (branch, commit, pull request) to TestingBot test runs?
Pass --repo-owner, --repo-name, --branch, --commit-sha and --pull-request-id flags to the CLI. The values appear in the TestingBot dashboard so you can jump from a CI failure to the exact commit. See the [CI/CD Metadata section](https://testingbot.com#metadata).
---
URL: https://testingbot.com/support/app-automate/espresso
# Espresso Testing
TestingBot provides access to Android physical devices and Android emulators, capable of running your Android Espresso tests.
[Espresso](https://developer.android.com/training/testing/espresso) is a mobile automation framework from Google, designed to run UI tests for Android applications.
Using Espresso in combination with TestingBot's Android cloud service is very straightforward:
- Upload your native mobile Android app, together with the Espresso test apk file. These are created with either Java or Kotlin.
- Specify on which devices you want to run the Espresso tests.
- Retrieve the results of the Espresso tests.
## Getting Started
To get started, please follow the steps below:
1. You will need a `key` and `secret` to connect to the TestingBot grid.
These credentials are available in the [TestingBot member area](https://testingbot.com/members).
2. Espresso tests require a separate `apk` file for testing. So you will need to upload both the app (`.apk` or `.aab` file) and the test file (`.apk`).
You can also use the [TestingBot Espresso Demo App](https://github.com/testingbot/android-espresso-demo-app) to give the Espresso Cloud Testing functionality a try.
## TestingBot CLI
The easiest way to run Espresso tests on TestingBot is using our CLI tool. It handles uploading your app, test APK, and running tests in a single command.
### Installation
npm install -g @testingbot/cli
### Authentication
Authenticate using this command:
testingbot login
This opens your browser for authentication. After logging in, your credentials are saved to `~/.testingbot` and you can start using the CLI.
#### Other Authentication Methods
- **Command-line options** : `--api-key` and `--api-secret`
- **Environment variables** : `TB_KEY` and `TB_SECRET`
- **Config file** : Create `~/.testingbot` with content `key:secret`
### Quick Start
Run Espresso tests with a single command:
testingbot espresso app.apk app-test.apk --device "Pixel 8" --platform-version "14"
testingbot espresso app.apk app-test.apk --device "Galaxy S24" --real-device
The CLI will:
1. Upload your app APK to TestingBot
2. Upload your test APK
3. Start the test run
4. Show real-time progress and results
## Upload Android App
To run Android Espresso tests on TestingBot, you will first have to upload your `.apk` or `.aab` file to TestingBot Storage.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
When using the CLI, the app upload is handled automatically as part of the `testingbot espresso` command. Simply specify your app APK file as the first argument:
testingbot espresso /path/to/app/Application-debug.apk /path/to/test/Application-debug-test.apk --device "Pixel 8"
Alternatively, you can use the `--app` option:
testingbot espresso --app /path/to/app/Application-debug.apk --test-app /path/to/test/Application-debug-test.apk --device "Pixel 8"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/app" \
-F "file=@/path/to/app/file/Application-debug.apk"
The API response will include an `id` response, which you can use in the other API calls.
{
"id": 4012
}
## Upload Espresso Test Suite
You can now upload the `.apk` file containing the Espresso tests.
The upload process is similar to the [app upload](https://testingbot.com#upload) process.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
When using the CLI, the test APK upload is handled automatically. Specify your test APK as the second argument:
testingbot espresso app.apk app-test.apk --device "Pixel 8"
Or use the `--test-app` option:
testingbot espresso --app app.apk --test-app app-test.apk --device "Pixel 8"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/tests" \
-F "file=@/path/to/app/file/Application-debug-test.apk"
Replace the `:id` with the identifier you received during the app upload call.
## Specify devices
You can specify one or more Android device capabilities with your API call.
These capabilities can target either Android emulators, or physical Android devices.
 Android 16.0 › Pixel 9
Loading environments...
Please wait while we load the available browsers and platforms.
We offer special parameters which you can use to allocate a device:
Regex Input | Result || `"Pixel.*"` | This will allocate any available Pixel device (phone) |
| `"*"` | This will allocate a random available Android device |
| `"Pixel [8-9]"` | This will allocate either an Pixel 8 or 9 |
Some Examples:
# find any Samsung Galaxy, except the S23 family
-d '{"capabilities":[{"deviceName":"^Galaxy(?!.*23).*$", "platformName":"Android", "realDevice": true}]}'
# find any device which name starts with Pixel
-d '{"capabilities":[{"deviceName":"Pixel.*", "platformName":"Android", "realDevice": true}]}'
### Tablet Only
You can specify the `tabletOnly` capability when you only want to allocate a tablet device.
### Phone Only
You can specify the `phoneOnly` capability when you only want to allocate a phone device.
-d '{"capabilities":[{"deviceName":"*", "tabletOnly": true, "platformName":"Android", "realDevice": true}]}'
-d '{"capabilities":[{"deviceName":"*", "phoneOnly": true, "platformName":"Android", "realDevice": true}]}'
## Run Espresso Tests
Now that you have uploaded both your Android application and the Espresso test suite, you can run your first Espresso test on the TestingBot cloud.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
Use the CLI to run tests with device selection options:
# Basic usage with device selection
testingbot espresso app.apk app-test.apk --device "Pixel 8" --platform-version "14"
# Real device
testingbot espresso app.apk app-test.apk --device "Galaxy S24" --real-device
# With test name and build identifier
testingbot espresso app.apk app-test.apk \
--device "Pixel 8" \
--platform-version "14" \
--name "Smoke Tests" \
--build "v2.1.0"
### Device Options
| Option | Description |
| --- | --- |
| `--device ` | Device name (e.g., "Pixel 8", "Samsung.\*") |
| `--platform-version ` | Android version (e.g., "12", "13", "14") |
| `--real-device` | Use a real device instead of emulator |
| `--tablet-only` | Only allocate tablet devices |
| `--phone-only` | Only allocate phone devices |
#### Test Filtering
# Run specific test classes
testingbot espresso app.apk app-test.apk \
--device "Pixel 8" \
--class "com.example.LoginTest,com.example.HomeTest"
# Run tests with specific annotations
testingbot espresso app.apk app-test.apk \
--device "Pixel 8" \
--annotation "com.example.SmokeTest" \
--size "small,medium"
# Exclude specific test classes
testingbot espresso app.apk app-test.apk \
--device "Pixel 8" \
--not-class "com.example.SlowTest"
#### Test Filtering Options
| Option | Description |
| --- | --- |
| `--class ` | Run tests in specific classes (comma-separated fully qualified names) |
| `--not-class ` | Exclude tests in specific classes |
| `--package ` | Run tests in specific packages (comma-separated) |
| `--not-package ` | Exclude tests in specific packages |
| `--annotation ` | Run tests with specific annotations (comma-separated) |
| `--not-annotation ` | Exclude tests with specific annotations |
| `--size ` | Run tests by size: small, medium, large (comma-separated) |
#### Network & Location
# With network throttling and geolocation
testingbot espresso app.apk app-test.apk \
--device "Pixel 8" \
--throttle-network "3G" \
--geo-location "DE" \
--language "de"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{"capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
Replace the `:id` with the identifier you received during the app upload call.
The API response will include a `success` response, indicating if the tests will start or if an error occurred.
{
"success": true
}
## View Espresso Test Results
You will now see a test appear in the [TestingBot dashboard](https://testingbot.com/members), together with a video, test logs and other metadata.
You can also use the CLI or an API call to get the results from the run(s):
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
By default, the CLI waits for test completion and displays real-time progress:
testingbot espresso app.apk app-test.apk --device "Pixel 8"
The CLI will show:
- Upload progress for app and test APK
- Device allocation status
- Live output from Espresso tests
- Final pass/fail status
### Async Mode
Use `--async` to start tests without waiting for results:
testingbot espresso app.apk app-test.apk --device "Pixel 8" --async
#### Download JUnit Report
Download the JUnit report after test completion:
testingbot espresso app.apk app-test.apk --device "Pixel 8" \
--report junit \
--report-output-dir ./reports
curl -u api_key:api_secret \
"https://api.testingbot.com/v1/app-automate/espresso/:id"
Replace the `:id` with the identifier you received during the app upload call.
The API response will include a response indicating the current state of the Espresso test results.
{
"runs": [
{
"status": "DONE",
"capabilities": {
"version": "12",
"deviceName": "Pixel 6",
"platformName": "Android"
},
"success": false,
"test": {
"sessionId": "30f642d082c3-804f3c0ec2df-a18b2d431ffb-169920992163-48455547",
"environment": {
"name": "chrome",
"os": "Pixel 6 - 12.0",
"version": "12.0"
}
}
}
],
"version": "1.0"
}
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/espresso/instrumentation
# Espresso Instrumentation Logs
Instrumentation logs are logs generated by Android Espresso. These logs list all the steps that were performed during the test, with any errors or meta data available from the Android Espresso test runner (usually `AndroidJUnitRunner`).
These logs are available in the [TestingBot dashboard](https://testingbot.com/members) as well as through the [REST API](https://testingbot.com/support/api).
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/espresso/filter
# Filter Espresso Test Cases
By default, TestingBot will execute all tests present in the Espresso `.apk`.
The Espresso test runner (which is usually **AndroidJUnitRunner** ) provides multiple options to filter test cases for execution.
You might want to use these filter options for specific use cases, such as:
- Run only specific tests for a bugfix.
- Only run a subset of tests, for quick smoke testing. Instead of waiting on all your Espresso tests.
- Run specific tests for specific environments (production, staging, ...)
Below you'll find the different filter options that TestingBot and Espresso provide.
- [class](https://testingbot.com#class): will run all tests in a specific class
- [notClass](https://testingbot.com#notClass): will run all tests except the tests in a particular class
- [size](https://testingbot.com#size): will run a specific test size annotated with `SmallTest`, `MediumTest` or `LargeTest`
- [package](https://testingbot.com#package): will run all tests in a specific Java package
- [notPackage](https://testingbot.com#notPackage): will run all tests except the tests in a specific package
- [annotation](https://testingbot.com#annotation): will run all tests with a specific annotation
- [notAnnotation](https://testingbot.com#notAnnotation): will run all tests except the tests with a specific annotation
- [numShards](https://testingbot.com#shards): filter test run to a shard of all tests, where numShards is an integer greater than 0 and shardIndex is an integer between 0 (inclusive) and numShards (exclusive)
## class Filter
To only run Espresso tests for a specific `class`, you can use the class option.
You can pass a list of fully qualified Java class names. See the example below.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--class "com.android.app.one,com.android.app.two"
This will start an Espresso test session on the specified device and only run tests for the classes specified.
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "class": ["com.android.app.one", "com.android.app.two"] }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
This call with start an Espresso test Session on the specific device and only run the Espresso tests for the classNames specified.
## notClass Filter
Use the `notClass` option to run all Espresso tests except for the tests in the classes specified with this option.
You can pass a list of fully qualified Java class names. See the example below.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--not-class "com.android.app.one,com.android.app.two"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "notClass": ["com.android.app.one", "com.android.app.two"] }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
## size Filter
The `size` option allows you to specify which tests you want to run, depending on the annotated size: `SmallTest`, `MediumTest` or `LargeTest`
You can pass an array of sizes, see below for an example.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--size "small,medium"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "size": ["small", "medium"] }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
## package Filter
The `package` filter allows you to specify for which package the Espresso tests should run.
You can pass an array of packages, see below for an example.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--package "com.android.one,com.android.two"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "package": ["com.android.one", "com.android.two"] }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
## notPackage Filter
The `notPackage` filter allows you to indicate that Espresso tests should run for everything except for the packages defined.
You can pass an array of packages, see below for an example.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--not-package "com.android.one,com.android.two"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "notPackage": ["com.android.one", "com.android.two"] }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
## annotation Filter
The `annotation` filter will run tests annotated with the field (or fields) you specified.
You can pass an array of annotations, see below for an example.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--annotation "com.android.foo.MyAnnotation,com.android.foo.MyAnnotationTwo"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "annotation": ["com.android.foo.MyAnnotation", "com.android.foo.MyAnnotationTwo"] }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
## notAnnotation Filter
The `notAnnotation` filter will run all tests except those annotated with the field (or fields) you specified.
You can pass an array of notAnnotations, see below for an example.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--not-annotation "com.android.foo.MyAnnotation,com.android.foo.MyAnnotationTwo"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "notAnnotation": ["com.android.foo.MyAnnotation", "com.android.foo.MyAnnotationTwo"] }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
## shards Filter
You can split your Espresso tests across multiple parallel shards to reduce total run time. Pass `numShards` (total number of shards, an integer greater than 0) and `shardIndex` (which shard this run executes, an integer between 0 inclusive and `numShards` exclusive).
For example, to split into 4 shards and run shard 0 (the first), pass `numShards=4` and `shardIndex=0`. Submit one test run per shard index to run all four shards in parallel.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--num-shards 4 \
--shard-index 0
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "numShards": 4, "shardIndex": 0 }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/espresso/options
# Set Espresso Test Runner
By default, TestingBot will run your Espresso tests with `${packageName}/androidx.test.runner.AndroidJUnitRunner` (TestingBot substitutes `${packageName}` with your app's package id automatically).
If you have defined your own `testInstrumentationRunner`, you can pass this custom runner during the start Espresso call:
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--test-runner "${packageName}/customTestRunner"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "testRunner": "${packageName}/customTestRunner" }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
Replace the `:id` with the identifier you received during the app upload call.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/espresso/screenshots
# Capturing Espresso Screenshots
Espresso does not have a built-in way to capture screenshots during test automation. You can however achieve this by using two options:
- **Native Screenshot grabbing**
Use native Java or Kotlin code to take a screenshot of the device, and save it on the device.
Once the test is completed, you can fetch the image through `ADB`, or if you run it via TestingBot, the screenshot will appear on the [test result page](https://testingbot.com#view).
- **Using the Spoon library**
The [Square Screenshots feature](https://square.github.io/spoon/#screenshots) is part of the Spoon Library.
It will take screenshots during your Espresso tests, but only for Android devices with a version of Android 10 or lower.
[Using Native Screenshot](https://testingbot.com#)[Using Spoon Library](https://testingbot.com#)
1. **Add the native screenshots file to your project**
You can see the implementation of Native Screenshots in the [TestingBot Espresso Demo App](https://github.com/testingbot/android-espresso-demo-app/blob/main/app/src/androidTest/java/com/testingbot/calculator/NativeScreenshot.java).
package com.testingbot.calculator;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Environment;
import androidx.test.runner.screenshot.BasicScreenCaptureProcessor;
import androidx.test.runner.screenshot.ScreenCapture;
import androidx.test.runner.screenshot.Screenshot;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
public final class NativeScreenshot {
private static String methodName;
private static String className;
private static final Pattern SCREENSHOT_NAME_VALIDATION = Pattern.compile("[a-zA-Z0-9_-]+");
private NativeScreenshot() {}
/**
* Captures screenshot using Androidx Screenshot library and stores in the filesystem.
* If there is any runtime exception while capturing screenshot, the method throws
* Exception and the test might fail if exception is not handled properly.
* @param screenshotName a screenshot identifier
* @return path to the screenshot file
*/
public static String capture(String screenshotName) {
StackTraceElement testClass = findTestClassTraceElement(Thread.currentThread().getStackTrace());
className = testClass.getClassName().replaceAll("[^A-Za-z0-9._-]", "_");
methodName = testClass.getMethodName();
EspressoScreenCaptureProcessor screenCaptureProcessor = new EspressoScreenCaptureProcessor();
if (!SCREENSHOT_NAME_VALIDATION.matcher(screenshotName).matches()) {
throw new IllegalArgumentException("ScreenshotName must match " + SCREENSHOT_NAME_VALIDATION.pattern() + ".");
} else {
ScreenCapture capture = Screenshot.capture();
capture.setFormat(Bitmap.CompressFormat.PNG);
capture.setName(screenshotName);
try {
return screenCaptureProcessor.process(capture);
} catch (IOException e) {
throw new RuntimeException("Unable to capture screenshot.", e);
}
}
}
/**
* Extracts the currently executing test's trace element based on the test runner
* or any framework being used.
* @param trace stacktrace of the currently running test
* @return StackTrace Element corresponding to the current test being executed.
*/
private static StackTraceElement findTestClassTraceElement(StackTraceElement[] trace) {
for(int i = trace.length - 1; i >= 0; --i) {
StackTraceElement element = trace[i];
if ("android.test.InstrumentationTestCase".equals(element.getClassName()) && "runMethod".equals(element.getMethodName())) {
return extractStackElement(trace, i);
}
if ("org.junit.runners.model.FrameworkMethod$1".equals(element.getClassName()) && "runReflectiveCall".equals(element.getMethodName())) {
return extractStackElement(trace, i);
}
if ("cucumber.runtime.model.CucumberFeature".equals(element.getClassName()) && "run".equals(element.getMethodName())) {
return extractStackElement(trace, i);
}
}
throw new IllegalArgumentException("Could not find test class!");
}
/**
* Based on the test runner or framework being used, extracts the exact traceElement.
* @param trace stacktrace of the currently running test
* @param i a reference index
* @return trace element based on the index passed
*/
private static StackTraceElement extractStackElement(StackTraceElement[] trace, int i) {
int testClassTraceIndex = Build.VERSION.SDK_INT >= 23 ? i - 2 : i - 3;
return trace[testClassTraceIndex];
}
private static class EspressoScreenCaptureProcessor extends BasicScreenCaptureProcessor {
private static final String SCREENSHOT = "screenshots";
EspressoScreenCaptureProcessor() {
File screenshotDir = new File(String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)), SCREENSHOT);
File classDir = new File(screenshotDir, className);
mDefaultScreenshotPath = new File(classDir, methodName);
}
/**
* Converts the filename to a standard path to be stored on device.
* Example: "post_addition" converts to "1648038895211_post_addition"
* which is later suffixed by the file extension i.e. png.
* @param filename a screenshot identifier
* @return custom filename format
*/
@Override
protected String getFilename(String filename) {
return System.currentTimeMillis() + "_" + filename;
}
}
}
2. **Modify your Espresso test scripts to take screenshots**
You can call the `NativeScreenshot.capture("tag")` method to capture screenshots in your Espresso tests.
The `tag` can be used to provide a name to the screenshot (usually you would use the testname for this).
@Test
public void ensureCalculatorWorks() {
onView(withId(R.id.buttonOne)).perform(click());
onView(withId(R.id.buttonAdd)).perform(click());
onView(withId(R.id.buttonTwo)).perform(click());
onView(withId(R.id.editText)).check(matches(withText("3")));
NativeScreenshot.capture("ensureCalculatorWorks");
}
3. **Android 11 and higher**
For Android 11 and higher, make sure to add the following `RuleChain` to your Espresso test:
@Rule
public RuleChain screenshotRule = RuleChain
.outerRule(GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE))
.around(activityScenarioRule);
1. **Add the required dependencies to your project**
See the [Spoon Documentation](https://github.com/square/spoon#download) on how to add Spoon to your project.
You can either use a `JAR` or use `Maven`.
2. **Change storage permissions**
If you do not yet have the `WRITE_EXTERNAL_STORAGE` permission in your `AndroidManifest.xml`, please add it.
It will allow Spoon to save the screenshots on your device.
...
If the targetSdk for your app is using Android 10 (level 29), please make sure to add `android:requestLegacyExternalStorage="true"` in the Application tag of your `AndroidManifest.xml` file.
3. **Modify your Espresso test scripts to take screenshots**
You can call the `Spoon.screenshot(activityName, tag)` method to capture screenshots in your Espresso tests.
The `tag` can be used to provide a name to the screenshot (usually you would use the testname for this).
@Rule
public ActivityScenarioRule activityRule =
new ActivityScenarioRule<>(MainActivity.class);
@Test
public void ensureCalculatorWorks() {
onView(withId(R.id.buttonOne)).perform(click());
onView(withId(R.id.buttonAdd)).perform(click());
onView(withId(R.id.buttonTwo)).perform(click());
onView(withId(R.id.editText)).check(matches(withText("3")));
activityRule.getScenario().onActivity(activity ->
Spoon.screenshot(activity, "ensureCalculatorWorks"));
}
## Viewing Espresso Screenshots
The Espresso tests that you run on TestingBot will appear automatically in the TestingBot dashboard.
If screenshots were generated during the test, they will be added on the Espresso test result page.
Additionally, these screenshots will also be available for download through our [REST API](https://testingbot.com/support/api).
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/espresso/set-ip-geolocation
# Use IP geolocation
TestingBot's GeoIP feature allows you to test your app from various parts of the world, by using IP addresses in [over 20 different countries](https://testingbot.com/support/web-automate/selenium/test-options#geo).
When you enable GeoIP, all traffic from your mobile Android app will originate from a specific IP address hosted in the country that you choose.
## GeoIP example
To use GeoIP, you can specify the geoIP option when starting an Espresso test.
Please see the example below where we specify that the app should connect via Germany.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--geo-location "DE"
The `DE` abbreviation is the ISO code of Germany. You can find other [GeoIP country codes](https://testingbot.com/support/web-automate/selenium/test-options#geo) to use.
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "geoLocation": "DE" }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
The `DE` abbreviation is the ISO code of Germany. You can find other [GeoIP country codes](https://testingbot.com/support/web-automate/selenium/test-options#geo) to use.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/espresso/set-localization-options
# Set localization options
With TestingBot you can run Android Espresso tests on a localized version of your Android app.
You can configure the language, locale and timezone of the remote device before running an Android Espresso test.
## Setting Language for Android Espresso Tests
You can test a localized version of your app with Android Espresso, by changing the language of the application under test.
You will need to specify a `language` option with the ISO code of the language you want to use.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--language "fr"
Use an [ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to set the language of your app.
Your app needs to be compiled with support for this language to notice a change.
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "language": "fr" }, "capabilities":[{"platform":"ANDROID", "version":12, "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
Use an [ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to set the language of your app.
Your app needs to be compiled with support for this language to notice a change.
## Set a locale for Android Espresso Testing
It is possible to set a locale, different than the default device's locale, with the `locale` parameter.
Please use the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code. For example: `DE` for Germany.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--locale "DE"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "locale": "DE" }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
## Change timezone for Android Espresso
It might be necessary during your tests to change the timezone to a different timezone than the default one on the device (UTC).
Please use a timezone format from the [list of timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--timezone "America/New_York"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "timeZone": "America/New_York" }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/espresso/simulate-network-conditions
# Simulate Network Conditions
TestingBot allows you to test your mobile apps under various network conditions, including download speed, upload speed, packet loss and latency.
## Simulate network conditions using a predefined network profile
TestingBot provides a set of predefined network profiles which you can choose from.
Profile Name | Bandwidth down/up (kbps) | Packet Loss (%) | Latency (ms) || Edge | 250/150 | 0 | 300 |
| 3G | 400/100 | 0 | 100 |
| 4G | 18000/9000 | 0 | 100 |
| airplane | 0 | 100 | 0 |
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--throttle-network "3G"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "throttle_network": "3G" }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
## Simulate network conditions using a custom network profile
You can specify a custom set of parameters, including:
- Download rate (kbps)
- Upload rate (kbps)
- Packet loss (%)
- Latency (ms)
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
The CLI supports predefined network profiles. For custom network profiles, use the cURL API directly.
# Predefined profiles: 4G, 3G, Edge, airplane
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--platform-version "12" \
--throttle-network "Edge"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/espresso/:id/run" \
-d '{ "espressoOptions": { "throttle_network": { "uploadSpeed": 10240, "downloadSpeed": 10240, "latency": 0, "loss": 0 } }, "capabilities":[{"version":"12", "deviceName":"Pixel 6", "platformName":"Android"}]}' \
-H "Content-Type: application/json"
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/espresso/test-reports
# Espresso Test Reports
TestingBot currently offers both test reports from inside the TestingBot member dashboard, or through REST-API where you can use a [JUnit formatted XML report](https://testingbot.com#junit).
## Dashboard Report
When you run Espresso Tests with TestingBot, you will find the test results, together with a video of the test, in the [TestingBot member dashboard](https://testingbot.com/members).
## API report
You can query the TestingBot API to fetch the result of the Espresso test run(s).
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
By default, the CLI waits for test completion and displays results in real-time:
testingbot espresso app.apk app-test.apk --device "Pixel 6"
The CLI will show:
- Upload progress for app and test APK
- Device allocation status
- Live output from Espresso tests
- Final pass/fail status
Use `--async` to start tests without waiting for results:
testingbot espresso app.apk app-test.apk --device "Pixel 6" --async
curl -u api_key:api_secret \
"https://api.testingbot.com/v1/app-automate/espresso/:id/:run_id"
Replace the `:id` and `:run_id` with the identifiers you received during the app upload call and the run call.
You will get back a response similar to this one:
{"id":86,"created_at":"2023-11-05T18:45:51.000Z","status":"DONE","capabilities":{"version":"12","deviceName":"Pixel 6","platformName":"Android"},"success":false,"test":{"sessionId":"30f642d082c3-804f3c0ec2df-a18b2d431ffb-169920992163-48455547","environment":{"name":"chrome","os":"Pixel 6 - 12.0","version":"12.0"}},"version":"1.0"}
## JUnit XML report
You can query our API to fetch a JUnit XML report which you can use a post-build step in your CI/CD environment.
**Report Structure**
In the XML report, each session maps to a `testsuite` tag. Inside that tag, multiple `testcase` items are available, each mapping to a single Espresso test.
If a test failed, a `failure` or `error` tag will be available.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
Download the JUnit XML report after test completion:
testingbot espresso app.apk app-test.apk \
--device "Pixel 6" \
--report junit \
--report-output-dir ./reports
The schema corresponds to the Android Studio's Gradle schema used when running JUnit tests.
curl -u api_key:api_secret \
"https://api.testingbot.com/v1/app-automate/espresso/:id/report"
The schema corresponds to the Android Studio's Gradle schema used when running JUnit tests.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/help
# Mobile App Help
Please see the topic below for help with Automated App testing:
## Help Topics
- [Prepare your App](https://testingbot.com/support/app-automate/help/prepare)
- [Upload your App](https://testingbot.com/support/app-automate/help/upload)
- [Tunnel + Real Devices](https://testingbot.com/support/app-automate/help/tunnel)
- [FAQ](https://testingbot.com/support/app-automate/help/faq)
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/help/prepare
# Prepare your Mobile App for Automated Testing
TestingBot provides Android Emulators and iOS Simulators as well as [Real Devices](https://testingbot.com/support/app-automate/devices) to run tests against your mobile app.
Below are the steps necessary to prepare your mobile app for Automated Testing.
## Building your app
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
### For Android:
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the .apk file needs to be compiled for the emulator/device you are running your test on (x86 or arm).
- Your app needs to have internet permissions
- The URL to the `.apk` file must be publicly available, or you can use our [TestingBot Storage](https://testingbot.com/support/app-automate/help/upload) to temporarily host your mobile app.
### For iOS (Real Device):
- Please supply the URL to an [.ipa file](https://testingbot.com#ipa). The URL must be publicly available, or you can use our [TestingBot Storage](https://testingbot.com/support/app-automate/help/upload) to temporarily host your mobile app.
- By default, we [automatically resign](https://testingbot.com/support/app-automate/help/app-resigning) your [.ipa file](https://testingbot.com#ipa) so that your development builds can run on our devices.
If you don't want this, specify `resigningEnabled: false` in your `tb:options` capabilities.
- Please make sure your target version matches the version of the simulator you want to test on.
### For iOS (Simulator):
- Please supply the URL to a .zip file that contains your .app
- The .app needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the .app bundle:
zip -r MyApp.zip MyApp.app
- Please make sure your target version matches the version of the simulator you want to test on.
## Building IPA file
To build an IPA file, please follow these steps:
- Open your project in XCode.
- Select **Generic iOS Device** as a project device target.
- In the **Product** menu, select **Archive**.
- Once archived, click **Export**.
- You will be asked what kind of export mode you want, you can pick any of these methods: **Ad Hoc** , **Enterprise** or **Development mode**.
- When asked for **Distribution options** , please set these options:
- App Thinning: disabled
- Uncheck **Rebuild from Bitcode**
- Uncheck **Strip Swift Symbols**
- Uncheck **Include manifest for over-the-air installation**
- Select your **Distribution Certificate** and **Provisioning Profile (either Automatic or Manual)**.
- Now your `IPA` file should be generated, export it to a folder on your hard disk.
- You can now upload the `IPA` file to a public URL or use our [TestingBot Storage](https://testingbot.com/support/app-automate/help/upload) to temporarily host your app.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/help/upload
# Upload your App
In order to run tests against your mobile app, our devices need to be able to download your app.
There are two ways to configure your tests to do this:
- Upload your app to TestingBot Storage: our private upload servers.
- Specify an HTTP or HTTPS url to the app you uploaded on a public internet website.
## TestingBot Storage
With TestingBot Storage, you can upload your app to our servers.
The advantage of this is that your tests will start faster, as the device can download the app from our internal network, instead of the public internet.
To get started, simply submit your app to our API:
$ curl -X POST "https://api.testingbot.com/v1/storage" \
-u "key:secret" -F "file=@/path/to/app/file/Application-debug.apk"
This call will return a unique identifier for your app (`{"app_url": "tb://..."}`), which you can use in the desired capabilities (see example below).
More information regarding this API call and other similar calls (update uploaded file, delete uploaded file, list uploaded files) are available in [our API documentation](https://testingbot.com/support/api#upload).
Note: uploads to TestingBot Storage are automatically deleted after 62 days.
require 'appium_lib'
caps = {
"platformName" => "iOS",
"appium:deviceName" => "iPhone 15",
"appium:platformVersion" => "18.0",
"appium:app" => "tb://...", # specify the link to your app here
"tb:options" => {
"name" => "single_test",
"build" => "Ruby Appium Sample"
}
}
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://API_KEY:API_SECRET@hub.testingbot.com/wd/hub"
}}, true)
driver = appium_driver.start_driver
driver.find_element(:name, "inputA").send_keys 5
driver.find_element(:name, "inputB").send_keys 10
driver.quit
## Public Internet
If you have your app available on a public website, you can specify this in the desired capabilities:
require 'appium_lib'
caps = {
"platformName" => "iOS",
"appium:deviceName" => "iPhone 15",
"appium:platformVersion" => "18.0",
"appium:app" => "https://...", # specify the link to your app here
"tb:options" => {
"name" => "single_test",
"build" => "Ruby Appium Sample"
}
}
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://API_KEY:API_SECRET@hub.testingbot.com/wd/hub"
}}, true)
driver = appium_driver.start_driver
driver.find_element(:name, "inputA").send_keys 5
driver.find_element(:name, "inputB").send_keys 10
driver.quit
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/help/tunnel
# Using TestingBot Tunnel with Real Devices
With TestingBot tunnel, we create a secure connection between a real device running in our datacenter, and your computer and internal network.
This way, you can run tests against apps using internal (staging) APIs, from our real mobile devices. Or test websites running on your local network through Safari/Chrome running on a real device.
TestingBot Tunnel in combination with Real Devices is available for both **public devices** and **private devices**.
More information regarding our TestingBot Tunnel is available on our [TestingBot Tunnel documentation page](https://testingbot.com/support/tunnel).
## Known Limitations
- **Localhost testing does not work on iOS**.
Due to an iOS Restriction, it is currently not possible to run tests against `http(s)://localhost` with TestingBot Tunnel.
A possible workaround would be to change the hosts file on the machine running TestingBot Tunnel, and add a custom hostname (for example `mylocalwebsite` and the IP address `127.0.0.1`).
Then configure your tests to use `http(s)://mylocalwebsite/...` instead of localhost.
## Security and Privacy (Advisory)
At TestingBot, we clean each device after every test. We take the utmost care to clean as much from the device as we can, to make sure your test runs without any leftover artifacts from previous tests.
However, due to the complexity of automatically cleaning different mobile device models, we cannot 100% guarantee that all data generated from a previous session is removed from our **public devices**.
Using our TestingBot tunnel in combination with a public device might have potential security risks.
We recommend to [opt for private, dedicated devices](https://testingbot.com/enterprise/private-device-cloud) for maximum security.
With **private devices** , your company will be the only one running tests on the device.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/help/faq
# Mobile App Testing FAQ
TestingBot provides mobile application testing on both **virtual devices** as well as **physical devices**.
Virtual devices are Android emulators and iOS simulators.
TestingBot provides a large collection of these, available for mobile app testing, both automated and manual testing.
This FAQ document will list questions and answers regarding the mobile native app testing service.
## Can I run Android emulator tests using Espresso instead of Appium?
TestingBot allows you to upload native mobile Android apps and use [Espresso testing](https://testingbot.com/support/app-automate/espresso) on both Android emulators and physical devices.
Of course, you can do [Appium automated app testing](https://testingbot.com/support/app-automate/appium) as well on TestingBot.
## Which apps are available on the devices?
TestingBot uses stock images for Android emulators, which come equipped with the PlayStore and Chrome.
Similarly, we use stock iOS simulators which come with Safari by default.
TestingBot does not use jailbroken or rooted physical devices.
All Android devices come with factory settings, with PlayStore enabled (if available) and Chrome.
The iOS devices come with Safari, the Safari version depends on the iOS version installed on the device.
## How does manual testing work?
We provide access to a realtime view of an Android emulator, iOS simulator, Android physical device and iOS device.
You can interact with the device, from your own mouse and keyboard, without installing any plugins or software.
The screens are streamed either through WebRTC or VNC, depending on your network connectivity settings.
## When are new devices added?
Our public device pool contains a collection of the most popular devices used, backed by public statistics of mobile device usage.
When new devices are released to the public, we aim to procure, setup and make these available to our customers in less than 2 weeks.
The criteria we use for selecting which devices to add depends on the popularity of the device and the demand for testing on such a device.
If you are looking to test on a specific device, please do not hesitate to [reach out to us](https://testingbot.com/contact/new).
## Is it possible to test push notifications?
It is possible to test push notifications in the following scenarios:
- **Android Physical Devices**
It will work automatically. Simply install the app and allow push notifications.
- **iOS Physical Devices**
This is only possible for private devices, which require an [Enterprise plan](https://testingbot.com/enterprise). [Reach out to us](https://testingbot.com/contact/new) for more information.
- **Android Emulators**
Unfortunately this is not supported yet.
If you'd like to be a part of the beta program, please let us know.
- **iOS Simulators**
This is possible since Appium 1.21.0.
## Can I upload or download files from a device or sim/emu?
Yes, you can upload files to devices using Appium's `pushFile` command and download files using the `pullFile` command.
Please see our [Upload Files documentation](https://testingbot.com/support/app-automate/appium/upload-files) for more information.
## How is my app protected during mobile app testing?
Our Real Device Cloud is located in a secure datacenter in Europe. We make sure that every device is fully protected, both physically and digitally.
All traffic happens through SSL and each device is cleaned after usage:
- All user apps are removed
- Generated data, such as browser data, is wiped
- SD card, accounts, and other data that was altered is removed
TestingBot's [automated cleaning process](https://testingbot.com/support/app-automate/help/cleaning) makes sure that no data is available from an earlier test.
If you think this did happen, please reach out to us so we can reset the device and check why this happened.
## Is audio supported, or microphone access?
Audio is available for Android devices running Android 12 and higher.
Older physical devices do not have the capability to stream audio, these will only stream a realtime video of the screen.
## My app needs to make requests to internal services. Can I use a VPN for this?
We suggest using the [TestingBot Tunnel](https://testingbot.com/support/tunnel). It will set up the device or sim/emu to proxy all requests through the tunnel to your internal services.
An alternative solution is to [whitelist our IP addresses](https://testingbot.com/support/other/configuration#ip-range), which would allow our devices to reach your protected services.
## Can I use Bluetooth on the physical devices?
Yes you can, please [reach out](https://testingbot.com/contact/new) to discuss your specific use case.
## Can I trigger a disconnect or reconnect from a charging cable?
This is currently not supported.
## Can I change the orientation of the device?
Yes, both on simulators/emulators as well as on physical devices. You can change the orientation to either `PORTRAIT` or `LANDSCAPE`.
You can do this via our manual testing feature, or [change the screen orientation during an automated test](https://testingbot.com/support/app-automate/appium/screen-orientation).
## Are code coverage reports by JaCoCo supported?
We currently do not support this.
## Can I change the geolocation during my test?
Yes, you can [change the geolocation](https://testingbot.com/support/web-automate/selenium/test-options#geo) either during manual testing or via automated tests.
## Why do I sometimes see a lock/PIN screen?
On trial devices, in our public device pool, we need to make some areas password protected to prevent users from changing certain settings.
The password protection is not in place on non-trial devices.
## Can I test payments on the physical devices?
Payments can be tested on private devices only. Please [reach out to us](https://testingbot.com/contact/new) for more information.
## Can I inspect the mobile app in your manual testing feature?
Yes, you can inspect the mobile app from the manual testing feature. Simply open the **DevTools** option and click the **Inspector** tab.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/help/cleaning
# Real Device Cleaning Process
TestingBot automatically cleans each physical mobile device after every test session. We do this to ensure each new test starts with a clean state, without any leftover data from previous sessions.
The cleaning process we use is automated for both iOS and Android. For devices in the public device pool, each test session automatically ends with a thorough cleaning process where we delete any apps installed by the user, together with various other files and data that were generated.
If you are using a private device, there's the possibility to alter or completely disable the cleaning process. You would for example use this when you want to make sure that a specific state is kept between various test sessions.
## Cleaning Process
TestingBot uses a proprietary process to clean its devices. Below are the various processes that happen when a device is being cleaned.
- All apps that have been installed by the customer are deleted. Some applications, which are used by TestingBot to operate the device, are kept.
- The browser history, together with any cached website data and cookies are removed.
- AppleID sessions are removed.
- Network settings such as WiFi and DNS configurations are reset to their default state.
- The system locale, language and time zone are reset to their default. (English, UTC+2).
- Any mock GPS location is reset to Brussels, Belgium.
- Media files such as Photos, Videos and other Files are removed from the device.
TestingBot's cleaning process aims to clean as much as possible from the device. If you prefer the guarantee of a pristine device, please consider using a [private mobile device](https://testingbot.com/enterprise/private-device-cloud).
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/help/app-resigning
# iOS App Resigning
When you [upload an iOS app](https://testingbot.com/support/app-automate/help/upload) to TestingBot, we will automatically re-sign the `.ipa` file with our own provisioning profile, to be able to install and run your app on our mobile devices.
Because of this procedure, the [entitlements](https://developer.apple.com/documentation/bundleresources/entitlements) of your iOS app will be removed.
You can choose to disable this re-signing, if your app is signed using the [Apple Developer Enterprise Program](https://developer.apple.com/programs/enterprise/). This way you can test **push notifications** or [universal links](https://developer.apple.com/ios/universal-links/) on our physical devices.
## Disable iOS Resigning
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
capabilities = {
"tb:options" => {
"resigningEnabled" => false
}
}
Map tbOptions = new HashMap<>();
tbOptions.put("resigningEnabled", false);
options.setCapability("tb:options", tbOptions);
$capabilities = [
"tb:options" => [
"resigningEnabled" => false
]
];
capabilities = {
"tb:options": {
"resigningEnabled": False
}
}
const capabilities = {
"tb:options": {
"resigningEnabled": false
}
};
var tbOptions = new Dictionary
{
["resigningEnabled"] = false
};
options.AddAdditionalAppiumOption("tb:options", tbOptions);
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/smart-tv
# Smart TV Testing
Run tests on physical Smart TV devices with TestingBot, using the open-source test framework Appium.
Currently TestingBot supports running automated tests on physical AppleTV 4k devices.
To get started with testing your Smart TV app in the cloud, you will need to follow these steps:
- Upload your native Smart TV app, either to a public URL or using TestingBot Storage.
- Create an Appium test script that will connect to TestingBot.
- The results will be available in the TestingBot dashboard, together with a video of the test and logs.
## Upload Smart TV App
To run Smart TV tests on TestingBot, you will first have to upload your `.ipa` file to TestingBot Storage.
[cURL](https://testingbot.com#)
$ curl -X POST "https://api.testingbot.com/v1/storage" \
-u key:secret -F "file=@/path/to/app/file/tvos.ipa"
This call will return a unique identifier for your SmartTV app (`{"app_url": "tb://..."}`), which you can use in the capabilities in your test (see example below).
More information regarding this API call and other similar calls (update uploaded file, delete uploaded file, list uploaded files) are available in [the TestingBot API documentation](https://testingbot.com/support/api#upload).
Note: uploads to TestingBot Storage are automatically deleted after 62 days.
## Run your first SmartTV test
Once you've [uploaded your Smart TV app](https://testingbot.com#upload), you can start with running your first test on the TestingBot SmartTV cloud.
TestingBot currently only supports running tests on physical AppleTV 4k devices. Please see the example tests below, where we use the [TestingBot tvOS example app](https://github.com/testingbot/tvos-example) to input two numbers in the calculator and verify its sum.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
require 'rubygems'
require 'appium_lib'
caps = {}
caps['name'] = 'Ruby tvOS Example'
caps['deviceName'] = 'Apple TV 4K'
caps['platformName'] = 'tvOS'
caps['version'] = '17.4'
caps['app'] = 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa'
caps['realDevice'] = true
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://key:secret@hub.testingbot.com/wd/hub"
}}, true)
driver = appium_driver.start_driver
wait = Selenium::WebDriver::Wait.new(:timeout => 30)
wait.until { driver.find_element(:accessibility_id, "inputField1").displayed? }
inputField1 = driver.find_element(:accessibility_id, "inputField1")
inputField1.click
inputField1.send_keys 5
driver.execute_script('mobile: pressButton', { name: 'down' })
driver.execute_script('mobile: pressButton', { name: 'select' })
driver.switch_to.active_element.send_keys 10
driver.execute_script('mobile: pressButton', { name: 'down' })
driver.execute_script('mobile: pressButton', { name: 'select' })
result = driver.find_element(:accessibility_id, "resultField")
if (!result.nil?) && (result.text.to_i == 15)
puts "Test Passed"
else
puts "Test Failed"
end
driver.quit
const wdio = require('webdriverio');
const caps = {
name: 'Node.js tvOS Example',
deviceName: 'Apple TV 4K',
platformName: 'tvOS',
platformVersion: '17.4',
app: 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
realDevice: true
};
const options = {
protocol: 'https',
hostname: 'hub.testingbot.com',
port: 443,
path: '/wd/hub',
user: 'key', // Replace 'key' with your TestingBot key
key: 'secret', // Replace 'secret' with your TestingBot secret
capabilities: caps
};
(async () => {
// Start the Appium driver
const driver = await wdio.remote(options);
try {
// Wait for the first input field to be displayed
const inputField1 = await driver.$('~inputField1');
await inputField1.waitForDisplayed({ timeout: 30000 });
// Interact with the first input field
await inputField1.click();
await inputField1.setValue(5);
// Navigate down and select
await driver.execute('mobile: pressButton', { name: 'down' });
await driver.execute('mobile: pressButton', { name: 'select' });
// Send keys to the active element
await driver.switchToParentFrame(); // Ensure focus is on the active frame
await driver.activeElement().setValue(10);
// Navigate down and select the result
await driver.execute('mobile: pressButton', { name: 'down' });
await driver.execute('mobile: pressButton', { name: 'select' });
// Verify the result
const result = await driver.$('~resultField');
const resultText = await result.getText();
if (resultText && parseInt(resultText, 10) === 15) {
console.log('Test Passed');
} else {
console.log('Test Failed');
}
} catch (err) {
console.error('Error occurred:', err);
} finally {
// Quit the driver
await driver.deleteSession();
}
})();
from appium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Define capabilities
caps = {
"name": "Python tvOS Example",
"deviceName": "Apple TV 4K",
"platformName": "tvOS",
"platformVersion": "17.4",
"app": "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
"realDevice": True
}
# Appium server URL
server_url = "https://key:secret@hub.testingbot.com/wd/hub"
# Initialize the driver
driver = webdriver.Remote(server_url, caps)
try:
# Wait for the first input field to be displayed
wait = WebDriverWait(driver, 30)
input_field1 = wait.until(EC.presence_of_element_located((By.ACCESSIBILITY_ID, "inputField1")))
# Interact with the first input field
input_field1.click()
input_field1.send_keys("5")
# Navigate down and select the second input field
driver.execute_script('mobile: pressButton', {'name': 'down'})
driver.execute_script('mobile: pressButton', {'name': 'select'})
# Send keys to the active element (second input field)
driver.switch_to.active_element.send_keys("10")
# Navigate down and select the result field
driver.execute_script('mobile: pressButton', {'name': 'down'})
driver.execute_script('mobile: pressButton', {'name': 'select'})
# Verify the result
result_field = driver.find_element(By.ACCESSIBILITY_ID, "resultField")
result_text = result_field.text
if result_text and int(result_text) == 15:
print("Test Passed")
else:
print("Test Failed")
except Exception as e:
print(f"An error occurred: {e}")
finally:
# Quit the driver
driver.quit()
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileBy;
import io.appium.java_client.remote.MobileCapabilityType;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
import java.time.Duration;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class TvOSTest {
public static void main(String[] args) {
AppiumDriver driver = null;
try {
// Set capabilities
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("name", "Java tvOS Example");
caps.setCapability("deviceName", "Apple TV 4K");
caps.setCapability("platformName", "tvOS");
caps.setCapability("platformVersion", "17.4");
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
caps.setCapability("realDevice", true);
URL serverUrl = new URL("https://key:secret@hub.testingbot.com/wd/hub");
driver = new AppiumDriver<>(serverUrl, caps);
// Wait for the first input field to be displayed
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
WebElement inputField1 = wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.AccessibilityId("inputField1")));
inputField1.click();
inputField1.sendKeys("5");
// Navigate down and select the second input field
driver.executeScript("mobile: pressButton", Map.of("name", "down"));
driver.executeScript("mobile: pressButton", Map.of("name", "select"));
// Send keys to the active element (second input field)
WebElement activeElement = driver.switchTo().activeElement();
activeElement.sendKeys("10");
// Navigate down and select the result field
driver.executeScript("mobile: pressButton", Map.of("name", "down"));
driver.executeScript("mobile: pressButton", Map.of("name", "select"));
WebElement resultField = driver.findElement(MobileBy.AccessibilityId("resultField"));
String resultText = resultField.getText();
if (resultText != null && Integer.parseInt(resultText) == 15) {
System.out.println("Test Passed");
} else {
System.out.println("Test Failed");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// Quit the driver
if (driver != null) {
driver.quit();
}
}
}
}
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Enums;
using OpenQA.Selenium.Appium.iOS;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
class TvOSTest
{
static void Main(string[] args)
{
AppiumDriver driver = null;
try
{
// Set capabilities
var caps = new AppiumOptions();
caps.AddAdditionalCapability(MobileCapabilityType.PlatformName, "tvOS");
caps.AddAdditionalCapability(MobileCapabilityType.DeviceName, "Apple TV 4K");
caps.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, "17.4");
caps.AddAdditionalCapability(MobileCapabilityType.App, "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
caps.AddAdditionalCapability("realDevice", true);
caps.AddAdditionalCapability("name", "C# tvOS Example");
// Initialize driver
var serverUri = new Uri("https://key:secret@hub.testingbot.com/wd/hub");
driver = new IOSDriver(serverUri, caps);
// Wait for the first input field to be displayed
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
var inputField1 = wait.Until(d => d.FindElement(MobileBy.AccessibilityId("inputField1")));
// Interact with the first input field
inputField1.Click();
inputField1.SendKeys("5");
// Navigate down and select the second input field
driver.ExecuteScript("mobile: pressButton", new Dictionary { { "name", "down" } });
driver.ExecuteScript("mobile: pressButton", new Dictionary { { "name", "select" } });
// Send keys to the active element (second input field)
var activeElement = driver.SwitchTo().ActiveElement();
activeElement.SendKeys("10");
// Navigate down and select the result field
driver.ExecuteScript("mobile: pressButton", new Dictionary { { "name", "down" } });
driver.ExecuteScript("mobile: pressButton", new Dictionary { { "name", "select" } });
// Verify the result
var resultField = driver.FindElement(MobileBy.AccessibilityId("resultField"));
var resultText = resultField.Text;
if (!string.IsNullOrEmpty(resultText) && int.Parse(resultText) == 15)
{
Console.WriteLine("Test Passed");
}
else
{
Console.WriteLine("Test Failed");
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
finally
{
// Quit the driver
driver?.Quit();
}
}
}
## View SmartTV Test Results
Once your test has finished running, you will see a new test entry in the [TestingBot dashboard.](https://testingbot.com/members). If you click the test, you will see detailed information about the test, including a video recording of the test, logs and screenshots.
You can also use the [TestingBot API](https://testingbot.com/support/api) to programmatically fetch the test results.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/smart-tv/options
# Smart TV Test Options
Please see the examples below on how to customize your test run on the available Smart TV devices.
## Specifying Appium Version
TestingBot will use the most recent, compatible, Appium version according to the device, OS and version you specify.
If you'd like to specify your own Appium version, you can do this with the `appiumVersion` capability.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
deviceName: "Apple TV 4K",
browserVersion: "17.4",
app: 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"tb:options" => {
"appiumVersion" : "2.0"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("appiumVersion", "2.0");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("deviceName", "Apple TV 4K");
caps.setCapability("browserVersion", "17.4");
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
tbOptions = {
'name': 'W3C Sample',
'appiumVersion': "2.0"
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'browserVersion': "17.4",
'deviceName': "Apple TV 4K",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"deviceName": 'Apple TV 4K',
"version": '17.4',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"appiumVersion": "2.0"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "17.4",
PlatformName = "tvOS",
DeviceName = "Apple TV 4K",
App = "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["appiumVersion"] = "2.0"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
### Appium 2 Versions
We currently support these Appium 2 versions:
- 2.11.3
- 2.10.3
- 2.9.0
- 2.5.1
- 2.4.1
- 2.3.0
- 2.2.1
If you specify `"appiumVersion": "latest"`, TestingBot will automatically use the latest Appium version. You can also use `latest-1`, `latest-2`, ... to test on the next most recent versions of Appium.
### Geolocation Testing
TestingBot provides an option where you can specify from which country you'd like to run the test from.
We will configure the Smart TV device to use a proxy that will route all traffic through the specified country.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
deviceName: "Apple TV 4K",
version: "17.4",
"tb:options" => {
"testingbot.geoCountryCode" : "DE"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("testingbot.geoCountryCode", "DE");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
tbOptions = {
'testingbot.geoCountryCode': "DE"
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"testingbot.geoCountryCode": "DE"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "17.4",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["testingbot.geoCountryCode"] = "DE"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Specify `testingbot.geoCountryCode` with one of the following country codes:
- **'\*'** : this will take a random country from the list below
- **'AU'** : Australia
- **'BE'** : Belgium
- **'BR'** : Brazil
- **'CA'** : Canada
- **'CL'** : Chile
- **'FR'** : France
- **'DE'** : Germany
- **'IN'** : India
- **'IT'** : Italy
- **'JP'** : Japan
- **'NO'** : Norway
- **'SG'** : Singapore
- **'ZA'** : South Africa
- **'SE'** : Sweden
- **'CH'** : Switzerland
- **'AE'** : United Arab Emirates
- **'GB'** : United Kingdom
- **'US'** : United States
**Important:** this does not work on tvOS 4.4
### Change Test Name
Add a name to this test, which will show up in the TestingBot member area and API.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
version: "17.4",
deviceName: "Apple TV 4K",
"tb:options" => {
"name" : "My Test Name"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("name", "My Test Name");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('tvOS');
$capabilities->setCapability('tb:options', array(
'name' => "My Test Name"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': "My Test Name"
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'deviceName': "Apple TV 4K",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"name": "My Test Name"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "17.4",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = "My Test Name"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || string | unnamed test |
### Group Tests
A key you can use to group certain tests in the same build (for example in Jenkins).
The builds will appear in our member area.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
version: "17.4",
deviceName: "Apple TV 4K",
"tb:options" => {
"build" : "My First Build"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("build", "My First Build");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('tvOS');
$capabilities->setCapability('tb:options', array(
'build' => "My First Build"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'build': "My First Build"
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'deviceName': "Apple TV 4K",
'version': '17.4',
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"build": "My First Build"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "17.4",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["build"] = "My First Build"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type || string |
### Idle Timeout
The maximum amount of time a device will wait before proceeding to the next step in your test.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
version: "17.4",
deviceName: "Apple TV 4K",
"tb:options" => {
"idletimeout" : 130
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("idletimeout", 130);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('tvOS');
$capabilities->setCapability('tb:options', array(
'idletimeout' => 130
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'idletimeout': 130
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"idletimeout": 130
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "17.4",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["idletimeout"] = 130
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || int (specify number of seconds) | 130 seconds |
### Maximum Test Duration
The maximum duration for a single test. This is a safeguard to prevent bad tests from using up your credits.
We generally recommend to keep tests short (less than 10 minutes). It's better to split up large tests in smaller individual tests.
This keeps your tests fast and allows for more parallelization of your tests.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
version: "17.4",
deviceName: "Apple TV 4K",
"tb:options" => {
"maxduration" : 1800
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("maxduration", 1800);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('tvOS');
$capabilities->setCapability('tb:options', array(
'maxduration' => 1800
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'maxduration': 1800
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"maxduration": 1800
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "17.4",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["maxduration"] = 1800
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || int (specify number of seconds) | 1800 seconds (30 minutes) |
### Custom Metadata
Send along custom data, for example your release, server, commit hash, ...
This will show up on the test detail page in the TestingBot member area.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
version: "17.4",
deviceName: "Apple TV 4K",
"tb:options" => {
"extra" : "Extra Information"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("extra", "Extra Information");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('tvOS');
$capabilities->setCapability('tb:options', array(
'extra' => "Extra Information"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'extra': "Extra Information"
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"extra": "Extra Information"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "17.4",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["extra"] = "Extra Information"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type || string |
### Taking screenshots during your tests
By default, TestingBot does not capture screenshots at every step of your mobile test. If you wish to take a screenshot for every step, please add this capability (set to `true`) to your request.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
version: "17.4",
deviceName: "Apple TV 4K",
"tb:options" => {
screenshot: true
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("screenshot", true);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa);
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('tvOS');
$capabilities->setCapability('tb:options', array(
'screenshot' => true
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'screenshot': True
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa,
'platformName': "tvOS",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"screenshot": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "23",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["screenshot"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || boolean | false |
### Make a video of your tests
By default we record a video of your test, which is accessible in the member area. If you do not wish to have this, you can disable it with this option.
Video should not slow down your test considerably.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
version: "17.4",
"tb:options" => {
screenrecorder: true
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("screenrecorder", true);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('tvOS');
$capabilities->setCapability('tb:options', array(
'screenrecorder' => true
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'screenrecorder': True
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"screenrecorder": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "17.4",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["screenrecorder"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || boolean | true |
### Test Privacy
Make the test results for this test public so that everyone can access the results.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
version: "17.4",
"tb:options" => {
"public" : false
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("public", false);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('tvOS');
$capabilities->setCapability('tb:options', array(
'public' => 130
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'public': False
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"public": false
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "17.4",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["public"] = false
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || boolean | false |
### Customize Logging
By default, TestingBot records logs of all test actions and its drivers.
Set this option to `false` if you don't want TestingBot to record anything (for example, if you have sensitive data).
You will not see any test logs in our member dashboard.
Set to `strip-parameters` to prevent the POST/GET parameters from being logged on the TestingBot test detail page (does not affect other logs like Appium logs, Chromedriver logs, ...).
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "tvOS",
app: "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
version: "17.4",
deviceName: "Apple TV 4K",
"tb:options" => {
"recordLogs" : true
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("recordLogs", true);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "tvOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('tvOS');
$capabilities->setCapability('tb:options', array(
'recordLogs' => true
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'recordLogs': True
}
chromeOpts = {
'app': "https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa",
'platformName': "tvOS",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://github.com/testingbot/tvos-example/releases/download/1.0.0/tvos-example.ipa',
"platformName": 'tvOS',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"recordLogs": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "17.4",
PlatformName = "tvOS",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["recordLogs"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value | Possible Values: || string | "true" | `true`, `false`, or `strip-parameters` |
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/smart-tv/remote-control-navigation
# TvOS (Apple TV 4K)
During an Appium test you can use the buttons actions to simulate remote control navigation on the Apple TV.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
driver.execute_script('mobile: pressButton', { name: 'down' })
driver.execute_script('mobile: pressButton', { name: 'select' })
await driver.execute('mobile: pressButton', { name: 'down' });
await driver.execute('mobile: pressButton', { name: 'select' });
driver.execute_script('mobile: pressButton', {'name': 'down'})
driver.execute_script('mobile: pressButton', {'name': 'select'})
driver.executeScript("mobile: pressButton", Map.of("name", "down"));
driver.executeScript("mobile: pressButton", Map.of("name", "select"));
driver.ExecuteScript("mobile: pressButton", new Dictionary { { "name", "down" } });
driver.ExecuteScript("mobile: pressButton", new Dictionary { { "name", "select" } });
The following buttons are available:
| Button Name | Behaviour |
| --- | --- |
| Home | Return to the home screen. |
| Menu | Return to the previous operation or screen. In the context of tvOS, it works as a traditional back button. |
| Select | Select the item with the current focus. |
| Up | Move the focus upward in the UI. |
| Down | Move the focus downward in the UI. |
| Left | Move the focus left in the UI. |
| Right | Move the focus right in the UI. |
| Play/Pause | Control media playback. Play/Pause is a toggle. |
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium
Documentation
# Appium Cloud Testing
Appium is the open-source W3C WebDriver framework for native, hybrid and mobile-web applications. TestingBot runs Appium tests on real iPhones, iPads and Android devices in our EU datacenter, alongside iOS simulators and Android emulators for fast iteration. Bring your existing Appium tests in Java, Python, Node.js, Ruby, C#, PHP or Robot Framework and point them at the TestingBot grid.
## Quick start: run your first Appium test
Four steps to point an existing Appium test at the TestingBot cloud:
1. **Sign up and grab your API key.** [Create a free account](https://testingbot.com/users/sign_up). Your API key and secret are in the dashboard under Account, then Keys and Authentication.
2. **Upload your app binary to TestingBot Storage.** Upload your `.ipa` (iOS) or `.apk` (Android) to [TestingBot Storage](https://testingbot.com/support/api#upload). The response returns a `tb://` URL you reference as the `appium:app` capability.
3. **Point Appium at the TestingBot endpoint.** Connect your Appium client to `https://hub.testingbot.com/wd/hub`. Set `platformName`, `appium:deviceName`, `appium:platformVersion` and `appium:app` (the `tb://` URL). Pass your key and secret via `tb:options`.
4. **Run the test and view results.** TestingBot streams a live video, screenshots, device logs and Appium command logs into the dashboard while the test runs and after it finishes.
## Pick a language or framework
Each card below links to a complete setup guide for that language or framework, including a working example, capability reference and CI integration.
### [Java](https://testingbot.com/support/app-automate/appium/java)
Appium with the Java Appium client. JUnit, TestNG and Cucumber friendly.
- [Java tutorial](https://testingbot.com/support/app-automate/appium/java)
- [TestNG](https://testingbot.com/support/app-automate/appium/java/testng)
### [Node.js](https://testingbot.com/support/app-automate/appium/nodejs)
Appium with the Node.js Appium or WebdriverIO clients.
- [Node.js tutorial](https://testingbot.com/support/app-automate/appium/nodejs)
- [CodeceptJS](https://testingbot.com/support/app-automate/appium/nodejs/codeceptjs)
- [WebdriverIO](https://testingbot.com/support/app-automate/appium/nodejs/webdriverio)
### [C# / .NET](https://testingbot.com/support/app-automate/appium/csharp)
Appium with the C# Appium client. NUnit, Reqnroll and SpecFlow friendly.
- [C# tutorial](https://testingbot.com/support/app-automate/appium/csharp)
- [NUnit](https://testingbot.com/support/app-automate/appium/csharp/nunit)
- [Reqnroll](https://testingbot.com/support/app-automate/appium/csharp/reqnroll)
- [SpecFlow](https://testingbot.com/support/app-automate/appium/csharp/specflow)
### [Python](https://testingbot.com/support/app-automate/appium/python)
Appium with the Python Appium client and pytest.
- [Python tutorial](https://testingbot.com/support/app-automate/appium/python)
- [Robot Framework](https://testingbot.com/support/app-automate/appium/robotframework)
### [Ruby](https://testingbot.com/support/app-automate/appium/ruby)
Appium with the Ruby `appium_lib` gem.
- [Ruby tutorial](https://testingbot.com/support/app-automate/appium/ruby)
### [PHP](https://testingbot.com/support/app-automate/appium/php)
Appium with the PHP WebDriver client.
- [PHP tutorial](https://testingbot.com/support/app-automate/appium/php)

### [Robot Framework](https://testingbot.com/support/app-automate/appium/robotframework)
Keyword-driven testing with AppiumLibrary on top of Robot Framework.
- [Robot Framework tutorial](https://testingbot.com/support/app-automate/appium/robotframework)
## Preparing your app
Before uploading, make sure your `.ipa` or `.apk` is built for testing (not release-signed for the App Store) so it can be installed on TestingBot devices. See [preparing a mobile app for testing](https://testingbot.com/support/app-automate/help/prepare) for the iOS provisioning profile and Android signing requirements.
## Useful Appium capabilities
Appium exposes [a comprehensive list of capabilities](https://appium.io/docs/en/2.0/guides/caps/) for configuring a test. The capabilities below are the ones we see come up most often:
### Android-specific
- `appium:appActivity` and `appium:appPackage`: by default Appium extracts the launch activity from your APK. Set these explicitly if extraction fails or if you want to start in a non-default activity.
- `appium:chromeOptions`: extra ChromeDriver options when testing the in-app Chromium web view.
- `appium:otherApps`: a JSON array of additional app URLs to install alongside the app under test (useful for testing share-targets or deep links to other apps).
### Android and iOS
- `appium:locale`: the language of the simulator or device (for example `fr_CA`).
This sets the locale on the entire device or simulator, except on real iOS devices. For real iOS devices, TestingBot passes `-AppleLocale` as a launch argument to the app instead.
- `appium:newCommandTimeout`: how many seconds Appium waits for a new command from the client before assuming the client quit and ending the session.
- `appium:autoGrantPermissions` (Android) or `appium:autoAcceptAlerts` (iOS): silently grant or accept permission popups so they do not block the test. See [permission popups](https://testingbot.com/support/app-automate/appium/permission-popups).
- `realDevice: true` inside `tb:options`: route the test to a physical iPhone, iPad or Android device instead of a simulator or emulator.
## Frequently asked questions
What is Appium and what is it used for?
Appium is an open-source cross-platform test framework for native, hybrid and mobile-web applications. It implements the W3C WebDriver protocol so you can use the same client library to drive iOS and Android apps from Java, Python, Node.js, Ruby, C#, PHP and other languages. TestingBot runs Appium on a cloud grid of real devices and simulators so you do not have to maintain local Appium servers.
Which languages and frameworks does Appium support on TestingBot?
All Appium client libraries work against the TestingBot grid: [Java](https://testingbot.com/support/app-automate/appium/java), [Python](https://testingbot.com/support/app-automate/appium/python), [Node.js](https://testingbot.com/support/app-automate/appium/nodejs) (including [WebdriverIO](https://testingbot.com/support/app-automate/appium/nodejs/webdriverio) and [CodeceptJS](https://testingbot.com/support/app-automate/appium/nodejs/codeceptjs)), [Ruby](https://testingbot.com/support/app-automate/appium/ruby), [C#](https://testingbot.com/support/app-automate/appium/csharp) (including [NUnit](https://testingbot.com/support/app-automate/appium/csharp/nunit), [Reqnroll](https://testingbot.com/support/app-automate/appium/csharp/reqnroll) and [SpecFlow](https://testingbot.com/support/app-automate/appium/csharp/specflow)), [PHP](https://testingbot.com/support/app-automate/appium/php) and [Robot Framework](https://testingbot.com/support/app-automate/appium/robotframework).
Can I run Appium tests on real iPhones and real Android devices?
Yes. TestingBot runs Appium tests on physical iPhones, iPads and Android phones and tablets housed in our EU datacenter, alongside iOS simulators and Android emulators. Use `realDevice: true` in your capabilities to target real hardware. See [real device testing](https://testingbot.com/real-device-testing) for the full real-hardware overview.
How do I upload my app binary for Appium tests?
Upload your `.ipa` or `.apk` to [TestingBot Storage](https://testingbot.com/support/api#upload) with your API key and secret. The response returns a `tb://` URL that you pass as the `appium:app` capability in your test. See [uploading apps for Appium](https://testingbot.com/support/app-automate/appium/upload-files) for details.
Which Appium version does TestingBot run?
TestingBot automatically selects the latest Appium 2 release compatible with the device, OS and OS version you request. Pin a specific version with the [`appiumVersion` capability](https://testingbot.com/support/app-automate/appium/appium-versions) if you need an exact release. The full list of supported versions is on the [Appium versions page](https://testingbot.com/support/app-automate/appium/appium-versions).
Can I test the mobile web (Safari, Chrome) with Appium on TestingBot?
Yes. Set `browserName` to `safari` (iOS) or `chrome` (Android) and omit the `app` capability. See [mobile browser testing](https://testingbot.com/support/web-automate/mobile) for the full setup.
How do I handle permission popups, push notifications or app upgrades in Appium tests?
TestingBot supports the standard Appium capabilities for these flows: [auto-grant or auto-accept permission popups](https://testingbot.com/support/app-automate/appium/permission-popups), [trigger and interact with push notifications](https://testingbot.com/support/app-automate/appium/push-notifications), and [test app upgrades](https://testingbot.com/support/app-automate/appium/test-app-upgrades) by chaining installs of two binaries.
Can I run Appium tests behind a firewall or against a staging API?
Yes. Start the [TestingBot Tunnel](https://testingbot.com/support/tunnel) and pass `tunnelIdentifier` in your `tb:options` capabilities. The cloud device will route HTTP traffic through your tunnel so it can reach internal staging APIs and local development servers.
## Next steps
- [ **Capabilities builder**
Generate ready-to-paste Appium capabilities](https://testingbot.com/support/app-automate/appium/capabilities)
- [ **tb:options reference**
All TestingBot-specific Appium options](https://testingbot.com/support/app-automate/appium/options)
- [ **Appium versions**
Pin a specific Appium release](https://testingbot.com/support/app-automate/appium/appium-versions)
- [ **Uploading apps**
.ipa and .apk uploads to TestingBot Storage](https://testingbot.com/support/app-automate/appium/upload-files)
- [ **Permission popups**
Auto-grant Android and iOS permission dialogs](https://testingbot.com/support/app-automate/appium/permission-popups)
- [ **Push notifications**
Test FCM and APNs flows in your app](https://testingbot.com/support/app-automate/appium/push-notifications)
- [ **Supported devices**
Real iPhones, iPads and Android devices](https://testingbot.com/support/app-automate/devices)
- [ **TestingBot Tunnel**
Test against internal or staging APIs](https://testingbot.com/support/tunnel)
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/capabilities
# Appium Capabilities Generator
Generate Appium capabilities for your mobile automated tests. Choose your device, OS version and configuration options to get the exact code you need for Java, Python, NodeJS, C#, Ruby or PHP.
[Selenium ](https://testingbot.com/support/web-automate/selenium/capabilities)[Appium ](https://testingbot.com/support/app-automate/appium/capabilities)[Puppeteer ](https://testingbot.com/support/web-automate/puppeteer/capabilities)[Playwright ](https://testingbot.com/support/web-automate/playwright/capabilities)
Mobile Environment
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available devices and platforms.
App URL or ID
Appium Version3.0.12.11.52.10.32.9.02.8.12.7.02.6.02.5.42.4.12.3.02.2.32.1.32.0.11.22.31.22.21.22.11.22.01.21.01.20.21.19.11.18.31.17.11.16.01.15.1
## Project Capabilities
Test NameAllows you to customize the name of the test, as it will appear in the dashboard.
Build NameGroup tests under the same build identifier for easy retrieval.
## Debugging
Record Video
YesNo
Take Screenshot After Each Step
YesNo
Collect Logs
YesNo
## Customization
Locale
Geolocation TestingRun the test from a different geographical location.NoneRandom CountryAustraliaBelgiumBrazilCanadaChileFranceGermanyIndiaItalyJapanNorwaySingaporeSouth AfricaSwedenSwitzerlandUnited Arab EmiratesUnited KingdomUnited States
Screen OrientationPortraitLandscape
Looking for more options? See [all Appium capabilities](https://testingbot.com/support/app-automate/appium/options).
W3C Protocol
W3C is the newest WebDriver protocol, TestingBot recommends using W3C.
JSONWP
JSONWP is the legacy protocol which is no longer actively maintained.
JavaPythonNodeJSC#RubyPHP
MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability(CapabilityType.BROWSER_NAME, "safari");
capabilities.setCapability(CapabilityType.BROWSER_VERSION, "18.6");
capabilities.setCapability("appium:deviceName", "iPhone 16");
capabilities.setCapability(CapabilityType.PLATFORM_NAME, "iOS");
capabilities.setCapability("appium:automationName", "XCUITest");
---
URL: https://testingbot.com/support/app-automate/appium/appium-versions
# Supported Appium Versions on TestingBot
This page lists the Appium releases currently available on the TestingBot grid, the drivers installed with each, and the differences between Appium 1, 2 and 3. To pin a specific version in your tests, set the `appiumVersion` capability inside `tb:options`.
## Appium 3
TestingBot supports Appium 3. See [supported versions](https://testingbot.com#versions) below to pin Appium 3 for your tests.
Appium 3 introduces a couple of breaking changes compared to Appium 2. The main changes are removal of legacy code and protocols, which were already marked as deprecated in [Appium 2](https://testingbot.com#appium2).
Breaking changes in Appium 3:
- Node.js and NPM minimum version bump (to 20.19 and 10) which is handled by TestingBot.
- Removal of certain deprecated endpoints.
- Security feature flag prefix requirements (for example replacing `adb_shell` with `uiautomator2:adb_shell`).
See the [Appium 3 migration guide](https://appium.io/docs/en/latest/guides/migrating-2-to-3/) for the full list.
## Appium 2
Appium 2 is the new version of Appium, the mobile automation framework to run tests against websites and mobile apps on phones and tablets (both simulators/emulators and physical devices).
The main difference between Appium 2 and Appium 1 is that Appium 2 uses the concept of plugins and drivers.
This makes for a better ecosystem, with cleaner code and improved flexibility and performance.
The drivers will add support for any given platform, for example an xcuitest driver will provide support for iOS XCUITest automation, an espresso driver for Android Espresso.
Plugins allow for adding and customizing existing behavior of Appium. Drivers and plugins can be easily shared, installed or removed from Appium 2.
Appium 2 supports iOS 14 and above and Android 6 and above.
## Appium Versions
TestingBot currently supports these Appium versions:
| Appium version | Status | Installed drivers |
| --- | --- | --- |
| 3.2.2 | active | These drivers are installed:
- `appium-xcuitest-driver`: 10.33.0
- `appium-safari-driver`: 4.1.9
- `appium-uiautomator2-driver`: 7.0.0
- `appium-flutter-driver`: 3.5.0
|
| 3.0.1 | active | These drivers are installed:
- `appium-xcuitest-driver`: 10.0.2
- `appium-safari-driver`: 4.0.1
- `appium-uiautomator2-driver`: 5.0.0
- `appium-flutter-driver`: 2.14.2
|
| 2.17.0 or `latest` | active | These drivers are installed:
- `appium-xcuitest-driver`: 8.4.3
- `appium-safari-driver`: 3.5.23
- `appium-uiautomator2-driver`: 4.1.2
- `appium-flutter-driver`: 2.14.2
- `appium-flutter-integration-driver`: 1.1.3
|
| 2.11.3 | active | These drivers are installed:
- `appium-xcuitest-driver`: 7.24.16
- `appium-safari-driver`: 3.5.17
- `appium-uiautomator2-driver`: 3.7.7
- `appium-flutter-driver`: 2.9.2
- `appium-flutter-integration-driver`: 1.1.3
|
| 2.10.3 or `latest-1` | December 1, 2025 | These drivers are installed:
- `appium-xcuitest-driver`: 7.17.5
- `appium-uiautomator2-driver`: 3.5.4
- `appium-flutter-driver`: 2.6.0
|
| 2.9.0 | November 1, 2025 | These drivers are installed:
- `appium-xcuitest-driver`: 7.17.5
- `appium-uiautomator2-driver`: 3.5.4
- `appium-flutter-driver`: 2.6.0
|
| 2.5.1 | October 1, 2025 | These drivers are installed:
- `appium-xcuitest-driver`: 7.9.1
- `appium-uiautomator2-driver`: 3.0.4
- `appium-flutter-driver`: 2.5.1
|
| 2.4.1 | September 1, 2025 | These drivers are installed:
- `appium-xcuitest-driver`: 5.15.1
- `appium-uiautomator2-driver`: 2.43.4
- `appium-flutter-driver`: 2.4.1
|
If you specify `"appiumVersion": "latest"`, TestingBot will automatically use the latest Appium version. You can also use `latest-1`, `latest-2`, ... to test on the next most recent versions of Appium.
### Appium 1 (legacy)
TestingBot keeps Appium 1 available as a legacy option for older test suites, currently at version `1.22.3`. We recommend migrating new tests to Appium 3.
## Appium 2 example on TestingBot
Appium 1 and 2 are both supported on TestingBot, on all our physical devices and virtual devices: iOS simulators and Android emulators.
These Appium 2 drivers are installed on TestingBot:
- [appium-uiautomator2-driver](https://github.com/appium/appium-uiautomator2-driver)
- [appium-xcuitest-driver](https://github.com/appium/appium-xcuitest-driver)
- [appium-geckodriver](https://github.com/appium/appium-geckodriver)
- [appium-safari-driver](https://github.com/appium/appium-safari-driver)
- [Flutter](https://testingbot.com/support/app-automate/appium/flutter)
These Appium 2 plugins are pre-installed and can be activated by specifying a `tb:appiumPlugins` capability (by default empty):
{
'tb:appiumPlugins': ['images', 'execute-driver', 'universal-xml']
}
- [Images Plugin](https://github.com/appium/appium/tree/master/packages/images-plugin)
- [Execute Driver Plugin](https://github.com/appium/appium/tree/master/packages/execute-driver-plugin)
- [Universal XML Plugin](https://github.com/appium/appium/tree/master/packages/universal-xml-plugin)
TestingBot defaults to the latest stable Appium release compatible with the device and OS version you request. Override it by [specifying the `appiumVersion` capability](https://testingbot.com/support/app-automate/appium/options#appiumVersion) inside `tb:options`.
The example below connects to the TestingBot device grid and pins Appium 2 for a mobile automation test:
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
XCUITestOptions options = new XCUITestOptions();
options.setPlatformName("iOS");
options.setCapability("appium:platformVersion", "18.0");
options.setCapability("appium:deviceName", "iPhone 16");
HashMap tbOptions = new HashMap<>();
tbOptions.put("appiumVersion", "3.2.2");
options.setCapability("tb:options", tbOptions);
const capabilities = {
platformName: 'iOS',
'appium:platformVersion': '18.0',
'appium:deviceName': 'iPhone 16',
'appium:automationName': 'XCUITest',
'tb:options': {
appiumVersion: '3.2.2'
}
}
capabilities = {
"platformName" : "iOS",
"appium:platformVersion" : "18.0",
"appium:deviceName" : "iPhone 16",
"appium:automationName": "XCUITest",
"tb:options" : {
"appiumVersion" : "3.2.2"
}
}
capabilities = {
"platformName" => "iOS",
"appium:platformVersion" => "18.0",
"appium:deviceName" => "iPhone 16",
"appium:automationName" => "XCUITest",
"tb:options" => {
"appiumVersion" => "3.2.2"
}
}
AppiumOptions options = new AppiumOptions();
options.PlatformName = "iOS";
options.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 16");
options.AddAdditionalAppiumOption("appium:automationName", "XCUITest");
var tbOptions = new Dictionary();
tbOptions.Add("appiumVersion", "3.2.2");
options.AddAdditionalAppiumOption("tb:options", tbOptions);
## Appium 2 Changes
If you've been using Appium 1 and want to try out Appium 2, then please be aware of the possible breaking changes listed below.
### Protocol Changes
Appium 2 only allows the [W3C WebDriver Protocol](https://www.w3.org/TR/webdriver/). Even though Appium 1 has supported this protocol since recent years, it also supported JSONWP (JSON Wire Protocol) and MJSONWP (Mobile Json Wire Protocol).
This means that if you are using an older Appium client or binding, you might have trouble communicating with Appium 2.
Instead of using `desiredCapabilities`, you now need to use `capabilities` with either `alwaysMatch` or `firstMatch` properties.
### Capabilities
Appium only accepts a couple of [standard W3C capabilities](https://www.w3.org/TR/webdriver/#capabilities), including:
- `browserName`
- `browserVersion`
- `platformName`
All other capabilities, such as `app` or `deviceName` need to be vendor-prefixed. A vendor prefix means you prefix each key with a string followed by a colon. In Appium's case, this would be `appium:`
An example of some of the capabilities you can use with Appium 2:
- `appium:app`
- `appium:deviceName`
- `appium:noReset`
### automationName is required
With Appium 2, you now need to specify an automationName. Before, with Appium 1 this was filled in automatically if you did not specify it.
For iOS testing, you should use `appium:automationName` with `XCUITest`. For Android, you should set the `appium:automationName` capability to `UiAutomator2`.
### TestingBot specific capabilities
You can specify [TestingBot specific capabilities](https://testingbot.com/support/app-automate/appium/options) with the `tb:options` capability.
See below for an example where we will be using both Appium 2 and custom TestingBot capabilities.
XCUITestOptions options = new XCUITestOptions();
options.setPlatformName("iOS");
options.setCapability("appium:platformVersion", "18.0");
options.setCapability("appium:deviceName", "iPhone 16");
HashMap tbOptions = new HashMap<>();
tbOptions.put("appiumVersion", "3.2.2");
options.setCapability("tb:options", tbOptions);
## Frequently asked questions
Which Appium version does TestingBot use by default?
TestingBot selects the latest stable Appium release compatible with the device, OS and OS version you request, biased toward Appium 3 for current iOS and Android versions. To pin a specific version, set the `appiumVersion` capability inside `tb:options`. Use `latest`, `latest-1` or an explicit version number like `3.2.2`.
How do I pin a specific Appium version for my test?
Add `appiumVersion` inside the `tb:options` capability map. For example, in WebdriverIO: `'tb:options': { appiumVersion: '3.2.2' }`. See the [code example below](https://testingbot.com#example) for Java, Node.js, Python, Ruby and C# snippets.
What is new in Appium 3 compared to Appium 2?
Appium 3 removes a number of deprecated endpoints, bumps the minimum Node.js and NPM versions (handled by TestingBot), and introduces a security feature flag prefix (for example `adb_shell` becomes `uiautomator2:adb_shell`). See the [Appium 3 migration guide](https://appium.io/docs/en/latest/guides/migrating-2-to-3/) for the full list.
What is the difference between Appium 1 and Appium 2?
Appium 2 introduced the plugin and driver architecture, allowing platform support to be installed as separate drivers (xcuitest for iOS, uiautomator2 for Android, and so on). It also restricts the protocol to W3C WebDriver only, requires the appium: vendor prefix for non-standard capabilities, and requires automationName to be set explicitly. Appium 1 is kept as a legacy option for older test suites.
Is Appium 1 still supported on TestingBot?
Yes, Appium 1 is supported as a legacy option on the grid, currently at `1.22.3`. We recommend migrating to Appium 3 for new tests because Appium 1 receives no upstream updates. To stay on Appium 1, pin `appiumVersion: "1.22.3"` in your `tb:options`.
Why is automationName required on Appium 2 and 3?
Appium 2 and 3 use a driver architecture where each platform driver registers a name. You must tell Appium which driver to use by setting the `appium:automationName` capability. For iOS use `XCUITest`; for Android use `UiAutomator2`; for Flutter use `Flutter`.
Can I use Appium 2 plugins on TestingBot?
Yes. Activate plugins per-test with the `tb:appiumPlugins` capability. The Images, Execute Driver and Universal XML plugins are pre-installed on the grid. For example: `'tb:appiumPlugins': ['images', 'execute-driver', 'universal-xml']`.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/csharp
### C# Examples:
- [NUnit](https://testingbot.com/support/app-automate/appium/csharp/nunit)
- [Reqnroll](https://testingbot.com/support/app-automate/appium/csharp/reqnroll)
- [SpecFlow](https://testingbot.com/support/app-automate/appium/csharp/specflow)
# C# Automated App Testing
This guide will help you run mobile automated tests with Appium and C# on the TestingBot device cloud.
[Command Line](https://testingbot.com#)[Visual Studio](https://testingbot.com#)
TestingBot has created a sample Visual Studio project to help you get started with C# and Appium testing.
[Download ZIP](https://github.com/testingbot/csharp-app-example/archive/refs/heads/main.zip)
You can import this project into Visual Studio and run the tests directly.
To run your tests with C# and .NET, you need to have the [.NET SDK](https://dotnet.microsoft.com/download) installed on your machine.
- **On Windows:**
- Download the [.NET SDK](https://dotnet.microsoft.com/en-us/download) installer and run it.
- Or use the `winget` command: `winget install Microsoft.DotNet.SDK.9`
- **On MacOS:** `brew install dotnet`
- **On Linux:** `sudo apt-get install dotnet-sdk-9.0`
After installing the .NET SDK, you can create a new NUnit test project using the command line:
dotnet new nunit -n AppiumTest
cd AppiumTest
Next, add the Appium and Selenium WebDriver packages to your project:
dotnet add package Appium.WebDriver
dotnet add package Selenium.WebDriver
dotnet add package Selenium.Support
Replace the contents of `UnitTest1.cs` with the example code below, then run your tests with:
TB_KEY=... TB_SECRET=... dotnet test
## Installation
See our [C# App example repository](https://github.com/testingbot/csharp-app-example) for a simple example on how to run C# tests on Mobile Devices with TestingBot.
Please download the [appium-dotnet-driver](https://github.com/appium/appium-dotnet-driver) and add it to your project.
You can use NuGet packages as well, in which case you'll need to install these packages:
- Appium.WebDriver
- DotNetSeleniumExtras.WaitHelpers
- Selenium.WebDriver
## Real Device Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
This will download and install a sample Android app we built. It will add two numbers to a textinput and display the sum of the two numbers.
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
namespace AppiumNunit
{
[TestFixture]
public class AndroidRealDeviceTest
{
private AndroidDriver? Driver;
[Test]
public void SampleTest()
{
var options = new AppiumOptions
{
PlatformName = "Android",
AutomationName = "UiAutomator2",
App = "https://testingbot.com/appium/sample.apk"
};
options.AddAdditionalAppiumOption("appium:deviceName", "Galaxy S24");
options.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TESTINGBOT_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TESTINGBOT_SECRET") ?? "",
["name"] = TestContext.CurrentContext.Test.Name,
["realDevice"] = true
});
Driver = new AndroidDriver(new Uri("https://hub.testingbot.com/wd/hub"), options, TimeSpan.FromSeconds(120));
var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
var inputA = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputA")));
inputA.SendKeys("10");
var inputB = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputB")));
inputB.SendKeys("5");
Assert.Pass();
}
[TearDown]
public void CleanUpAfterEveryTestMethod()
{
if (Driver != null)
{
var passed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed;
((IJavaScriptExecutor)Driver).ExecuteScript("tb:test-result=" + (passed ? "passed" : "failed"));
Driver.Dispose();
}
}
}
}
This will download and install a sample iOS app we built. It will add two numbers to a textinput and display the sum of the two numbers.
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.iOS;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
namespace AppiumNunit
{
[TestFixture]
public class iOSRealDeviceTest
{
private IOSDriver? Driver;
[Test]
public void SampleTest()
{
var options = new AppiumOptions
{
PlatformName = "iOS",
AutomationName = "XCUITest",
App = "https://testingbot.com/appium/sample.ipa"
};
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 15");
options.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TESTINGBOT_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TESTINGBOT_SECRET") ?? "",
["name"] = TestContext.CurrentContext.Test.Name,
["realDevice"] = true
});
Driver = new IOSDriver(new Uri("https://hub.testingbot.com/wd/hub"), options, TimeSpan.FromSeconds(300));
var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
var inputA = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputA")));
inputA.SendKeys("10");
var inputB = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputB")));
inputB.SendKeys("5");
var sum = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("sum")));
Console.WriteLine(sum.Text);
Assert.Pass();
}
[TearDown]
public void CleanUpAfterEveryTestMethod()
{
if (Driver != null)
{
var passed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed;
((IJavaScriptExecutor)Driver).ExecuteScript("tb:test-result=" + (passed ? "passed" : "failed"));
Driver.Dispose();
}
}
}
}
## Simulator/Emulator Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
This will download and install a sample Android app we built. It will add two numbers to a textinput and display the sum of the two numbers.
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
namespace AppiumNunit
{
[TestFixture]
public class AndroidEmulatorTest
{
private AndroidDriver? Driver;
[Test]
public void SampleTest()
{
var options = new AppiumOptions
{
PlatformName = "Android",
AutomationName = "UiAutomator2",
App = "https://testingbot.com/appium/sample.apk"
};
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 8");
options.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TESTINGBOT_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TESTINGBOT_SECRET") ?? "",
["name"] = TestContext.CurrentContext.Test.Name
});
Driver = new AndroidDriver(new Uri("https://hub.testingbot.com/wd/hub"), options, TimeSpan.FromSeconds(240));
var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
var inputA = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputA")));
inputA.SendKeys("10");
var inputB = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputB")));
inputB.SendKeys("5");
Assert.Pass();
}
[TearDown]
public void CleanUpAfterEveryTestMethod()
{
if (Driver != null)
{
var passed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed;
((IJavaScriptExecutor)Driver).ExecuteScript("tb:test-result=" + (passed ? "passed" : "failed"));
Driver.Dispose();
}
}
}
}
This will download and install a sample iOS app we built. It will add two numbers to a textinput and display the sum of the two numbers.
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.iOS;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
namespace AppiumNunit
{
[TestFixture]
public class iOSSimulatorTest
{
private IOSDriver? Driver;
[Test]
public void SampleTest()
{
var options = new AppiumOptions
{
PlatformName = "iOS",
AutomationName = "XCUITest",
App = "https://testingbot.com/appium/sample.zip"
};
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 15");
options.AddAdditionalAppiumOption("appium:platformVersion", "17.4");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TESTINGBOT_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TESTINGBOT_SECRET") ?? "",
["name"] = TestContext.CurrentContext.Test.Name
});
Driver = new IOSDriver(new Uri("https://hub.testingbot.com/wd/hub"), options, TimeSpan.FromSeconds(300));
var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
var inputA = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputA")));
inputA.SendKeys("10");
var inputB = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputB")));
inputB.SendKeys("5");
var sum = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("sum")));
Console.WriteLine(sum.Text);
Assert.Pass();
}
[TearDown]
public void CleanUpAfterEveryTestMethod()
{
if (Driver != null)
{
var passed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed;
((IJavaScriptExecutor)Driver).ExecuteScript("tb:test-result=" + (passed ? "passed" : "failed"));
Driver.Dispose();
}
}
}
}
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Browsers & Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote machines. If the test was running on your local machine or network, you can simply change your existing test like this:
**Before:**
Driver = new IOSDriver(new Uri("http://localhost:4444/wd/hub"), options, TimeSpan.FromSeconds(300));
**After:**
Driver = new IOSDriver(new Uri("https://hub.testingbot.com/wd/hub"), options, TimeSpan.FromSeconds(300));
To let TestingBot know on which device you want to run your test on, you need to specify the `appium:deviceName`, `appium:platformVersion`, `platformName` and other optional options in the capabilities field.
To see how to do this, please select a combination of device type and device name in the drop-down menus below.
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a C# test with our Tunnel:
1. [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
2. Adjust your test: instead of pointing to `'hub.testingbot.com/wd/hub'` like the example above - change it to point to your tunnel's IP address.
Assuming you run the tunnel on the same machine you run your tests, change to `'localhost:4445/wd/hub'`. localhost is the machine running the tunnel, 4445 is the default port of the tunnel.
This way your test will go securely through the tunnel to TestingBot and back:
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.iOS;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
namespace AppiumNunit
{
[TestFixture]
public class iOSTunnelTest
{
private IOSDriver? Driver;
[Test]
public void SampleTest()
{
var options = new AppiumOptions
{
PlatformName = "iOS",
AutomationName = "XCUITest",
App = "https://testingbot.com/appium/sample.zip"
};
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 15");
options.AddAdditionalAppiumOption("appium:platformVersion", "17.4");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["name"] = TestContext.CurrentContext.Test.Name
});
Driver = new IOSDriver(new Uri("http://localhost:4445/wd/hub"), options, TimeSpan.FromSeconds(300));
var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
var inputA = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputA")));
inputA.SendKeys("10");
var inputB = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("inputB")));
inputB.SendKeys("5");
var sum = wait.Until(d => d.FindElement(AppiumBy.AccessibilityId("sum")));
Console.WriteLine(sum.Text);
Assert.Pass();
}
[TearDown]
public void CleanUpAfterEveryTestMethod()
{
if (Driver != null)
{
var passed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed;
((IJavaScriptExecutor)Driver).ExecuteScript("tb:test-result=" + (passed ? "passed" : "failed"));
Driver.Dispose();
}
}
}
}
## Run tests in Parallel
Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.
You can run the same tests on all different browser configurations or run different tests all on the same browser configuration.
TestingBot has a large grid of machines and browsers, which means you can use our service to do efficient parallel testing. It is one of the key features we provide to greatly cut down on your total testing time.
### Queuing
Every plan we provide comes with a limit of parallel tests.
If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.
## Mark tests as passed/failed
To see if a test passed or not in our member area, or to send additional meta-data to TestingBot, you can use our API.
Please see the example below on how to notify TestingBot about the test success state:
[TearDown]
public void CleanUp()
{
if (Driver != null)
{
bool passed = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed;
// Logs the result to TestingBot
((IJavaScriptExecutor)Driver).ExecuteScript("tb:test-result=" + (passed ? "passed" : "failed"));
// Terminates the remote webdriver session
Driver.Dispose();
}
}
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/csharp/specflow
# SpecFlow Automated App Testing
SpecFlow reached end-of-life at the end of 2024 and is succeeded by [Reqnroll](https://reqnroll.net). For new projects we recommend using [Reqnroll](https://testingbot.com/support/app-automate/appium/csharp/reqnroll) instead. The examples below still work with SpecFlow 3.x but new development should target Reqnroll.
See our [SpecFlow App example repository](https://github.com/testingbot/specflow-app-example) for a simple example on how to run SpecFlow tests on Mobile Devices with TestingBot.
This example project can be used with Visual Studio, or via terminal: `dotnet test`.
SpecFlow is an open-source .NET utility which allows you to write tests using Cucumber-compatible Gherkin syntax.
## Installation
We'll assume you are using Visual Studio to run your tests.
Here are the steps to set up the required environment for your SpecFlow tests:
- Add NuGet Packages: `Appium.WebDriver, NUnit, SpecFlow`
- For more information, please see the [SpecFlow documentation](https://specflow.org/getting-started/#InstallSetup).
In the example below, we'll create a calculator test that enters 2 numbers in 2 input fields and validates the sum.
The app used in the example is a demo app we created at TestingBot and runs on both iOS and Android.
## SpecFlow Example
First, we'll create the feature file **feature.feature** :
Feature: Calculator
Scenario Outline: Can do simple math
Given I am using the calculator
When I add inputA for "5"
When I add inputB for "10"
Then I should see the sum "15"
Now we need to define the logic behind these steps:
**SingleStep.cs**
using NUnit.Framework;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using TechTalk.SpecFlow;
namespace testingbot_specflow
{
[Binding]
public class SingleStep
{
private readonly ScenarioContext _scenarioContext;
private AndroidDriver _driver;
private readonly TestingBotDriver _tbDriver;
public SingleStep(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
_tbDriver = (TestingBotDriver) _scenarioContext["tbDriver"];
}
[Given(@"I am using the calculator")]
public void GivenIAmOnTheGooglePage()
{
_driver = _tbDriver.Init();
_driver.LaunchApp();
}
[When(@"I add inputA for ""(.*)""")]
public void WhenIAddA(string amount)
{
var inputA = _driver.FindElement(AppiumBy.Id("inputA"));
inputA.SendKeys(amount);
}
[When(@"I add inputB for ""(.*)""")]
public void WhenIAddB(string amount)
{
var inputB = _driver.FindElement(AppiumBy.Id("inputB"));
inputB.SendKeys(amount);
}
[Then(@"I should see the sum ""(.*)""")]
public void ThenIShouldSeeTitle(string sum)
{
Assert.That(_driver.FindElement(AppiumBy.XPath("//android.widget.EditText[@content-desc=\"sum\"]")).Text, Is.EqualTo(sum));
}
}
}
Finally, we'll need to create the `TestingBotDriver`:
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Appium.Enums;
using TechTalk.SpecFlow;
namespace testingbot_specflow
{
[Binding]
public sealed class testingbot_specflow
{
private readonly ScenarioContext _scenarioContext;
private TestingBotDriver tbDriver;
public testingbot_specflow(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[BeforeScenario]
public void BeforeScenario()
{
tbDriver = new TestingBotDriver(_scenarioContext);
_scenarioContext["tbDriver"] = tbDriver;
}
[AfterScenario]
public void AfterScenario()
{
tbDriver.Cleanup();
}
}
public class TestingBotDriver
{
private AndroidDriver driver;
private ScenarioContext current;
public TestingBotDriver(ScenarioContext current)
{
this.current = current;
}
public AndroidDriver Init()
{
AppiumOptions appiumOptions = new AppiumOptions();
appiumOptions.AddAdditionalAppiumOption("appium:deviceName", "Galaxy S9");
appiumOptions.AddAdditionalAppiumOption("platformName", "Android");
appiumOptions.AddAdditionalAppiumOption("appium:platformVersion", "9.0");
appiumOptions.AddAdditionalAppiumOption("appium:app", "tb://...");
string key = "TESTINGBOT_KEY";
string secret = "TESTINGBOT_SECRET";
appiumOptions.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = key,
["secret"] = secret
});
AndroidDriver driver = new AndroidDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions
);
return driver;
}
public void Cleanup()
{
if (driver != null)
{
driver.Quit();
}
}
}
}
## Real Device Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
This will download and install a sample Android app we built. It will add two numbers to a textinput and display the sum of the two numbers.
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Appium.Enums;
using TechTalk.SpecFlow;
namespace testingbot_specflow
{
[Binding]
public sealed class testingbot_specflow
{
private readonly ScenarioContext _scenarioContext;
private TestingBotDriver tbDriver;
public testingbot_specflow(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[BeforeScenario]
public void BeforeScenario()
{
tbDriver = new TestingBotDriver(_scenarioContext);
_scenarioContext["tbDriver"] = tbDriver;
}
[AfterScenario]
public void AfterScenario()
{
tbDriver.Cleanup();
}
}
public class TestingBotDriver
{
private AndroidDriver driver;
private ScenarioContext current;
public TestingBotDriver(ScenarioContext current)
{
this.current = current;
}
public AndroidDriver Init()
{
AppiumOptions appiumOptions = new AppiumOptions();
appiumOptions.AddAdditionalAppiumOption("appium:deviceName", "Galaxy S10");
appiumOptions.AddAdditionalAppiumOption("platformName", "Android");
appiumOptions.AddAdditionalAppiumOption("appium:platformVersion", "10.0");
appiumOptions.AddAdditionalAppiumOption("appium:app", "tb://...");
appiumOptions.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TESTINGBOT_KEY"),
["secret"] = Environment.GetEnvironmentVariable("TESTINGBOT_SECRET"),
["realDevice"] = true
});
AndroidDriver driver = new AndroidDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions
);
return driver;
}
public void Cleanup()
{
if (driver != null)
{
driver.Quit();
}
}
}
}
This will download and install a sample iOS app we built. It will add two numbers to a textinput and display the sum of the two numbers.
using System;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Enums;
using OpenQA.Selenium.Appium.iOS;
using TechTalk.SpecFlow;
namespace testingbot_specflow
{
[Binding]
public sealed class testingbot_specflow
{
private readonly ScenarioContext _scenarioContext;
private TestingBotDriver tbDriver;
public testingbot_specflow(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[BeforeScenario]
public void BeforeScenario()
{
tbDriver = new TestingBotDriver(_scenarioContext);
_scenarioContext["tbDriver"] = tbDriver;
}
[AfterScenario]
public void AfterScenario()
{
tbDriver.Cleanup();
}
}
public class TestingBotDriver
{
private IOSDriver driver;
private ScenarioContext current;
public TestingBotDriver(ScenarioContext current)
{
this.current = current;
}
public IOSDriver Init()
{
AppiumOptions appiumOptions = new AppiumOptions();
appiumOptions.AddAdditionalAppiumOption("appium:deviceName", "iPhone 15");
appiumOptions.AddAdditionalAppiumOption("platformName", "iOS");
appiumOptions.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
appiumOptions.AddAdditionalAppiumOption("appium:app", "tb://...");
appiumOptions.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TESTINGBOT_KEY"),
["secret"] = Environment.GetEnvironmentVariable("TESTINGBOT_SECRET"),
["realDevice"] = true
});
IOSDriver driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions
);
return driver;
}
public void Cleanup()
{
if (driver != null)
{
driver.Quit();
}
}
}
}
## Simulator/Emulator Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
This will download and install a sample Android app we built. It will add two numbers to a textinput and display the sum of the two numbers.
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Appium.Enums;
using TechTalk.SpecFlow;
namespace testingbot_specflow
{
[Binding]
public sealed class testingbot_specflow
{
private readonly ScenarioContext _scenarioContext;
private TestingBotDriver tbDriver;
public testingbot_specflow(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[BeforeScenario]
public void BeforeScenario()
{
tbDriver = new TestingBotDriver(_scenarioContext);
_scenarioContext["tbDriver"] = tbDriver;
}
[AfterScenario]
public void AfterScenario()
{
tbDriver.Cleanup();
}
}
public class TestingBotDriver
{
private AndroidDriver driver;
private ScenarioContext current;
public TestingBotDriver(ScenarioContext current)
{
this.current = current;
}
public AndroidDriver Init()
{
AppiumOptions appiumOptions = new AppiumOptions();
appiumOptions.AddAdditionalAppiumOption("appium:deviceName", "Galaxy S9");
appiumOptions.AddAdditionalAppiumOption("platformName", "Android");
appiumOptions.AddAdditionalAppiumOption("appium:platformVersion", "9.0");
appiumOptions.AddAdditionalAppiumOption("appium:app", "tb://...");
appiumOptions.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TESTINGBOT_KEY"),
["secret"] = Environment.GetEnvironmentVariable("TESTINGBOT_SECRET")
});
AndroidDriver driver = new AndroidDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions
);
return driver;
}
public void Cleanup()
{
if (driver != null)
{
driver.Quit();
}
}
}
}
This will download and install a sample iOS app we built. It will add two numbers to a textinput and display the sum of the two numbers.
using System;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Enums;
using OpenQA.Selenium.Appium.iOS;
using TechTalk.SpecFlow;
namespace testingbot_specflow
{
[Binding]
public sealed class testingbot_specflow
{
private readonly ScenarioContext _scenarioContext;
private TestingBotDriver tbDriver;
public testingbot_specflow(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[BeforeScenario]
public void BeforeScenario()
{
tbDriver = new TestingBotDriver(_scenarioContext);
_scenarioContext["tbDriver"] = tbDriver;
}
[AfterScenario]
public void AfterScenario()
{
tbDriver.Cleanup();
}
}
public class TestingBotDriver
{
private IOSDriver driver;
private ScenarioContext current;
public TestingBotDriver(ScenarioContext current)
{
this.current = current;
}
public IOSDriver Init()
{
AppiumOptions appiumOptions = new AppiumOptions();
appiumOptions.AddAdditionalAppiumOption("appium:deviceName", "iPhone 16");
appiumOptions.AddAdditionalAppiumOption("platformName", "iOS");
appiumOptions.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
appiumOptions.AddAdditionalAppiumOption("appium:app", "tb://...");
appiumOptions.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TESTINGBOT_KEY"),
["secret"] = Environment.GetEnvironmentVariable("TESTINGBOT_SECRET")
});
IOSDriver driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions
);
return driver;
}
public void Cleanup()
{
if (driver != null)
{
driver.Quit();
}
}
}
}
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Browsers & Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote machines. If the test was running on your local machine or network, you can simply change your existing test like this:
**Before:**
IOSDriver driver = new IOSDriver(
new Uri("http://localhost:4723/wd/hub"),
appiumOptions
);
**After:**
IOSDriver driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions
);
### Configuring Capabilities
Capabilities are key-value pairs that allows you to customize your tests on TestingBot.
Appium provides its [own set of capabilities](https://appium.io/docs/en/2.0/guides/caps/) which you can specify.
TestingBot also provide its own [Custom Capabilities](https://testingbot.com/support/app-automate/appium/options), to customize tests ran on the TestingBot platform.
You can use the drop-down menus below to see how to configure your tests to run on a specific mobile device:
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a C# test with our Tunnel:
1. [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
2. Adjust your test: instead of pointing to `'hub.testingbot.com/wd/hub'` like the example above - change it to point to your tunnel's IP address.
Assuming you run the tunnel on the same machine you run your tests, change to `'localhost:4445/wd/hub'`. localhost is the machine running the tunnel, 4445 is the default port of the tunnel.
This way your test will go securely through the tunnel to TestingBot and back:
using System;
using System.Collections.Generic;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
namespace AppiumTunnelTest {
class Program {
static void Main(string[] args) {
var appiumOptions = new AppiumOptions
{
PlatformName = "Android",
AutomationName = "UiAutomator2",
App = "tb://..."
};
appiumOptions.AddAdditionalAppiumOption("appium:deviceName", "Pixel 8");
appiumOptions.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
appiumOptions.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["name"] = "SpecFlow Tunnel Test"
});
// Point to local tunnel instead of hub.testingbot.com
var driver = new AndroidDriver(
new Uri("http://localhost:4445/wd/hub"), appiumOptions
);
// Run your app test against the internal/staged app...
driver.Quit();
}
}
}
## Run tests in Parallel
Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.
You can run the same tests on all different browser configurations or run different tests all on the same browser configuration.
TestingBot has a large grid of machines and browsers, which means you can use our service to do efficient parallel testing. It is one of the key features we provide to greatly cut down on your total testing time.
### Queuing
Every plan we provide comes with a limit of parallel tests.
If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.
## Mark tests as passed/failed
To see if a test passed or not in our member area, or to send additional meta-data to TestingBot, you can use our API.
Please see the example below on how to notify TestingBot about the test success state:
[TearDown]
public void CleanUp()
{
bool passed = TestContext.CurrentContext.Result.Status == TestStatus.Passed;
try
{
// Logs the result to TestingBot
((IJavaScriptExecutor)driver).ExecuteScript("tb:test-result=" + (passed ? "passed" : "failed"));
}
finally
{
// Terminates the remote webdriver session
driver.Quit();
}
}
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/csharp/reqnroll
# Reqnroll Automated App Testing
[Reqnroll](https://reqnroll.net/) is an open-source .NET test automation framework for Behavior-Driven Development (BDD). It is the community-maintained successor to SpecFlow and is fully compatible with Gherkin syntax.
Reqnroll works with all major .NET versions (.NET 6.0, 7.0, 8.0, 9.0) and integrates with popular test frameworks:
- NUnit
- xUnit
- MSTest
- TUnit
**Migrating from SpecFlow?** Reqnroll is based on the SpecFlow codebase and provides an easy migration path. See the [migration section](https://testingbot.com#migration) below.
In the example below, we run a Gherkin scenario against the TestingBot demo app on both Android and iOS. The app adds two numbers and shows the sum.
## Installation
First, install the **Reqnroll for Visual Studio 2022** extension from the Visual Studio Marketplace to get syntax highlighting, navigation and project templates.
### Create a new project
Create a new test project and add the required packages:
# Create a new NUnit test project
dotnet new nunit -n AppiumReqnrollTests
cd AppiumReqnrollTests
# Add Reqnroll with NUnit integration
dotnet add package Reqnroll.NUnit
# Add the Appium .NET client
dotnet add package Appium.WebDriver
dotnet add package Selenium.WebDriver
dotnet add package Selenium.Support
Or add these packages to your `.csproj` file:
Alternative packages for other test frameworks:
- `Reqnroll.xUnit` - for xUnit 2.x
- `Reqnroll.xUnit.v3` - for xUnit 3.x
- `Reqnroll.MsTest` - for MSTest
- `Reqnroll.TUnit` - for TUnit
## Your first Reqnroll test
Reqnroll tests consist of three parts: a Feature file (Gherkin), Step Definitions (C#), and a Driver helper class.
### 1. Feature File
Create a `Features` folder and add `Calculator.feature`:
Feature: Calculator
Scenario Outline: Can do simple math
Given I am using the calculator
When I add inputA for "5"
When I add inputB for "10"
Then I should see the sum "15"
### 2. Driver Helper Class
Create a helper class to manage the Appium connection to TestingBot:
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
namespace AppiumReqnrollTests.Support;
public class TestingBotDriver : IDisposable
{
public AndroidDriver Driver { get; private set; }
private bool _testPassed = true;
public void Initialize()
{
var options = new AppiumOptions
{
PlatformName = "Android",
AutomationName = "UiAutomator2",
App = "https://testingbot.com/appium/sample.apk"
};
options.AddAdditionalAppiumOption("appium:deviceName", "Galaxy S24");
options.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TB_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TB_SECRET") ?? "",
["name"] = "Reqnroll Appium Test",
["realDevice"] = true
});
Driver = new AndroidDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options,
TimeSpan.FromSeconds(120)
);
}
public void MarkTestFailed() => _testPassed = false;
public void Dispose()
{
if (Driver != null)
{
var status = _testPassed ? "passed" : "failed";
((IJavaScriptExecutor)Driver).ExecuteScript($"tb:test-result={status}");
Driver.Dispose();
}
}
}
### 3. Step Definitions
Create `StepDefinitions/CalculatorSteps.cs`:
using OpenQA.Selenium.Appium;
using Reqnroll;
using AppiumReqnrollTests.Support;
namespace AppiumReqnrollTests.StepDefinitions;
[Binding]
public class CalculatorSteps
{
private readonly TestingBotDriver _tbDriver;
public CalculatorSteps(TestingBotDriver tbDriver)
{
_tbDriver = tbDriver;
}
[Given("I am using the calculator")]
public void GivenIAmUsingTheCalculator()
{
if (_tbDriver.Driver == null)
_tbDriver.Initialize();
}
[When(@"I add inputA for ""(.*)""")]
public void WhenIAddInputA(string amount)
{
var inputA = _tbDriver.Driver.FindElement(AppiumBy.AccessibilityId("inputA"));
inputA.SendKeys(amount);
}
[When(@"I add inputB for ""(.*)""")]
public void WhenIAddInputB(string amount)
{
var inputB = _tbDriver.Driver.FindElement(AppiumBy.AccessibilityId("inputB"));
inputB.SendKeys(amount);
}
[Then(@"I should see the sum ""(.*)""")]
public void ThenIShouldSeeTheSum(string sum)
{
var sumElement = _tbDriver.Driver.FindElement(AppiumBy.AccessibilityId("sum"));
Assert.That(sumElement.Text, Is.EqualTo(sum));
}
}
### 4. Hooks for Setup/Teardown
Create `Hooks/TestHooks.cs` to manage the driver lifecycle:
using Reqnroll;
using AppiumReqnrollTests.Support;
namespace AppiumReqnrollTests.Hooks;
[Binding]
public class TestHooks
{
private readonly TestingBotDriver _tbDriver;
public TestHooks(TestingBotDriver tbDriver)
{
_tbDriver = tbDriver;
}
[AfterScenario]
public void AfterScenario(ScenarioContext scenarioContext)
{
if (scenarioContext.TestError != null)
{
_tbDriver.MarkTestFailed();
}
_tbDriver.Dispose();
}
}
Run your tests with:
TB_KEY=your_key TB_SECRET=your_secret dotnet test
## Real Device Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
The example below runs the Reqnroll scenario on a real Android device. Set `realDevice: true` inside `tb:options` to target physical hardware.
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
namespace AppiumReqnrollTests.Support;
public class TestingBotDriver : IDisposable
{
public AndroidDriver Driver { get; private set; }
private bool _testPassed = true;
public void Initialize()
{
var options = new AppiumOptions
{
PlatformName = "Android",
AutomationName = "UiAutomator2",
App = "https://testingbot.com/appium/sample.apk"
};
options.AddAdditionalAppiumOption("appium:deviceName", "Galaxy S24");
options.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TB_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TB_SECRET") ?? "",
["name"] = "Reqnroll Android Real Device",
["realDevice"] = true
});
Driver = new AndroidDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options,
TimeSpan.FromSeconds(120)
);
}
public void MarkTestFailed() => _testPassed = false;
public void Dispose()
{
if (Driver != null)
{
var status = _testPassed ? "passed" : "failed";
((IJavaScriptExecutor)Driver).ExecuteScript($"tb:test-result={status}");
Driver.Dispose();
}
}
}
The example below runs the Reqnroll scenario on a real iPhone. Use `IOSDriver` instead of `AndroidDriver`.
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.iOS;
namespace AppiumReqnrollTests.Support;
public class TestingBotDriver : IDisposable
{
public IOSDriver Driver { get; private set; }
private bool _testPassed = true;
public void Initialize()
{
var options = new AppiumOptions
{
PlatformName = "iOS",
AutomationName = "XCUITest",
App = "https://testingbot.com/appium/sample.ipa"
};
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 15");
options.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TB_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TB_SECRET") ?? "",
["name"] = "Reqnroll iOS Real Device",
["realDevice"] = true
});
Driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options,
TimeSpan.FromSeconds(300)
);
}
public void MarkTestFailed() => _testPassed = false;
public void Dispose()
{
if (Driver != null)
{
var status = _testPassed ? "passed" : "failed";
((IJavaScriptExecutor)Driver).ExecuteScript($"tb:test-result={status}");
Driver.Dispose();
}
}
}
## Simulator/Emulator Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
Run the same Reqnroll scenario on an Android emulator. Omit `realDevice` from `tb:options` to target an emulator.
var options = new AppiumOptions
{
PlatformName = "Android",
AutomationName = "UiAutomator2",
App = "https://testingbot.com/appium/sample.apk"
};
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 8");
options.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TB_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TB_SECRET") ?? "",
["name"] = "Reqnroll Android Emulator"
});
Driver = new AndroidDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options,
TimeSpan.FromSeconds(240)
);
Run the same Reqnroll scenario on an iOS simulator.
var options = new AppiumOptions
{
PlatformName = "iOS",
AutomationName = "XCUITest",
App = "https://testingbot.com/appium/sample.zip"
};
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 15");
options.AddAdditionalAppiumOption("appium:platformVersion", "17.4");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["key"] = Environment.GetEnvironmentVariable("TB_KEY") ?? "",
["secret"] = Environment.GetEnvironmentVariable("TB_SECRET") ?? "",
["name"] = "Reqnroll iOS Simulator"
});
Driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options,
TimeSpan.FromSeconds(300)
);
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Browsers & Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote machines. If the test was running on your local machine or network, you can simply change your existing test like this:
**Before:**
Driver = new IOSDriver(
new Uri("http://localhost:4723/wd/hub"),
options
);
**After:**
Driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options
);
### Configuring Capabilities
Capabilities are key-value pairs that allow you to customize your tests on TestingBot.
Appium provides its [own set of capabilities](https://appium.io/docs/en/2.0/guides/caps/) which you can specify.
TestingBot also provides its own [Custom Capabilities](https://testingbot.com/support/app-automate/appium/options), to customize tests run on the TestingBot platform.
You can use the drop-down menus below to see how to configure your tests to run on a specific mobile device:
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged or internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a Reqnroll test with our Tunnel:
1. [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
2. Adjust your test: instead of pointing to `'hub.testingbot.com/wd/hub'` like the example above, change it to point to your tunnel's IP address.
Assuming you run the tunnel on the same machine you run your tests, change to `'localhost:4445/wd/hub'`. localhost is the machine running the tunnel, 4445 is the default port of the tunnel.
This way your test will go securely through the tunnel to TestingBot and back:
var options = new AppiumOptions
{
PlatformName = "Android",
AutomationName = "UiAutomator2",
App = "tb://..."
};
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 8");
options.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
options.AddAdditionalAppiumOption("tb:options", new Dictionary
{
["name"] = "Reqnroll Tunnel Test"
});
// Point to the local tunnel instead of hub.testingbot.com
Driver = new AndroidDriver(
new Uri("http://localhost:4445/wd/hub"),
options
);
## Run tests in Parallel
Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.
Reqnroll supports parallel test execution when using NUnit or xUnit as the test runner.
### NUnit Parallel Execution
Add to your `AssemblyInfo.cs` or any file:
using NUnit.Framework;
[assembly: Parallelizable(ParallelScope.Fixtures)]
[assembly: LevelOfParallelism(5)]
Configure in `reqnroll.json`:
{
"$schema": "https://schemas.reqnroll.net/reqnroll-config-latest.json",
"bindingCulture": {
"name": "en-US"
}
}
### xUnit Parallel Execution
Add `xunit.runner.json`:
{
"parallelizeAssembly": true,
"parallelizeTestCollections": true,
"maxParallelThreads": 5
}
### Queuing
Every TestingBot plan has a limit on parallel tests. If you exceed this limit, additional tests are queued (up to 6 minutes) and run as slots become available.
## Mark tests as passed/failed
Report test results to TestingBot using JavaScript execution in your `[AfterScenario]` hook:
[Binding]
public class TestHooks
{
private readonly TestingBotDriver _tbDriver;
public TestHooks(TestingBotDriver tbDriver)
{
_tbDriver = tbDriver;
}
[AfterScenario]
public void AfterScenario(ScenarioContext scenarioContext)
{
if (_tbDriver.Driver != null)
{
var status = scenarioContext.TestError == null ? "passed" : "failed";
((IJavaScriptExecutor)_tbDriver.Driver).ExecuteScript($"tb:test-result={status}");
_tbDriver.Driver.Dispose();
}
}
}
## Migrating from SpecFlow
Reqnroll is designed as a drop-in replacement for SpecFlow. Most Appium projects can migrate with minimal changes:
### 1. Update NuGet Packages
# Remove SpecFlow packages
dotnet remove package SpecFlow
dotnet remove package SpecFlow.NUnit
# Add Reqnroll packages
dotnet add package Reqnroll.NUnit
### 2. Update Namespaces
Replace `TechTalk.SpecFlow` with `Reqnroll` in your code:
// Before (SpecFlow)
using TechTalk.SpecFlow;
// After (Reqnroll)
using Reqnroll;
### 3. Update Configuration
Rename `specflow.json` to `reqnroll.json` and update the schema reference:
{
"$schema": "https://schemas.reqnroll.net/reqnroll-config-latest.json"
}
### 4. Update Visual Studio Extension
Install the "Reqnroll for Visual Studio 2022" extension and uninstall the SpecFlow extension.
For a complete migration guide, see the [official SpecFlow to Reqnroll migration guide](https://reqnroll.net/news/2024/02/from-specflow-to-reqnroll-why-and-how/).
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/csharp/nunit
# NUnit Automated App Testing
NUnit is a unit-testing framework for all .NET languages. You can use NUnit to run tests on mobile devices, together with Appium.
This documentation page will show you how to set up and run Appium tests with NUnit on TestingBot's remote physical device grid. Once you've finished integrating, you will be able to run tests on iOS and Android devices, in parallel, on the remote TestingBot device grid.
## Installation
We'll assume you are using Visual Studio to run your tests.
Make sure to add these NuGet packages:
- `Appium.WebDriver`
- `NUnit`
## Initialize remote Appium Webdriver
To be able to connect to TestingBot's device grid, we'll need to set up a remote Appium WebDriver. Please see the example file (`TestingBotDriver`) below on how to do this.
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
using System;
using System.Collections.Generic;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
namespace testingbot
{
[TestFixture]
public class TestingBotAndroidTest
{
private AndroidDriver driver;
[SetUp]
public void SetUp()
{
AppiumOptions appiumOptions = new AppiumOptions();
appiumOptions.PlatformName = "Android";
appiumOptions.AutomationName = "UiAutomator2";
appiumOptions.AddAdditionalAppiumOption("appium:deviceName", "Galaxy S23");
appiumOptions.AddAdditionalAppiumOption("appium:platformVersion", "13.0");
appiumOptions.AddAdditionalAppiumOption("appium:app", "tb://...");
string key = "TESTINGBOT_KEY";
string secret = "TESTINGBOT_SECRET";
var tbOptions = new Dictionary
{
["key"] = key,
["secret"] = secret,
["realDevice"] = true
};
appiumOptions.AddAdditionalAppiumOption("tb:options", tbOptions);
driver = new AndroidDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions
);
}
[Test]
public void SampleTest()
{
driver.FindElement(AppiumBy.AccessibilityId("inputA")).SendKeys("10");
driver.FindElement(AppiumBy.AccessibilityId("inputB")).SendKeys("5");
Assert.Pass();
}
[TearDown]
public void TearDown()
{
if (driver != null)
{
driver.Quit();
}
}
}
}
using System;
using System.Collections.Generic;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.iOS;
namespace testingbot
{
[TestFixture]
public class TestingBotIOSTest
{
private IOSDriver driver;
[SetUp]
public void SetUp()
{
AppiumOptions appiumOptions = new AppiumOptions();
appiumOptions.PlatformName = "iOS";
appiumOptions.AutomationName = "XCUITest";
appiumOptions.AddAdditionalAppiumOption("appium:deviceName", "iPhone 15");
appiumOptions.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
appiumOptions.AddAdditionalAppiumOption("appium:app", "tb://...");
string key = "TESTINGBOT_KEY";
string secret = "TESTINGBOT_SECRET";
var tbOptions = new Dictionary
{
["key"] = key,
["secret"] = secret,
["realDevice"] = true
};
appiumOptions.AddAdditionalAppiumOption("tb:options", tbOptions);
driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions
);
}
[Test]
public void SampleTest()
{
driver.FindElement(AppiumBy.AccessibilityId("inputA")).SendKeys("10");
driver.FindElement(AppiumBy.AccessibilityId("inputB")).SendKeys("5");
Assert.Pass();
}
[TearDown]
public void TearDown()
{
if (driver != null)
{
driver.Quit();
}
}
}
}
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote machines. If the test was running on your local machine or network, you can simply change your existing test like this:
**Before:**
IOSDriver driver = new IOSDriver(
new Uri("http://localhost:4723/wd/hub"),
appiumOptions
);
**After:**
IOSDriver driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions
);
### Configuring Capabilities
Capabilities are key-value pairs that allows you to customize your tests on TestingBot.
Appium provides its [own set of capabilities](https://appium.io/docs/en/2.0/guides/caps/) which you can specify.
TestingBot also provide its own [Custom Capabilities](https://testingbot.com/support/app-automate/appium/options), to customize tests ran on the TestingBot platform.
You can use the drop-down menus below to see how to configure your tests to run on a specific mobile device:
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a NUnit C# test with our Tunnel:
1. [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
2. Adjust your test: instead of pointing to `'hub.testingbot.com/wd/hub'` like the example above - change it to point to your tunnel's IP address.
Assuming you run the tunnel on the same machine you run your tests, change to `'localhost:4445/wd/hub'`. localhost is the machine running the tunnel, 4445 is the default port of the tunnel.
This way your test will go securely through the tunnel to TestingBot and back:
using System;
using System.Collections.Generic;
using NUnit.Framework;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
namespace AppiumTest
{
[TestFixture]
public class LocalTunnelTests
{
[Test]
public void TestLocal()
{
AppiumOptions appiumOptions = new AppiumOptions();
appiumOptions.PlatformName = "Android";
appiumOptions.AutomationName = "UiAutomator2";
appiumOptions.AddAdditionalAppiumOption("appium:deviceName", "Galaxy S23");
appiumOptions.AddAdditionalAppiumOption("appium:platformVersion", "13.0");
appiumOptions.AddAdditionalAppiumOption("appium:app", "tb://...");
var tbOptions = new Dictionary
{
["name"] = "Tunnel Test"
};
appiumOptions.AddAdditionalAppiumOption("tb:options", tbOptions);
// Point to local tunnel instead of hub.testingbot.com
AndroidDriver driver = new AndroidDriver(
new Uri("http://localhost:4445/wd/hub"),
appiumOptions
);
// Perform your app test actions here
driver.Quit();
}
}
}
## Run tests in Parallel
Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.
You can run the same tests on all different device configurations or run different tests all on the same device configuration.
TestingBot has a large grid of machines and browsers, which means you can use our service to do efficient parallel testing. It is one of the key features we provide to greatly cut down on your total testing time.
### Queuing
Every plan we provide comes with a limit of parallel tests.
If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.
## Mark tests as passed/failed
To see if a test passed or not in our member area, or to send additional meta-data to TestingBot, you can use our API.
Please see the example below on how to notify TestingBot about the test success state:
[TearDown]
public void CleanUp()
{
bool passed = TestContext.CurrentContext.Result.Status == TestStatus.Passed;
try
{
// Logs the result to TestingBot
((IJavaScriptExecutor)driver).ExecuteScript("tb:test-result=" + (passed ? "passed" : "failed"));
}
finally
{
// Terminates the remote webdriver session
driver.Quit();
}
}
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/flutter
# Automated Flutter App Testing
TestingBot supports automated testing of Flutter apps via [Appium's Flutter Driver](https://github.com/appium-userland/appium-flutter-driver).
To get started, please make sure to follow these steps:
1. Compile your Flutter app in `debug` or `profile` mode.
Appium's Flutter driver does not currently support apps built in `release` mode.
2. Make sure your app's `pubspec.yaml` file contains:
3. Import the new `dev_dependencies` that you have added in the previous step:
4. Next, import the `flutter_driver_extension` library in your `main.dart` file:
5. Your `main.dart` file should contain `enableFlutterDriverExtension()` before `runApp`.
6. The `automationName` in your desired capabilities should be set to `Flutter`.
## Preparing your App
You can build an `.apk` file for Android, `.ipa` file for iOS physical devices or `.app` bundle for iOS Simulator testing.
This file needs to be [uploaded](https://testingbot.com#upload) to TestingBot for automated or manual testing.
Please see the table below on how to build your app:
| OS | Build Mode | Command |
| --- | --- | --- |
| Android | debug |
flutter build apk --debug
|
| | profile |
flutter build apk --profile
|
| iOS | debug |
flutter build ios --debug
|
| | simulator |
flutter build ios --simulator
|
| | profile |
flutter build ios --profile
|
- For iOS 14 and higher, please build the app in `profile` mode only.
- Release mode is not supported by Appium's Flutter Driver.
- For iOS real device testing, please use `flutter build ipa`, then do:
mkdir Payload
mv Runner.app Payload/
zip --symlinks -qr test-app.ipa Payload
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Example
TestingBot has created a [flutter-demo-app](https://github.com/testingbot/flutter-demo-app) which you can use to run your first Flutter Automated test with TestingBot.
Please see the example below where we run an automated Flutter test on TestingBot.
const { remote } = require('webdriverio');
const assert = require('assert');
const { byValueKey } = require('appium-flutter-finder');
const capabilities = {
'platformName': 'Android',
'appium:deviceName': 'Pixel 8',
'appium:platformVersion': '14.0',
'appium:automationName': 'Flutter',
'appium:app': 'https://github.com/testingbot/flutter-demo-app/releases/download/v1.0.0/demo.apk',
'tb:options': {
'realDevice': true
}
};
(async () => {
const driver = await remote({
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: 'api_key',
key: 'api_secret',
capabilities
});
const counterTextFinder = byValueKey('counter');
const buttonFinder = byValueKey('incrementButton');
// appium-flutter-driver exposes Flutter actions via execute('flutter:...')
assert.strictEqual(await driver.execute('flutter:getText', counterTextFinder), '0');
await driver.execute('flutter:tap', buttonFinder);
await driver.execute('flutter:tap', buttonFinder);
assert.strictEqual(await driver.execute('flutter:getText', counterTextFinder), '2');
await driver.deleteSession();
})();
This example test will open the [flutter-demo-app](https://github.com/testingbot/flutter-demo-app), find both the counter number and increment button by `valueKey`.
It will then verify the initial number, click the button twice and verify if the number is correct.
You can use the following demo applications to try out automated Flutter testing:
- For iOS simulator testing:
https://github.com/testingbot/flutter-demo-app/releases/download/v1.0.0/demo-ios-simulator.zip
- For iOS physical device testing:
https://github.com/testingbot/flutter-demo-app/releases/download/v1.0.0/demo.ipa
- For Android testing:
https://github.com/testingbot/flutter-demo-app/releases/download/v1.0.0/demo.apk
## Specify Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote devices. If the test was running on your local machine or network, you can simply change your existing test like this:
To let TestingBot know on which device you want to run your test on, you need to specify the `appium:deviceName`, `appium:platformVersion`, `platformName` and other optional options in the capabilities field.
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Additional Options
Appium provides [multiple Flutter capabilities](https://github.com/appium/appium-flutter-driver#capabilities) to configure your test.
Additionally, you can use [Flutter Finders](https://github.com/appium/appium-flutter-driver#Finders) and [Flutter Commands](https://github.com/appium/appium-flutter-driver#commands).
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/java
### Java Examples:
- [TestNG](https://testingbot.com/support/app-automate/appium/java/testng)
# JUnit Automated App Testing
See our [JUnit example repository](https://github.com/testingbot/java-junit-app-example) for a simple example on how to run JUnit mobile app tests.
## Installation
First, make sure you install the necessary dependencies to run a test. For this example, we'll be using [JUnit 5](https://junit.org/junit5/) and the [Appium Java Client](https://github.com/appium/java-client).
Add the following dependencies to your `pom.xml`:
org.junit.jupiter
junit-jupiter
5.10.2
test
io.appium
java-client
10.0.0
test
**Note:** Appium Java Client 10.x requires Java 11 or higher.
## JUnit Example
The example below shows a complete JUnit 5 test that connects to the TestingBot hub and runs a test on a real device. In this test, we'll test a sample calculator app by entering values into two input fields and verifying the sum.
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
import io.appium.java_client.AppiumBy;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import org.junit.jupiter.api.*;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class AndroidAppTest {
private AndroidDriver driver;
@BeforeEach
public void setUp() throws MalformedURLException {
// Configure capabilities using UiAutomator2Options
UiAutomator2Options options = new UiAutomator2Options()
.setPlatformName("Android")
.setPlatformVersion("16.0")
.setDeviceName("Pixel 9")
.setApp("tb://")
.setAutomationName("UiAutomator2");
// Add TestingBot-specific options
options.setCapability("tb:options", java.util.Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET",
"name", "Android Calculator Test",
"build", "JUnit Build 1"
));
// Connect to TestingBot hub
driver = new AndroidDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
}
@Test
public void testCalculatorSum() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
// Enter first number
WebElement inputA = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputA"))
);
inputA.sendKeys("10");
// Enter second number
WebElement inputB = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputB"))
);
inputB.sendKeys("5");
// Verify the sum
WebElement sum = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("sum"))
);
assertEquals("15", sum.getText());
}
@AfterEach
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
import io.appium.java_client.AppiumBy;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import org.junit.jupiter.api.*;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class IOSAppTest {
private IOSDriver driver;
@BeforeEach
public void setUp() throws MalformedURLException {
// Configure capabilities using XCUITestOptions
XCUITestOptions options = new XCUITestOptions()
.setPlatformName("iOS")
.setPlatformVersion("26.0")
.setDeviceName("iPhone 17")
.setApp("tb://")
.setAutomationName("XCUITest");
// Add TestingBot-specific options
options.setCapability("tb:options", java.util.Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET",
"name", "iOS Calculator Test",
"build", "JUnit Build 1"
));
// Connect to TestingBot hub
driver = new IOSDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
}
@Test
public void testCalculatorSum() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
// Enter first number
WebElement inputA = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputA"))
);
inputA.sendKeys("10");
// Enter second number
WebElement inputB = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputB"))
);
inputB.sendKeys("5");
// Verify the sum
WebElement sum = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("sum"))
);
assertEquals("15", sum.getText());
}
@AfterEach
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
Always call `driver.quit()` in your teardown method to properly end the session. Otherwise, the test will continue running until it times out.
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Browsers & Devices
To run your tests on TestingBot, you need to configure the device and platform you want to test on. The Appium Java client provides type-safe Options classes for this purpose.
### Using Options Classes (Recommended)
For Android testing, use `UiAutomator2Options`. For iOS testing, use `XCUITestOptions`:
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
UiAutomator2Options options = new UiAutomator2Options()
.setPlatformName("Android")
.setPlatformVersion("16.0")
.setDeviceName("Pixel 9")
.setApp("tb://")
.setAutomationName("UiAutomator2");
// Add TestingBot credentials
options.setCapability("tb:options", Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET",
"name", "My Test",
"build", "Build 1"
));
AndroidDriver driver = new AndroidDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
XCUITestOptions options = new XCUITestOptions()
.setPlatformName("iOS")
.setPlatformVersion("26.0")
.setDeviceName("iPhone 17")
.setApp("tb://")
.setAutomationName("XCUITest");
// Add TestingBot credentials
options.setCapability("tb:options", Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET",
"name", "My Test",
"build", "Build 1"
));
IOSDriver driver = new IOSDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
### Using BaseOptions (Generic)
For more flexibility or when working with multiple platforms, you can use `BaseOptions`:
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.remote.options.BaseOptions;
import org.openqa.selenium.MutableCapabilities;
import java.net.URL;
import java.util.Map;
BaseOptions> options = new BaseOptions<>()
.setPlatformName("Android")
.amend("appium:automationName", "UiAutomator2")
.amend("appium:deviceName", "Pixel 9")
.amend("appium:platformVersion", "16.0")
.amend("appium:app", "tb://");
options.setCapability("tb:options", Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET"
));
AppiumDriver driver = new AppiumDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
### Required Capabilities
To specify which device you want to test on, you need to provide:
- `platformName` - The mobile OS (Android or iOS)
- `appium:platformVersion` - The OS version (e.g., "17.0", "26.0")
- `appium:deviceName` - The device name (e.g., "Pixel 9", "iPhone 17")
- `appium:app` - The app to test (URL or tb:// reference)
- `appium:automationName` - The automation driver ("UiAutomator2" for Android, "XCUITest" for iOS)
Use the device picker below to generate the configuration for your target device:
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a JUnit test with our Tunnel:
**1.** [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
**2.** Update your test to connect through the tunnel instead of directly to the TestingBot hub. Change the URL from `hub.testingbot.com` to `localhost:4445`:
// Connect through the tunnel instead of directly to TestingBot
AndroidDriver driver = new AndroidDriver(
new URL("http://localhost:4445/wd/hub"),
options
);
Your test traffic will now go securely through the tunnel, allowing you to test apps that access internal resources.
## Run tests in Parallel
Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.
You can run the same tests on all different browser configurations or run different tests all on the same browser configuration.
TestingBot has a large grid of machines and browsers, which means you can use our service to do efficient parallel testing. It is one of the key features we provide to greatly cut down on your total testing time.
Please see our [Parallel JUnit documentation](https://testingbot.com/support/web-automate/selenium/java/parallel-junit) for parallel testing.
### Queuing
Every plan we provide comes with a limit of parallel tests.
If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.
## Mark tests as passed/failed
TestingBot cannot automatically determine if your test passed or failed - that depends on your test assertions. To report test results to TestingBot, use our [REST API](https://testingbot.com/support/api).
First, add the [TestingBot Java client](https://github.com/testingbot/testingbot-java) to your dependencies:
com.testingbot
testingbotrest
1.0.10
Then use it in your test to report the result:
import com.testingbot.comingbotrest.TestingbotREST;
import java.util.Map;
@AfterEach
public void tearDown() {
if (driver != null) {
// Report test result to TestingBot
TestingbotREST api = new TestingbotREST(
"YOUR_TB_KEY",
"YOUR_TB_SECRET"
);
api.updateTest(
driver.getSessionId().toString(),
Map.of(
"success", "1", // "1" for passed, "0" for failed
"name", "Calculator Test"
)
);
driver.quit();
}
}
For JUnit 5, you can use a `TestWatcher` extension to automatically report results:
import org.junit.jupiter.api.extension.*;
public class TestingBotWatcher implements TestWatcher {
private final AndroidDriver driver;
private final TestingbotREST api;
public TestingBotWatcher(AndroidDriver driver, String key, String secret) {
this.driver = driver;
this.api = new TestingbotREST(key, secret);
}
@Override
public void testSuccessful(ExtensionContext context) {
updateTest("1", context.getDisplayName());
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
updateTest("0", context.getDisplayName());
}
private void updateTest(String success, String name) {
api.updateTest(driver.getSessionId().toString(), Map.of(
"success", success,
"name", name
));
}
}
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/java/testng
# TestNG Automated App Testing
See our [TestNG example repository](https://github.com/testingbot/java-testng-app-example) for a simple example on how to run TestNG mobile app tests.
## Installation
First, make sure you install the necessary dependencies to run a test. For this example, we'll be using [TestNG](https://testng.org/) and the [Appium Java Client](https://github.com/appium/java-client).
Add the following dependencies to your `pom.xml`:
org.testng
testng
7.11.0
test
io.appium
java-client
10.0.0
test
**Note:** Appium Java Client 10.x requires Java 11 or higher.
## TestNG Example
The example below shows a complete TestNG test that connects to the TestingBot hub and runs a test on a real device. In this test, we'll test a sample calculator app by entering values into two input fields and verifying the sum.
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
import io.appium.java_client.AppiumBy;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.Map;
public class AndroidAppTest {
private AndroidDriver driver;
@BeforeMethod
public void setUp() throws MalformedURLException {
// Configure capabilities using UiAutomator2Options
UiAutomator2Options options = new UiAutomator2Options()
.setPlatformName("Android")
.setPlatformVersion("16.0")
.setDeviceName("Pixel 9")
.setApp("tb://")
.setAutomationName("UiAutomator2");
// Add TestingBot-specific options
options.setCapability("tb:options", Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET",
"name", "Android Calculator Test",
"build", "TestNG Build 1"
));
// Connect to TestingBot hub
driver = new AndroidDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
}
@Test
public void testCalculatorSum() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
// Enter first number
WebElement inputA = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputA"))
);
inputA.sendKeys("10");
// Enter second number
WebElement inputB = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputB"))
);
inputB.sendKeys("5");
// Verify the sum
WebElement sum = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("sum"))
);
Assert.assertEquals(sum.getText(), "15");
}
@AfterMethod
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
import io.appium.java_client.AppiumBy;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.Map;
public class IOSAppTest {
private IOSDriver driver;
@BeforeMethod
public void setUp() throws MalformedURLException {
// Configure capabilities using XCUITestOptions
XCUITestOptions options = new XCUITestOptions()
.setPlatformName("iOS")
.setPlatformVersion("26.0")
.setDeviceName("iPhone 17")
.setApp("tb://")
.setAutomationName("XCUITest");
// Add TestingBot-specific options
options.setCapability("tb:options", Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET",
"name", "iOS Calculator Test",
"build", "TestNG Build 1"
));
// Connect to TestingBot hub
driver = new IOSDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
}
@Test
public void testCalculatorSum() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
// Enter first number
WebElement inputA = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputA"))
);
inputA.sendKeys("10");
// Enter second number
WebElement inputB = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputB"))
);
inputB.sendKeys("5");
// Verify the sum
WebElement sum = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("sum"))
);
Assert.assertEquals(sum.getText(), "15");
}
@AfterMethod
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
Always call `driver.quit()` in your `@AfterMethod` to properly end the session. Otherwise, the test will continue running until it times out.
## Reusable Base Test Class
For cleaner test organization, you can create a reusable base class that handles driver setup and teardown. Tests can then extend this base class and focus only on test logic.
### Base Class
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import org.testng.annotations.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
public class TestingBotBaseTest {
protected AndroidDriver driver;
@BeforeMethod(alwaysRun = true)
@Parameters({"platformVersion", "deviceName", "app"})
public void setUp(
@Optional("16.0") String platformVersion,
@Optional("Pixel 9") String deviceName,
@Optional("tb://") String app
) throws MalformedURLException {
// Get credentials from environment or use defaults
String key = System.getenv("TB_KEY");
String secret = System.getenv("TB_SECRET");
if (key == null || secret == null) {
throw new RuntimeException("TB_KEY and TB_SECRET environment variables must be set");
}
// Configure capabilities
UiAutomator2Options options = new UiAutomator2Options()
.setPlatformName("Android")
.setPlatformVersion(platformVersion)
.setDeviceName(deviceName)
.setApp(app)
.setAutomationName("UiAutomator2");
options.setCapability("tb:options", Map.of(
"key", key,
"secret", secret,
"name", this.getClass().getSimpleName()
));
driver = new AndroidDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
}
@AfterMethod(alwaysRun = true)
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
### Test Class Using the Base
import io.appium.java_client.AppiumBy;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.time.Duration;
public class CalculatorTest extends TestingBotBaseTest {
@Test
public void testAddition() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
WebElement inputA = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputA"))
);
inputA.sendKeys("10");
WebElement inputB = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputB"))
);
inputB.sendKeys("5");
WebElement sum = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("sum"))
);
Assert.assertEquals(sum.getText(), "15");
}
}
### TestNG XML Configuration
Use a `testng.xml` file to pass parameters and run tests on multiple devices:
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Browsers & Devices
To run your tests on TestingBot, you need to configure the device and platform you want to test on. The Appium Java client provides type-safe Options classes for this purpose.
### Using Options Classes (Recommended)
For Android testing, use `UiAutomator2Options`. For iOS testing, use `XCUITestOptions`:
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
UiAutomator2Options options = new UiAutomator2Options()
.setPlatformName("Android")
.setPlatformVersion("16.0")
.setDeviceName("Pixel 9")
.setApp("tb://")
.setAutomationName("UiAutomator2");
// Add TestingBot credentials
options.setCapability("tb:options", Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET",
"name", "My Test",
"build", "Build 1"
));
AndroidDriver driver = new AndroidDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
XCUITestOptions options = new XCUITestOptions()
.setPlatformName("iOS")
.setPlatformVersion("26.0")
.setDeviceName("iPhone 17")
.setApp("tb://")
.setAutomationName("XCUITest");
// Add TestingBot credentials
options.setCapability("tb:options", Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET",
"name", "My Test",
"build", "Build 1"
));
IOSDriver driver = new IOSDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
### Using BaseOptions (Generic)
For more flexibility or when working with multiple platforms, you can use `BaseOptions`:
import io.appium.java_client.remote.options.BaseOptions;
BaseOptions options = new BaseOptions()
.setPlatformName("Android")
.setAutomationName("UiAutomator2")
.amend("appium:deviceName", "Pixel 9")
.amend("appium:platformVersion", "16.0")
.amend("appium:app", "tb://");
options.setCapability("tb:options", Map.of(
"key", "YOUR_TB_KEY",
"secret", "YOUR_TB_SECRET"
));
AppiumDriver driver = new AppiumDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
### Required Capabilities
To specify which device you want to test on, you need to provide:
- `platformName` - The mobile OS (Android or iOS)
- `appium:platformVersion` - The OS version (e.g., "16.0", "26.0")
- `appium:deviceName` - The device name (e.g., "Pixel 9", "iPhone 17")
- `appium:app` - The app to test (URL or tb:// reference)
- `appium:automationName` - The automation driver ("UiAutomator2" for Android, "XCUITest" for iOS)
Use the device picker below to generate the configuration for your target device:
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a TestNG test with our Tunnel:
**1.** [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
**2.** Update your test to connect through the tunnel instead of directly to the TestingBot hub. Change the URL from `hub.testingbot.com` to `localhost:4445`:
// Connect through the tunnel instead of directly to TestingBot
AndroidDriver driver = new AndroidDriver(
new URL("http://localhost:4445/wd/hub"),
options
);
Your test traffic will now go securely through the tunnel, allowing you to test apps that access internal resources.
## Mark tests as passed/failed
TestingBot cannot automatically determine if your test passed or failed - that depends on your test assertions. To report test results to TestingBot, use our [REST API](https://testingbot.com/support/api).
First, add the [TestingBot Java client](https://github.com/testingbot/testingbot-java) to your dependencies:
com.testingbot
testingbotrest
1.0.10
Then use it in your test to report the result:
import com.testingbot.comingbotrest.TestingbotREST;
import java.util.Map;
@AfterMethod
public void tearDown() {
if (driver != null) {
// Report test result to TestingBot
TestingbotREST api = new TestingbotREST(
"YOUR_TB_KEY",
"YOUR_TB_SECRET"
);
api.updateTest(
driver.getSessionId().toString(),
Map.of(
"success", "1", // "1" for passed, "0" for failed
"name", "Calculator Test"
)
);
driver.quit();
}
}
For automatic result reporting, you can use a TestNG listener:
import org.testng.ITestListener;
import org.testng.ITestResult;
import com.testingbot.comingbotrest.TestingbotREST;
import java.util.Map;
public class TestingBotListener implements ITestListener {
private TestingbotREST api;
public TestingBotListener() {
this.api = new TestingbotREST(
System.getenv("TB_KEY"),
System.getenv("TB_SECRET")
);
}
@Override
public void onTestSuccess(ITestResult result) {
updateTestStatus(result, "1");
}
@Override
public void onTestFailure(ITestResult result) {
updateTestStatus(result, "0");
}
private void updateTestStatus(ITestResult result, String success) {
Object testInstance = result.getInstance();
if (testInstance instanceof TestingBotBaseTest) {
AndroidDriver driver = ((TestingBotBaseTest) testInstance).driver;
if (driver != null) {
api.updateTest(
driver.getSessionId().toString(),
Map.of(
"success", success,
"name", result.getMethod().getMethodName()
)
);
}
}
}
}
Register the listener in your `testng.xml`:
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/nodejs
### NodeJS Examples:
- [WebDriverIO](https://testingbot.com/support/app-automate/appium/nodejs/webdriverio)
- [CodeceptJS](https://testingbot.com/support/app-automate/appium/nodejs/codeceptjs)
# NodeJS Automated App Testing
First, make sure you have installed NodeJS. If you haven't, you can easily install it by downloading NodeJS from the [NodeJS website](https://nodejs.org/en/download/)
## Installation
With TestingBot you can easily run your automated tests with any NodeJS test framework, here's a simple example with **WebdriverIO** :
npm install webdriverio
## Real Device Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
This will download and install a sample Android app we built. It will add two numbers to a textinput and display the sum of the two numbers.
const { remote } = require('webdriverio');
const capabilities = {
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: "api_key",
key: "api_secret",
capabilities: {
'appium:deviceName': 'Pixel 9',
'platformName': 'Android',
'appium:platformVersion': '16.0',
'appium:automationName': 'UiAutomator2',
'appium:app': 'https://testingbot.com/appium/sample.apk',
'tb:options': {
'name': 'My First App Test',
'realDevice': true
}
}
};
(async () => {
const driver = await remote(capabilities);
const inputA = await driver.$('~inputA');
await inputA.waitForDisplayed({ timeout: 30000 });
await inputA.setValue('10');
const inputB = await driver.$('~inputB');
await inputB.waitForDisplayed({ timeout: 30000 });
await inputB.setValue('5');
const sum = await driver.$('~sum');
await sum.waitForDisplayed({ timeout: 30000 });
const value = await sum.getText();
console.log(value === '15' ? 'Test passed!' : 'Test failed!');
await driver.deleteSession();
})();
Make sure to always end your session (`await driver.deleteSession()`), otherwise it will continue running, leading to a timeout.
This will download and install a sample iOS app we built. It will add two numbers to a textinput and display the sum of the two numbers.
const { remote } = require('webdriverio');
const capabilities = {
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: "api_key",
key: "api_secret",
capabilities: {
'appium:deviceName': 'iPhone 17',
'platformName': 'iOS',
'appium:platformVersion': '26.0',
'appium:automationName': 'XCUITest',
'appium:app': 'https://testingbot.com/appium/sample.ipa',
'tb:options': {
'name': 'My First App Test',
'realDevice': true
}
}
};
(async () => {
const driver = await remote(capabilities);
const inputA = await driver.$('~inputA');
await inputA.waitForDisplayed({ timeout: 30000 });
await inputA.setValue('10');
const inputB = await driver.$('~inputB');
await inputB.waitForDisplayed({ timeout: 30000 });
await inputB.setValue('5');
await driver.deleteSession();
})();
Make sure to always end your session (`await driver.deleteSession()`), otherwise it will continue running, leading to a timeout.
## Simulator/Emulator Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
This will download and install a sample Android app we built. It will add two numbers to a textinput and display the sum of the two numbers.
const { remote } = require('webdriverio');
const capabilities = {
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: "api_key",
key: "api_secret",
capabilities: {
'appium:deviceName': 'Pixel 9',
'platformName': 'Android',
'appium:platformVersion': '16.0',
'appium:automationName': 'UiAutomator2',
'appium:app': 'https://testingbot.com/appium/sample.apk',
'tb:options': { 'name': 'My First App Test' }
}
};
(async () => {
const driver = await remote(capabilities);
const inputA = await driver.$('~inputA');
await inputA.waitForDisplayed({ timeout: 30000 });
await inputA.setValue('10');
const inputB = await driver.$('~inputB');
await inputB.waitForDisplayed({ timeout: 30000 });
await inputB.setValue('5');
const sum = await driver.$('~sum');
await sum.waitForDisplayed({ timeout: 30000 });
const value = await sum.getText();
console.log(value === '15' ? 'Test passed!' : 'Test failed!');
await driver.deleteSession();
})();
Make sure to always end your session (`await driver.deleteSession()`), otherwise it will continue running, leading to a timeout.
This will download and install a sample iOS app we built. It will add two numbers to a textinput and display the sum of the two numbers.
const { remote } = require('webdriverio');
const capabilities = {
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: "api_key",
key: "api_secret",
capabilities: {
'appium:deviceName': 'iPhone 17',
'platformName': 'iOS',
'appium:platformVersion': '26.0',
'appium:automationName': 'XCUITest',
'appium:app': 'https://testingbot.com/appium/sample.zip',
'tb:options': { 'name': 'My First App Test' }
}
};
(async () => {
const driver = await remote(capabilities);
const inputA = await driver.$('~inputA');
await inputA.waitForDisplayed({ timeout: 30000 });
await inputA.setValue('10');
const inputB = await driver.$('~inputB');
await inputB.waitForDisplayed({ timeout: 30000 });
await inputB.setValue('5');
await driver.deleteSession();
})();
Make sure to always end your session (`await driver.deleteSession()`), otherwise it will continue running, leading to a timeout.
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote machines. If the test was running on your local machine or network, you can simply change your existing test like this:
**Before:**
const capabilities = {
hostname: 'localhost',
port: 4723,
...
};
**After:**
const capabilities = {
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: 'api_key',
key: 'api_secret',
capabilities: {
'appium:app': 'tb://...'
}
};
To let TestingBot know on which device you want to run your test on, you need to specify the `appium:deviceName`, `appium:platformVersion`, `platformName` and other optional options in the capabilities field.
To see how to do this, please select a combination of device type and device name in the drop-down menus below.
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a NodeJS test with our Tunnel:
1. [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
2. Adjust your test: instead of pointing to `'hub.testingbot.com/wd/hub'` like the example above - change it to point to your tunnel's IP address.
Assuming you run the tunnel on the same machine you run your tests, change to `'localhost:4445/wd/hub'`. localhost is the machine running the tunnel, 4445 is the default port of the tunnel.
This way your test will go securely through the tunnel to TestingBot and back:
const { remote } = require('webdriverio');
const capabilities = {
hostname: 'localhost',
port: 4445,
protocol: 'http',
path: '/wd/hub',
user: "api_key",
key: "api_secret",
capabilities: {
'appium:deviceName': 'iPhone 17',
'platformName': 'iOS',
'appium:platformVersion': '26.0',
'appium:automationName': 'XCUITest',
'appium:app': 'https://testingbot.com/appium/sample.zip',
'tb:options': { 'name': 'My First App Test' }
}
};
(async () => {
const driver = await remote(capabilities);
const inputA = await driver.$('~inputA');
await inputA.waitForDisplayed({ timeout: 30000 });
await inputA.setValue('10');
const inputB = await driver.$('~inputB');
await inputB.waitForDisplayed({ timeout: 30000 });
await inputB.setValue('5');
await driver.deleteSession();
})();
## Run tests in Parallel
Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.
You can run the same tests on all different browser configurations or run different tests all on the same browser configuration.
TestingBot has a large grid of machines and browsers, which means you can use our service to do efficient parallel testing. It is one of the key features we provide to greatly cut down on your total testing time.
To run tests in parallel, we recommend using a test runner like [WebDriverIO](https://testingbot.com/support/app-automate/appium/nodejs/webdriverio) to run your tests simultaneously.
### Queuing
Every plan we provide comes with a limit of parallel tests.
If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.
## Mark tests as passed/failed
As TestingBot has no way to determine whether your test passed or failed (it is determined by your business logic), we offer a way to send the test status back to TestingBot. This is useful if you want to see if a test succeeded or failed from the TestingBot member area.
Install our NPM package to interact with the TestingBot API:
npm install testingbot-api
After your test completed, you can send back the test success state and other meta-data:
const TestingBot = require('testingbot-api');
const tb = new TestingBot({
api_key: "api_key",
api_secret: "api_secret"
});
// Obtain the session id from your WebDriver session
// const sessionId = driver.sessionId; // (WebdriverIO)
tb.updateTest({
name: 'Example Test',
success: true
}, sessionId, (err) => {
if (err) console.error(err);
});
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/nodejs/codeceptjs
# Getting started with CodeceptJS
[CodeceptJS](https://codecept.io/) is an end-to-end testing framework which supports Appium for automated mobile testing.
It offers scenario driven testing, PageObjects support, beautiful reports and much more.
Let's start with installing everything we need:
npm i --save-dev codeceptjs
Now we need to set up CodeceptJS. Please use the command below to initialize a new project:
npx codeceptjs init
This will ask a bunch of questions, and will create a sample test at the end of the process.
## Example test
Now that you have everything set up, you can create a very simple test:
Feature('My First Mobile App Test');
Scenario('test sample calculator', (I) => {
I.seeAppIsInstalled("com.testingbot.sample");
I.click('~inputA');
I.fillField('~inputA', '10');
I.click('~inputB');
I.fillField('~inputB', '5');
I.see('15', '~sum');
});
This simple test will test a calculator mobile app, it will fill in two numbers in two input fields and check the sum.
Before we can run this test, we need to configure CodeceptJS to connect to TestingBot.
## Configuration
You can configure CodeceptJS with a `codecept.conf.js` file:
exports.config = {
tests: './*_test.js',
output: './output',
helpers: {
Appium: {
host: "hub.testingbot.com",
port: 443,
protocol: "https",
path: "/wd/hub",
user: "api_key",
key: "api_secret",
capabilities: {
platformName: "Android",
"appium:app": "tb://...",
"appium:deviceName": "Pixel 8",
"appium:platformVersion": "14.0",
"appium:automationName": "UiAutomator2"
},
},
},
include: {
I: './steps_file.js'
},
bootstrap: null,
mocha: {},
name: 'codeceptjs-example',
plugins: {
retryFailedStep: {
enabled: true
},
screenshotOnFail: {
enabled: true
},
}
}
To run the test, you can do:
npx codeceptjs run --steps
This will start a Pixel 8 Emulator and run the test on our grid.
You will see a test appear in your member dashboard on TestingBot, together with a video, log files and other assets generated by the test.
## Configuring capabilities
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote machines. If the test was running on your local machine or network, you can simply change your existing test like this:
**Before**
// codecept.conf.js
exports.config = {
tests: './*_test.js',
output: './output',
helpers: {
Appium : {}
}
};
**After**
// codecept.conf.js
helpers: {
Appium: {
host: "hub.testingbot.com",
port: 443,
protocol: "https",
path: "/wd/hub",
user: "api_key",
key: "api_secret",
capabilities: {
platformName: "Android",
"appium:app": "tb://...",
"appium:deviceName": "Pixel 8",
"appium:platformVersion": "14.0",
"appium:automationName": "UiAutomator2"
},
},
},
### Specifying the device
To let TestingBot know on which device you want to run your test on, you need to specify the deviceName and platformName.
To see how to do this, please select a combination of deviceName and platformName in the drop-down menus below.
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Mark tests as passed/failed
CodeceptJS allows you to mark tests as passed or failed, which is useful for debugging and reporting purposes. You can use the `I.see()` and `I.dontSee()` methods to check for specific elements or text on the page. If the element is not found, the test will fail.
This is useful for checking if a specific element is present on the page, or if a specific text is visible. If the element or text is not found, the test will fail and you will see an error message in the report.
## Parallel Testing
Please see [CodeceptJS parallel testing](https://codecept.io/parallel/#parallel-execution-by-workers) on how to run your tests in parallel.
You can specify the concurrency by specifying a number of workers:
npx codeceptjs run-workers 2
or run multiple different devices with:
npx codeceptjs run-multiple --all
### Queuing
Every plan we provide comes with a limit of parallel tests.
If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/nodejs/webdriverio
# WebDriverIO Automated App Testing
First, make sure you have installed NodeJS. If you haven't, you can easily install it by [downloading NodeJS](https://nodejs.org/en/download/).
See our [WebdriverIO examples repository](https://github.com/testingbot/webdriverio-examples) for some examples on how to use WebdriverIO with TestingBot.
## Installation
TestingBot has its own [WebDriverIO service](https://webdriver.io/docs/testingbot-service.html) plugin which makes configuring your tests easier and which sends test meta-data back to TestingBot.
Let's start with making sure `webdriverio` is installed:
npm i --save-dev @wdio/cli @wdio/local-runner
You are now ready to run a WebDriver test with NodeJS on our grid.
## WebDriverIO TestingBot Service
WebDriverIO contains a [TestingBot service](https://webdriver.io/docs/testingbot-service.html) which makes it easy to run tests with WebDriverIO on TestingBot. With this service, WebDriverIO will start a [TestingBot Tunnel](https://testingbot.com/support/tunnel) if required, and send back test meta-data (passed state, name, build-id, ...)
In order to use the service you need to download it from NPM:
npm install @wdio/testingbot-service --save-dev
## Mocha Example
To run Mocha tests on TestingBot, please follow these steps:
npm install @wdio/testingbot-service --save-dev
npm install @wdio/dot-reporter --save-dev
npm install @wdio/mocha-framework --save-dev
Now we'll make a simple Mocha test that runs against a calculator app on a real Android device on TestingBot and verifies the sum of two numbers.
Please create the following files:
**wdio.conf.js**
exports.config = {
/**
* specify test files
*/
specs: [
'./runner-specs/mocha.test.js'
],
/**
* capabilities
*/
capabilities: [{
platformName: 'Android',
'appium:deviceName': 'Pixel 8',
'appium:platformVersion': '14.0',
'appium:app': 'https://testingbot.com/appium/sample.apk',
'tb:options': {
name: 'My First App Test'
}
}],
/**
* test configurations
*/
logLevel: 'silent',
waitforTimeout: 10000,
connectionRetryTimeout: 480000,
framework: 'mocha',
services: [
['testingbot']
],
user: 'api_key',
key: 'api_secret',
reporters: ['dot'],
mochaOpts: {
ui: 'bdd'
}
};
**runner-specs/mocha.test.js**
const assert = require('assert')
describe('calculator', function() {
it('should calculate a sum', async () => {
const inputA = await $('~inputA')
await inputA.waitForDisplayed(5000)
await inputA.click()
try {
await inputA.addValue('10')
} catch (e) {}
const inputB = await $('~inputB')
await inputB.waitForDisplayed(5000)
await inputB.click()
try {
await inputB.addValue('5')
} catch (e) {}
const sumElement = await $('~sum')
const sum = await sumElement.getText()
assert.equal(sum, '15') // 10 + 5
})
})
Now we can run this mobile app test on TestingBot. The test will do a simple sum calculation and verify if the calculation was correct.
To start the test, please run this command:
npx wdio run wdio.conf.js
The test will now run on TestingBot. At the end of the test, the testname and success state will be available on TestingBot, together with a video and other meta-data.
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote machines. If the test was running on your local machine or network, you can simply change your existing test like this:
**Before:**
exports.config = {
// ...
services: ['selenium-standalone'],
// ...
};
**After:**
exports.config = {
// ...
services: [
['testingbot']
],
user: process.env.TB_KEY,
key: process.env.TB_SECRET,
// ...
};
To let TestingBot know on which device you want to run your test on, you need to specify the `appium:deviceName`, `appium:platformVersion`, `platformName` and other optional options in the capabilities field.
To see how to do this, please select a combination of device type and device name in the drop-down menus below.
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a WebdriverIO test with our Tunnel:
1. Adjust your `wdio.conf.js` configuration to use `tbTunnel: true`.
The [wdio-testingbot-service](https://webdriver.io/docs/testingbot-service/) will automatically download and use the tunnel for your tests.
**wdio.conf.js**
exports.config = {
services: [
['testingbot', {
tbTunnel: true
}]
],
user: 'api_key',
key: 'api_secret'
};
## Run Tests in Parallel
Parallel testing allows you to run multiple tests at the same time, significantly reducing your total test execution time.
You can run the same test across different device configurations, or run different tests on the same configuration, all simultaneously.
TestingBot provides a large grid of browsers and devices, enabling you to run tests in parallel efficiently. This is one of our core features designed to help you test faster and ship more reliably.
With WebDriverIO, you can enable parallel test execution by setting the `maxInstances` property in your `wdio.conf.js` file and specifying multiple `capabilities`. This defines how many instances of the same browser can run in parallel. We recommend setting this value to match the number of parallel sessions allowed by your TestingBot plan.
exports.config = {
maxInstances: 5, // Adjust this based on your TestingBot plan
capabilities: [{
platformName: 'Android',
'appium:deviceName': 'Pixel 8',
'appium:platformVersion': '14.0',
'appium:app': 'https://testingbot.com/appium/sample.apk',
'tb:options': {
name: 'My First App Test'
}
},
{
platformName: 'Android',
'appium:deviceName': 'Pixel 9',
'appium:platformVersion': '15.0',
'appium:app': 'https://testingbot.com/appium/sample.apk',
'tb:options': {
name: 'My Second App Test'
}
}]
};
### Queuing
Every TestingBot plan includes a fixed number of parallel test slots.
If you exceed this limit, additional tests will be automatically queued and executed as soon as slots become available, no tests will be dropped or skipped.
## Mark tests as passed/failed
As TestingBot has no way to determine whether your test passed or failed (it is determined by your business logic), we offer a way to send the test status back to TestingBot. This is useful if you want to see if a test succeeded or failed from the TestingBot member area.
If you are using the [wdio-testingbot-service](https://webdriver.io/docs/testingbot-service/) then your tests will automatically report back meta-data to TestingBot (like test success, name, stacktrace, ...)
## Common errors
Below are some common errors you might encounter when running WebDriverIO tests on TestingBot:
- `UND_ERR_HEADERS_TIMEOUT`
**Problem:** This error indicates that WebdriverIO did not receive a response in time. This might happen when requesting a device that is currently in use, or in case you are trying to run more tests in parallel than your plan allows and the tests have been [queued](https://testingbot.com#queuing) for a long time.
**Solution:** You can try adding the `connectionRetryTimeout` in your WebDriverIO configuration with a high enough value:
connectionRetryTimeout: 480000
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/php
# PHPUnit Automated App Testing
See our [PHPUnit Mobile example repository](https://github.com/testingbot/php-phpunit-mobile-example) for a simple example on how to run PHPUnit tests for Mobile Apps on TestingBot.
Let's start with making sure PHP is available on your system.
**For Windows:**
- You can download PHP for Windows from [PHP For Windows](https://windows.php.net/download/).
- Run the installer and follow the setup wizard to install PHP.
**For Linux:**
sudo apt-get install curl libcurl3 libcurl3-dev php
**For macOS:**
PHP should already be present on macOS by default.
## Install PHPUnit and php-webdriver
The legacy `appium/php-client` and `phpunit/phpunit-selenium` packages are no longer maintained. The examples on this page use the actively-maintained [php-webdriver/webdriver](https://github.com/php-webdriver/php-webdriver) client, which speaks the W3C / Appium 2 protocol.
- Make sure you have [Composer installed](https://getcomposer.org/).
- Install PHPUnit and php-webdriver in your project:
## PHPUnit Example Real Devices
Please see the example below. We're going to test our sample app on a Real Device.
In this test we'll add values to two input fields and see if the calculator works.
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
setCapability('platformName', 'Android');
$caps->setCapability('appium:deviceName', 'Galaxy S24');
$caps->setCapability('appium:platformVersion', '14.0');
$caps->setCapability('appium:automationName', 'UiAutomator2');
$caps->setCapability('appium:app', 'https://testingbot.com/appium/sample.apk');
$caps->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'PHP Appium Example',
'realDevice' => true
]);
$driver = RemoteWebDriver::create('https://hub.testingbot.com/wd/hub', $caps, 120000);
$driver->findElement(WebDriverBy::accessibilityId('inputA'))->sendKeys('10');
$driver->findElement(WebDriverBy::accessibilityId('inputB'))->sendKeys('5');
echo $driver->findElement(WebDriverBy::accessibilityId('sum'))->getText();
$driver->quit();
setCapability('platformName', 'iOS');
$caps->setCapability('appium:deviceName', 'iPhone 15');
$caps->setCapability('appium:platformVersion', '18.0');
$caps->setCapability('appium:automationName', 'XCUITest');
$caps->setCapability('appium:app', 'https://testingbot.com/appium/sample.ipa');
$caps->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'PHP Appium Example',
'realDevice' => true
]);
$driver = RemoteWebDriver::create('https://hub.testingbot.com/wd/hub', $caps, 120000);
$driver->findElement(WebDriverBy::accessibilityId('inputA'))->sendKeys('10');
$driver->findElement(WebDriverBy::accessibilityId('inputB'))->sendKeys('5');
$driver->quit();
## PHPUnit Example Simulator/Emulator
Please see the example below. We're going to test our sample app on an iOS Simulator or Android Emulator.
In this test we'll add values to two input fields and see if the calculator works.
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
setCapability('platformName', 'Android');
$caps->setCapability('appium:deviceName', 'Pixel 8');
$caps->setCapability('appium:platformVersion', '14.0');
$caps->setCapability('appium:automationName', 'UiAutomator2');
$caps->setCapability('appium:app', 'https://testingbot.com/appium/sample.apk');
$caps->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'PHP Appium Example'
]);
$driver = RemoteWebDriver::create('https://hub.testingbot.com/wd/hub', $caps, 120000);
$driver->findElement(WebDriverBy::accessibilityId('inputA'))->sendKeys('10');
$driver->findElement(WebDriverBy::accessibilityId('inputB'))->sendKeys('5');
$driver->quit();
setCapability('platformName', 'iOS');
$caps->setCapability('appium:deviceName', 'iPhone 15');
$caps->setCapability('appium:platformVersion', '17.4');
$caps->setCapability('appium:automationName', 'XCUITest');
$caps->setCapability('appium:app', 'https://testingbot.com/appium/sample.zip');
$caps->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'PHP Appium Example'
]);
$driver = RemoteWebDriver::create('https://hub.testingbot.com/wd/hub', $caps, 120000);
$driver->findElement(WebDriverBy::accessibilityId('inputA'))->sendKeys('10');
$driver->findElement(WebDriverBy::accessibilityId('inputB'))->sendKeys('5');
$driver->quit();
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Browsers & Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote machines. If the test was running on your local machine or network, you can simply change your existing test like this:
$driver = RemoteWebDriver::create('https://API_KEY:API_SECRET@hub.testingbot.com/wd/hub', $caps, 120000);
To let TestingBot know on which device you want to run your test on, you need to specify the `appium:deviceName`, `appium:platformVersion`, `platformName` and other optional options in the capabilities field.
$caps = new DesiredCapabilities();
$caps->setCapability('platformName', 'iOS');
$caps->setCapability('appium:deviceName', 'iPhone 15');
$caps->setCapability('appium:platformVersion', '17.4');
$caps->setCapability('appium:automationName', 'XCUITest');
$caps->setCapability('appium:app', 'tb://...');
To see how to do this, please select a combination of device type and device name in the drop-down menus below.
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a PHP WebDriver test with our Tunnel:
1. [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
2. Adjust your test: instead of pointing to `'hub.testingbot.com/wd/hub'` like the example above - change it to point to your tunnel's IP address.
Assuming you run the tunnel on the same machine you run your tests, change to `'localhost:4445/wd/hub'`. localhost is the machine running the tunnel, 4445 is the default port of the tunnel.
This way your test will go securely through the tunnel to TestingBot and back:
setCapability('platformName', 'iOS');
$caps->setCapability('appium:deviceName', 'iPhone 15');
$caps->setCapability('appium:platformVersion', '17.4');
$caps->setCapability('appium:automationName', 'XCUITest');
$caps->setCapability('appium:app', 'https://testingbot.com/appium/sample.zip');
$caps->setCapability('tb:options', [
'name' => 'PHP Appium Tunnel Example'
]);
// Point to local tunnel instead of hub.testingbot.com
$driver = RemoteWebDriver::create('http://localhost:4445/wd/hub', $caps, 120000);
$driver->findElement(WebDriverBy::accessibilityId('inputA'))->sendKeys('10');
$driver->findElement(WebDriverBy::accessibilityId('inputB'))->sendKeys('5');
$driver->quit();
## Run tests in Parallel
Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.
You can run the same tests on all different browser configurations or run different tests all on the same browser configuration.
TestingBot has a large grid of machines and browsers, which means you can use our service to do efficient parallel testing. It is one of the key features we provide to greatly cut down on your total testing time.
To run tests in parallel, we recommend using [Paratest](https://github.com/brianium/paratest), which makes it very easy to run multiple PHPUnit tests simultaneously.
vendor/bin/paratest -p 8 -f --phpunit=vendor/bin/phpunit WebdriverTest.php
The command above will run your tests in 8 separate processes (8 tests at once).
### Queuing
Every plan we provide comes with a limit of parallel tests.
If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.
## Mark tests as passed/failed
As TestingBot has no way to determine whether your test passed or failed (it is determined by your business logic), we offer a way to send the test status back to TestingBot. This is useful if you want to see if a test succeeded or failed from the TestingBot member area.
You can use our [PHP API client](https://github.com/testingbot/testingbot-php) to report back test results.
$api = new TestingBot\TestingBotAPI("API_KEY", "API_SECRET");
$api->updateJob($driver->getSessionID(), ['name' => 'mytest', 'success' => true]);
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/proxy
# Configure a device with a proxy
You can use Appium or Selenium WebDriver to configure a proxy on the remote Android or iOS device, at the start of your test.
This makes sure that all traffic on the device will go through the external proxy, which can be useful to do [geolocation testing](https://testingbot.com/support/app-automate/appium/options#geo) or latency testing.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
desired_caps = {
'platformName' => 'Android',
'appium:deviceName' => 'Pixel 9',
'appium:platformVersion' => '16.0',
'appium:app' => 'https://testingbot.com/appium/sample.apk',
'tb:options' => {
'realDevice' => true
}
}
# Set up the proxy settings
proxy = {
'proxyType' => 'manual', # Proxy type (manual or system)
'httpProxy' => 'http://your-proxy-server:8080', # Replace with your HTTP proxy URL
'sslProxy' => 'http://your-proxy-server:8080', # Replace with your SSL proxy URL
'noProxy' => 'localhost,127.0.0.1' # Optional: bypass proxy for specific hosts
}
# Add proxy to desired capabilities
desired_caps['proxy'] = proxy
# Initialize the Appium driver
driver = Appium::Driver.new({
caps: desired_caps,
appium_lib: { server_url: 'https://API_KEY:API_SECRET@hub.testingbot.com/wd/hub' }
}, true)
UiAutomator2Options options = new UiAutomator2Options();
// Set basic capabilities
options.setPlatformName("Android");
options.setCapability("appium:deviceName", "Pixel 9");
options.setCapability("appium:platformVersion", "16.0");
options.setCapability("appium:app", "https://testingbot.com/appium/sample.apk");
// Set TestingBot-specific options
Map tbOptions = new HashMap<>();
tbOptions.put("realDevice", true);
options.setCapability("tb:options", tbOptions);
// Set up proxy settings
Proxy proxy = new Proxy();
proxy.setHttpProxy("http://your-proxy-server:8080"); // Replace with your HTTP proxy URL
proxy.setSslProxy("http://your-proxy-server:8080"); // Replace with your SSL proxy URL
// Apply the proxy settings to the capabilities
options.setProxy(proxy);
// Initialize the driver with TestingBot Appium server URL
URL appiumServerUrl = new URL("https://hub.testingbot.com/wd/hub");
AndroidDriver driver = new AndroidDriver(appiumServerUrl, options);
$capabilities = array(
'platformName' => 'Android',
'appium:app' => 'https://testingbot.com/appium/sample.apk',
'appium:deviceName' => 'Pixel 9',
'appium:platformVersion' => '16.0',
'tb:options' => array(
'realDevice' => true
),
'proxy' => array(
'proxyType' => 'manual', // Proxy type, "manual" for custom proxy settings
'httpProxy' => 'http://your-proxy-server:8080', // Replace with your HTTP proxy
'sslProxy' => 'http://your-proxy-server:8080', // Replace with your SSL proxy (HTTPS traffic)
'noProxy' => 'localhost,127.0.0.1' // Optional: addresses that should bypass the proxy
)
);
// Pass HTTP proxy host/port as the 5th/6th args to RemoteWebDriver::create()
$web_driver = RemoteWebDriver::create('https://hub.testingbot.com/wd/hub', $capabilities, null, null, 'your-proxy-server', 8080);
capabilities = {
"platformName": "Android",
"appium:deviceName": "Pixel 9",
"appium:platformVersion": "16.0",
"appium:app": "https://testingbot.com/appium/sample.apk",
"tb:options": {
"realDevice": True
},
"proxy": {
"proxyType": "manual", # Or "system" if you want to use system proxy
"httpProxy": "http://your-proxy-server:8080", # Replace with your proxy server
"sslProxy": "http://your-proxy-server:8080", # Replace with your proxy server for SSL
"noProxy": "localhost,127.0.0.1" # Optional: specify addresses to bypass the proxy
}
}
from appium.options.android import UiAutomator2Options
options = UiAutomator2Options().load_capabilities(capabilities)
driver = webdriver.Remote(
command_executor="https://hub.testingbot.com/wd/hub",
options=options
)
const { remote } = require('webdriverio');
const capabilities = {
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: 'api_key',
key: 'api_secret',
capabilities: {
'platformName': 'Android',
'appium:deviceName': 'Pixel 9',
'appium:platformVersion': '16.0',
'appium:app': 'https://testingbot.com/appium/sample.apk',
'tb:options': {
'name': 'My First App Test',
'realDevice': true
},
'proxy': {
proxyType: 'manual',
httpProxy: 'http://proxy-server:8080', // Replace with your proxy server URL
sslProxy: 'http://proxy-server:8080', // If needed for SSL connections
noProxy: '' // You can define URLs to bypass the proxy
}
}
};
const driver = await remote(capabilities);
AppiumOptions options = new AppiumOptions();
options.PlatformName = "Android";
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 9");
options.AddAdditionalAppiumOption("appium:platformVersion", "16.0");
options.AddAdditionalAppiumOption("appium:app", "https://testingbot.com/appium/sample.apk");
// Set TestingBot-specific options
var tbOptions = new Dictionary
{
["realDevice"] = true
};
options.AddAdditionalAppiumOption("tb:options", tbOptions);
// Set up proxy using the Proxy class
Proxy proxy = new Proxy();
proxy.HttpProxy = "http://your-proxy-server:8080"; // Replace with your proxy URL
proxy.SslProxy = "http://your-proxy-server:8080"; // Replace with your proxy URL
proxy.NoProxy = "localhost,127.0.0.1"; // Optional: bypass proxy for specific addresses
// Apply the proxy to the Appium options
options.Proxy = proxy;
// Initialize the driver
AndroidDriver driver = new AndroidDriver(new Uri("https://hub.testingbot.com/wd/hub"), options);
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/python
# Python Automated App Testing
Before you can start testing apps with Python, let's start with making sure Python is available on your system.
**For Windows:**
- Download the latest python installer for Windows: [Python Releases for Windows](https://www.python.org/downloads/windows/)
- Run the installer and follow the setup wizard to install Python.
**For Linux/macOS:**
- macOS, Ubuntu and most other Linux distros come with Python pre-installed.
## Installation
With TestingBot you can easily run automated tests with Python against your mobile app.
To get started, please install the following libraries:
pip install Appium-Python-Client selenium
### Real Device Example
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
Below is a Python Example, this will download and install our sample app (a calculator), add numbers to two input fields and show the sum of the two numbers:
from appium import webdriver
from appium.options.common import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
capabilities = {
"platformName": "Android",
"appium:deviceName": "Galaxy S24",
"appium:platformVersion": "14.0",
"appium:app": "https://testingbot.com/appium/sample.apk",
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"realDevice": True
}
}
options = AppiumOptions().load_capabilities(capabilities)
driver = webdriver.Remote("https://hub.testingbot.com/wd/hub", options=options)
inputA = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputA"))
)
inputA.send_keys("10")
inputB = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputB"))
)
inputB.send_keys("5")
sum_element = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "sum"))
)
assert sum_element.text == "15", "Sum should be 15"
driver.quit()
from appium import webdriver
from appium.options.common import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
capabilities = {
"platformName": "iOS",
"appium:deviceName": "iPhone 15",
"appium:platformVersion": "18.0",
"appium:app": "https://testingbot.com/appium/sample.ipa",
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"realDevice": True
}
}
options = AppiumOptions().load_capabilities(capabilities)
driver = webdriver.Remote("https://hub.testingbot.com/wd/hub", options=options)
inputA = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputA"))
)
inputA.send_keys("10")
inputB = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputB"))
)
inputB.send_keys("5")
driver.quit()
### Simulator/Emulator Example
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
Below is a Python Example, this will download and install our sample app (a calculator), add numbers to two input fields and show the sum of the two numbers:
from appium import webdriver
from appium.options.common import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
capabilities = {
"platformName": "Android",
"appium:deviceName": "Pixel 8",
"appium:platformVersion": "14.0",
"appium:app": "https://testingbot.com/appium/sample.apk",
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}
options = AppiumOptions().load_capabilities(capabilities)
driver = webdriver.Remote("https://hub.testingbot.com/wd/hub", options=options)
inputA = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputA"))
)
inputA.send_keys("10")
inputB = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputB"))
)
inputB.send_keys("5")
sum_element = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "sum"))
)
assert sum_element.text == "15", "Sum should be 15"
driver.quit()
from appium import webdriver
from appium.options.common import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
capabilities = {
"platformName": "iOS",
"appium:deviceName": "iPhone 15",
"appium:platformVersion": "17.4",
"appium:app": "https://testingbot.com/appium/sample.zip",
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}
options = AppiumOptions().load_capabilities(capabilities)
driver = webdriver.Remote("https://hub.testingbot.com/wd/hub", options=options)
inputA = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputA"))
)
inputA.send_keys("10")
inputB = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputB"))
)
inputB.send_keys("5")
driver.quit()
Make sure to always stop your test (`driver.quit()`), otherwise it will continue running, leading to a timeout.
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote machines. If the test was running on your local machine or network, you can simply change your existing test like this:
**Before:**
driver = webdriver.Remote("http://localhost:4723/wd/hub", options=AppiumOptions().load_capabilities(capabilities))
**After:**
options = AppiumOptions().load_capabilities(capabilities)
driver = webdriver.Remote("https://hub.testingbot.com/wd/hub", options=options)
To let TestingBot know on which device you want to run your test on, you need to specify the `appium:deviceName`, `appium:platformVersion`, `platformName` and other optional options in the capabilities field.
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a Python test with our Tunnel:
1. [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
2. Adjust your test: instead of pointing to `'hub.testingbot.com/wd/hub'` like the example above - change it to point to your tunnel's IP address.
Assuming you run the tunnel on the same machine you run your tests, change to `'localhost:4445/wd/hub'`. localhost is the machine running the tunnel, 4445 is the default port of the tunnel.
This way your test will go securely through the tunnel to TestingBot and back:
from appium import webdriver
from appium.options.common import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
capabilities = {
"platformName": "iOS",
"appium:deviceName": "iPhone 15",
"appium:platformVersion": "17.4",
"appium:app": "https://testingbot.com/appium/sample.zip",
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}
# Point to local tunnel instead of hub.testingbot.com
driver = webdriver.Remote("http://localhost:4445/wd/hub", capabilities)
inputA = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputA"))
)
inputA.send_keys("10")
inputB = WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "inputB"))
)
inputB.send_keys("5")
driver.quit()
## Run tests in Parallel
Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.
You can run the same tests on all different device configurations or run different tests all on the same device configuration.
TestingBot has a large grid of devices, which means you can use our service to do efficient parallel testing. It is one of the key features we provide to greatly cut down on your total testing time.
To run tests in parallel, we recommend using pytest with pytest-xdist, which makes it very easy to run multiple Python tests simultaneously:
pip install pytest pytest-xdist
pytest -n
### Queuing
Every plan we provide comes with a limit of parallel tests.
If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.
## Mark tests as passed/failed
As TestingBot has no way to determine whether your test passed or failed (it is determined by your business logic), we offer a way to send the test status back to TestingBot. This is useful if you want to see if a test succeeded or failed from the TestingBot member area.
You can use our [Python API client](https://github.com/testingbot/testingbotclient) to report back test results.
from testingbotclient import TestingBotClient
tb = TestingBotClient("API_KEY", "API_SECRET")
tb.tests.update_test(self.driver.session_id, name="My Test", passed=True, build="build-123")
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/robotframework
# Robot Framework Automated App Testing
See our [Robot Framework app example repository](https://github.com/testingbot/robotframework-app-example) for a complete example on how to run Robot Framework mobile app tests on TestingBot.
With [AppiumLibrary](https://github.com/serhatbolsu/robotframework-appiumlibrary) you can run [Appium](https://appium.io) tests with [Robot Framework](https://robotframework.org/) against native and hybrid mobile apps on Android and iOS.
Robot Framework is a keyword-driven test automation framework that produces easy to read test cases and HTML reports. Combined with TestingBot's device cloud, you can run your Robot Framework app tests on a large range of real Android and iOS devices, Android emulators and iOS simulators.
## Installation
Make sure you have Python and [pip](https://pip.pypa.io/en/stable/) installed, then install the required libraries:
pip install robotframework robotframework-appiumlibrary Appium-Python-Client requests
To run a test, use the `robot` command. The examples below rely on two small Python helper libraries (`TestingBot.py` and `AppOptions.py`), see the [Helper Libraries](https://testingbot.com#helpers) section for their content, or clone the [example repository](https://github.com/testingbot/robotframework-app-example).
PYTHONPATH=$PYTHONPATH:. robot test_android_emulator.robot
## Android Examples
Below are Robot Framework examples that download and install our sample Android app (a calculator), fill in two input fields and verify the sum. Switch between running on an Android emulator or on a real Android device.
[Emulator](https://testingbot.com#)[Real Device](https://testingbot.com#)
Save the example below in a `test_android_emulator.robot` file:
***Settings***
Library AppiumLibrary
Library TestingBot
Library AppOptions
Test Setup Open test app
Test Teardown Close test app
***Test Cases***
Android Sample App Sum Test
Wait Until Page Contains Element accessibility_id=inputA timeout=30s
Input Text accessibility_id=inputA 10
Wait Until Page Contains Element accessibility_id=inputB timeout=30s
Input Text accessibility_id=inputB 5
Wait Until Page Contains Element accessibility_id=sum timeout=30s
Element Text Should Be accessibility_id=sum 15
***Keywords***
Open test app
${options}= Get App Options Android
# W3C + Appium capabilities for a TestingBot Android emulator
Call Method ${options} set_capability platformName Android
Call Method ${options} set_capability appium:deviceName Pixel.*
Call Method ${options} set_capability appium:platformVersion 16.0
Call Method ${options} set_capability appium:automationName UiAutomator2
Call Method ${options} set_capability appium:app https://testingbot.com/appium/sample.apk
# TestingBot capabilities
${tb_options}= Create Dictionary key=api_key secret=api_secret name=Android Emulator App Test build=MyAppBuild
Call Method ${options} set_capability tb:options ${tb_options}
Open Testingbot App ${options}
Close test app
Report Testingbot Status ${SUITE_NAME} | ${TEST_NAME} ${TEST_STATUS}
Close Application
PYTHONPATH=$PYTHONPATH:. robot test_android_emulator.robot
Save the example below in a `test_android_physical.robot` file:
***Settings***
Library AppiumLibrary
Library TestingBot
Library AppOptions
Test Setup Open test app
Test Teardown Close test app
***Test Cases***
Android Sample App Sum Test
Wait Until Page Contains Element accessibility_id=inputA timeout=30s
Input Text accessibility_id=inputA 10
Wait Until Page Contains Element accessibility_id=inputB timeout=30s
Input Text accessibility_id=inputB 5
Wait Until Page Contains Element accessibility_id=sum timeout=30s
Element Text Should Be accessibility_id=sum 15
***Keywords***
Open test app
${options}= Get App Options Android
# W3C + Appium capabilities for a TestingBot Android real device
Call Method ${options} set_capability platformName Android
Call Method ${options} set_capability appium:deviceName Galaxy S24
Call Method ${options} set_capability appium:platformVersion 14.0
Call Method ${options} set_capability appium:automationName UiAutomator2
Call Method ${options} set_capability appium:app https://testingbot.com/appium/sample.apk
# TestingBot capabilities, realDevice=True routes the test to a physical device
${tb_options}= Create Dictionary key=api_key secret=api_secret name=Android Physical App Test build=MyAppBuild realDevice=True
Call Method ${options} set_capability tb:options ${tb_options}
Open Testingbot App ${options}
Close test app
Report Testingbot Status ${SUITE_NAME} | ${TEST_NAME} ${TEST_STATUS}
Close Application
PYTHONPATH=$PYTHONPATH:. robot test_android_physical.robot
## iOS Examples
Below are Robot Framework examples for our iOS sample app. Switch between running on an iOS simulator or on a real iPhone/iPad.
[Simulator](https://testingbot.com#)[Real Device](https://testingbot.com#)
Save the example below in a `test_ios_simulator.robot` file:
***Settings***
Library AppiumLibrary
Library TestingBot
Library AppOptions
Test Setup Open test app
Test Teardown Close test app
***Test Cases***
iOS Sample App Sum Test
Wait Until Page Contains Element accessibility_id=inputA timeout=30s
Input Text accessibility_id=inputA 10
Wait Until Page Contains Element accessibility_id=inputB timeout=30s
Input Text accessibility_id=inputB 5
***Keywords***
Open test app
${options}= Get App Options iOS
# W3C + Appium capabilities for a TestingBot iOS simulator
Call Method ${options} set_capability platformName iOS
Call Method ${options} set_capability appium:deviceName iPhone.*
Call Method ${options} set_capability appium:platformVersion 26.0
Call Method ${options} set_capability appium:automationName XCUITest
Call Method ${options} set_capability appium:app https://testingbot.com/appium/sample.zip
# TestingBot capabilities
${tb_options}= Create Dictionary key=api_key secret=api_secret name=iOS Simulator App Test build=MyAppBuild
Call Method ${options} set_capability tb:options ${tb_options}
Open Testingbot App ${options}
Close test app
Report Testingbot Status ${SUITE_NAME} | ${TEST_NAME} ${TEST_STATUS}
Close Application
PYTHONPATH=$PYTHONPATH:. robot test_ios_simulator.robot
Save the example below in a `test_ios_physical.robot` file:
***Settings***
Library AppiumLibrary
Library TestingBot
Library AppOptions
Test Setup Open test app
Test Teardown Close test app
***Test Cases***
iOS Sample App Sum Test
Wait Until Page Contains Element accessibility_id=inputA timeout=30s
Input Text accessibility_id=inputA 10
Wait Until Page Contains Element accessibility_id=inputB timeout=30s
Input Text accessibility_id=inputB 5
***Keywords***
Open test app
${options}= Get App Options iOS
# W3C + Appium capabilities for a TestingBot iOS real device
Call Method ${options} set_capability platformName iOS
Call Method ${options} set_capability appium:deviceName iPhone.*
Call Method ${options} set_capability appium:platformVersion 26.*
Call Method ${options} set_capability appium:automationName XCUITest
Call Method ${options} set_capability appium:app https://testingbot.com/appium/sample.ipa
# TestingBot capabilities, realDevice=True routes the test to a physical device
${tb_options}= Create Dictionary key=api_key secret=api_secret name=iOS Physical App Test build=MyAppBuild realDevice=True
Call Method ${options} set_capability tb:options ${tb_options}
Open Testingbot App ${options}
Close test app
Report Testingbot Status ${SUITE_NAME} | ${TEST_NAME} ${TEST_STATUS}
Close Application
PYTHONPATH=$PYTHONPATH:. robot test_ios_physical.robot
Make sure to always close the session in your test teardown (`Close Application`), otherwise the session will keep running until TestingBot's idle timeout.
## Helper Libraries
The examples above import two small Python helper libraries, `AppOptions` and `TestingBot`, that encapsulate driver creation and test status reporting. You can copy the snippets below, or grab them from the [example repository](https://github.com/testingbot/robotframework-app-example).
Save the following in a `AppOptions.py` file:
from appium.options.android import UiAutomator2Options
from appium.options.ios import XCUITestOptions
def get_app_options(platform):
if platform.lower() == 'android':
return UiAutomator2Options()
elif platform.lower() == 'ios':
return XCUITestOptions()
else:
raise ValueError(f"Unsupported platform: {platform}")
Save the following in a `TestingBot.py` file:
import os
import requests
from appium import webdriver
from robot.libraries.BuiltIn import BuiltIn
TB_KEY = os.environ.get('TB_KEY', 'api_key')
TB_SECRET = os.environ.get('TB_SECRET', 'api_secret')
HUB_URL = f'https://{TB_KEY}:{TB_SECRET}@hub.testingbot.com/wd/hub'
def open_testingbot_app(options):
driver = webdriver.Remote(HUB_URL, options=options)
appium = BuiltIn().get_library_instance('AppiumLibrary')
appium._cache.register(driver)
def report_testingbot_status(name, status):
appium = BuiltIn().get_library_instance('AppiumLibrary')
session_id = appium._current_application().session_id
payload = {'test[name]': name, 'test[success]': int(status == 'PASS')}
url = f'https://api.testingbot.com/v1/tests/{session_id}'
response = requests.put(url, data=payload, auth=(TB_KEY, TB_SECRET))
assert response.status_code == 200, response.text
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your `.apk`, `.ipa` or `.zip` to TestingBot and reference it from the `appium:app` capability.
## Specify Devices
To let TestingBot know which device to run your test on, set the `platformName`, `appium:deviceName` and `appium:platformVersion` capabilities. Pick a combination below to see the matching snippet:
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal apps or backends.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information.
1. [Download our tunnel](https://testingbot.com/support/tunnel) and start it:
java -jar testingbot-tunnel.jar key secret
2. In your `TestingBot.py` helper, point `HUB_URL` at the tunnel instead of `hub.testingbot.com`. Assuming the tunnel runs on the same machine as your tests, use `http://localhost:4445/wd/hub` (4445 is the default tunnel port).
HUB_URL = 'http://localhost:4445/wd/hub'
Your test will then route securely through the tunnel to TestingBot and back.
## Run tests in Parallel
You can run multiple Robot Framework tests in parallel using the [Pabot](https://pabot.org/) library.
Install Pabot:
pip install -U robotframework-pabot
Run your tests with `pabot` instead of `robot`:
PYTHONPATH=$PYTHONPATH:. pabot --processes 2 test_android_emulator.robot test_ios_simulator.robot
Some recommendations when running tests in parallel:
- Make sure each test case is independent and does not rely on shared state.
- Set `--processes` to the number of parallel sessions your TestingBot plan allows.
- Consider using `--processtimeout` to set a timeout for each process.
### Queuing
Every plan comes with a limit of parallel tests. If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and start them as soon as slots become available.
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/ruby
# Ruby Automated App Testing
Let's start with making sure Ruby is available on your system.
**For Windows:**
- You can download Ruby for Windows from [RubyInstaller](https://rubyinstaller.org/)
- Run the installer and follow the setup wizard to install Ruby.
**For Linux/macOS:**
Download [RVM](https://rvm.io/) and install Ruby:
curl -L https://get.rvm.io | bash -s stable --ruby
## Installation
With TestingBot you can easily run your automated app tests with Ruby.
Please install the following gems:
gem install appium_lib
gem install selenium-webdriver
Please see the example below. We're going to run a test on our sample app (calculator) where we'll add values to two input fields.
### Real Device Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
require 'appium_lib'
caps = {
"platformName" => "Android",
"appium:deviceName" => "Galaxy S24",
"appium:platformVersion" => "14.0",
"appium:app" => "https://testingbot.com/appium/sample.apk",
"tb:options" => {
"name" => "Ruby Appium Example",
"realDevice" => true
}
}
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://API_KEY:API_SECRET@hub.testingbot.com/wd/hub"
}}, true)
driver = appium_driver.start_driver
wait = Selenium::WebDriver::Wait.new(:timeout => 30)
wait.until { driver.find_element(:accessibility_id, "inputA").displayed? }
driver.find_element(:accessibility_id, "inputA").send_keys 5
driver.find_element(:accessibility_id, "inputB").send_keys 10
result = driver.find_element(:accessibility_id, "sum")
if (!result.nil?) && (result.text.eql? "15")
puts "Test Passed"
else
puts "Test Failed"
end
driver.quit
require 'appium_lib'
caps = {
"platformName" => "iOS",
"appium:deviceName" => "iPhone 15",
"appium:platformVersion" => "18.0",
"appium:app" => "https://testingbot.com/appium/sample.ipa",
"tb:options" => {
"name" => "Ruby Appium Example",
"realDevice" => true
}
}
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://API_KEY:API_SECRET@hub.testingbot.com/wd/hub"
}}, true)
driver = appium_driver.start_driver
wait = Selenium::WebDriver::Wait.new(:timeout => 30)
driver.find_element(:accessibility_id, "inputA").send_keys 5
driver.find_element(:accessibility_id, "inputB").send_keys 10
driver.quit
### Simulator/Emulator Testing
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
require 'appium_lib'
caps = {
"platformName" => "Android",
"appium:deviceName" => "Pixel 8",
"appium:platformVersion" => "14.0",
"appium:app" => "https://testingbot.com/appium/sample.apk",
"tb:options" => {
"name" => "Ruby Appium Example"
}
}
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://API_KEY:API_SECRET@hub.testingbot.com/wd/hub"
}}, true)
driver = appium_driver.start_driver
wait = Selenium::WebDriver::Wait.new(:timeout => 30)
wait.until { driver.find_element(:accessibility_id, "inputA").displayed? }
driver.find_element(:accessibility_id, "inputA").send_keys 5
driver.find_element(:accessibility_id, "inputB").send_keys 10
result = driver.find_element(:accessibility_id, "sum")
if (!result.nil?) && (result.text.eql? "15")
puts "Test Passed"
else
puts "Test Failed"
end
driver.quit
require 'appium_lib'
caps = {
"platformName" => "iOS",
"appium:deviceName" => "iPhone 15",
"appium:platformVersion" => "17.4",
"appium:app" => "https://testingbot.com/appium/sample.zip",
"tb:options" => {
"name" => "Ruby Appium Example"
}
}
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://API_KEY:API_SECRET@hub.testingbot.com/wd/hub"
}}, true)
driver = appium_driver.start_driver
wait = Selenium::WebDriver::Wait.new(:timeout => 30)
driver.find_element(:accessibility_id, "inputA").send_keys 5
driver.find_element(:accessibility_id, "inputB").send_keys 10
driver.quit
Make sure to always stop your test (`driver.quit`), otherwise it will continue running, leading to a timeout.
## Uploading Your App
Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.
## Specify Devices
To run your existing tests on TestingBot, your tests will need to be configured to use the TestingBot remote devices. If the test was running on your local machine or network, you can simply change your existing test like this:
**Before:**
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "http://localhost:4723/wd/hub"
}}, true)
**After:**
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://API_KEY:API_SECRET@hub.testingbot.com/wd/hub"
}}, true)
To let TestingBot know on which device you want to run your test on, you need to specify the `appium:deviceName`, `appium:platformVersion`, `platformName` and other optional options in the capabilities field.
To see how to do this, please select a combination of device type and device name in the drop-down menus below.
 iOS 18.6 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
## Testing Internal Websites
We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.
The example below shows how to easily run a Ruby WebDriver test with our Tunnel:
1. [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:
java -jar testingbot-tunnel.jar key secret
2. Adjust your test: instead of pointing to `'hub.testingbot.com/wd/hub'` like the example above - change it to point to your tunnel's IP address.
Assuming you run the tunnel on the same machine you run your tests, change to `'localhost:4445/wd/hub'`. localhost is the machine running the tunnel, 4445 is the default port of the tunnel.
This way your test will go securely through the tunnel to TestingBot and back:
require 'appium_lib'
caps = {
"platformName" => "iOS",
"appium:deviceName" => "iPhone 15",
"appium:platformVersion" => "17.4",
"appium:app" => "https://testingbot.com/appium/sample.zip",
"tb:options" => {
"name" => "single_test",
"build" => "Ruby Appium Sample"
}
}
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
# Point to local tunnel instead of hub.testingbot.com
server_url: "http://localhost:4445/wd/hub"
}}, true)
driver = appium_driver.start_driver
wait = Selenium::WebDriver::Wait.new(:timeout => 30)
driver.find_element(:accessibility_id, "inputA").send_keys 5
driver.find_element(:accessibility_id, "inputB").send_keys 10
driver.quit
## Run tests in Parallel
Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.
You can run the same tests on all different device configurations or run different tests all on the same device configuration.
TestingBot has a large grid of devices, which means you can use our service to do efficient parallel testing. It is one of the key features we provide to greatly cut down on your total testing time.
To get started, please see the examples on [https://github.com/grosser/parallel\_tests](https://github.com/grosser/parallel_tests)
### Queuing
Every plan we provide comes with a limit of parallel tests.
If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.
## Mark tests as passed/failed
As TestingBot has no way to determine whether your test passed or failed (it is determined by your business logic), we offer a way to send the test status back to TestingBot. This is useful if you want to see if a test succeeded or failed from the TestingBot member area.
You can use our [Ruby API client](https://github.com/testingbot/testingbot_ruby) to report back test results.
api = TestingBot::Api.new(key, secret)
api.update_test(driver.session_id, { :name => new_name, :success => true })
## Preparing your App
You do not need to install any code or plugin to run a test.
Below are some things that are necessary to successfully run a mobile test:
**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.
**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.
**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:
- Create a Simulator build:
xcodebuild -sdk iphonesimulator -configuration Debug
- Zip the `.app` bundle:
zip -r MyApp.zip MyApp.app
## Additional Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`)
This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/test-app-upgrades
# Test App Upgrades/Mid-Session App Installations
As app developers, we frequently release new versions of our applications. In most cases, users upgrade from an older version rather than installing the app from scratch. This transition often requires migrating existing user data to ensure the app functions correctly, making upgrade testing a crucial step in development. By thoroughly testing upgrades, we can confirm that the app continues to work as expected post-update.
App upgrades aren't the only scenario where installing apps during a running session is necessary. Some applications may depend on other apps to function properly. If your app interacts with another application, such as pulling data or integrating with its features, it's essential to verify that these dependencies remain intact after an upgrade.
Additionally, there are cases where you might need to delete and reinstall an app during a session. For example, if you want to test how the app behaves when a user uninstalls and reinstalls it, the mid-session install feature can facilitate this process.
## (Re-)Installing apps during a test
Before you can install a new app, or a new version of the app, we encourage you to first upload your `.apk/.ipa/.zip` file to [TestingBot Storage](https://testingbot.com/support/app-automate/help/upload).
Alternatively, you can also specify a URL to an `.apk/.ipa/.zip` file that is publicly accessible.
During your test, you can use the `mobile:installApp` command to install the app on the device.
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
driver.executeScript("mobile: installApp", ImmutableMap.of("appPath", "tb://.apk|ipa|zip"));
await driver.execute('mobile: installApp', {"appPath": "tb://.apk|ipa|zip"});
driver.execute_script('mobile: installApp', {"appPath": "tb://.apk|ipa|zip"})
driver.execute_script('mobile: installApp', {"appPath" => "tb://.apk|ipa|zip"})
driver.ExecuteScript("mobile: installApp", new Dictionary { { "appPath", "tb://.apk|ipa|zip" } });
The `mobile: installApp` command returns no payload on success, Appium will throw an exception if the install fails. Use `mobile: queryAppState` (Android) or `mobile: isAppInstalled` to verify the install, and read the version from the app itself if needed.
Appium will not automatically launch the app after you installed it during the test. You can launch the app manually using the following commands after installation:
- **Android:** `mobile: startActivity`
- **iOS:** `mobile: activateApp` (`mobile: launchApp` is deprecated)
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
// Android
driver.executeScript("mobile: startActivity", ImmutableMap.of("intent", "com.testingbot.demo/.MainActivity"));
// iOS
driver.executeScript("mobile: activateApp", ImmutableMap.of("bundleId", "com.testingbot.demo"));
// Android
await driver.execute('mobile: startActivity', {intent: 'com.testingbot.demo/.MainActivity'});
// iOS
await driver.execute('mobile: activateApp', {bundleId: 'com.testingbot.demo'});
# Android
driver.execute_script("mobile: startActivity", {"intent": "com.testingbot.demo/.MainActivity"})
# iOS
driver.execute_script("mobile: activateApp", {"bundleId": "com.testingbot.demo"})
# Android
@driver.execute_script("mobile: startActivity", {"intent" => "com.testingbot.demo/.MainActivity"})
# iOS
@driver.execute_script("mobile: activateApp", {"bundleId" => "com.testingbot.demo"})
// Android
driver.ExecuteScript("mobile: startActivity", new Dictionary { { "intent", "com.testingbot.demo/.MainActivity" } });
// iOS
driver.ExecuteScript("mobile: activateApp", new Dictionary { { "bundleId", "com.testingbot.demo" } });
## Removing your app during a test
To remove your app during a test, you can use the [`mobile: removeApp`](https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#mobile-removeapp) command.
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
// Android
driver.executeScript("mobile: removeApp", ImmutableMap.of("appId", "com.testingbot.demo"));
// iOS
driver.executeScript("mobile: removeApp", ImmutableMap.of("bundleId", "com.testingbot.demo"));
// Android
await driver.execute('mobile: removeApp', {appId: 'com.testingbot.demo'});
// iOS
await driver.execute('mobile: removeApp', {bundleId: 'com.testingbot.demo'});
# Android
driver.execute_script("mobile: removeApp", {"appId": "com.testingbot.demo"})
# iOS
driver.execute_script("mobile: removeApp", {"bundleId": "com.testingbot.demo"})
# Android
@driver.execute_script("mobile: removeApp", {"appId" => "com.testingbot.demo"})
# iOS
@driver.execute_script("mobile: removeApp", {"bundleId" => "com.testingbot.demo"})
// Android
driver.ExecuteScript("mobile: removeApp", new Dictionary { { "appId", "com.testingbot.demo" } });
// iOS
driver.ExecuteScript("mobile: removeApp", new Dictionary { { "bundleId", "com.testingbot.demo" } });
## Other app commands
Appium supports more commands to handle apps during a test, such as:
### mobile: terminateApp
This will forcefully stop the app (specified by bundleId or appId) on the device.
### mobile: isAppInstalled
Verifies if the app (specified by bundleId or appId) is on the device. Returns a boolean true or false.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/upload-files
# Upload files to physical devices
TestingBot provides a feature that allows you to specify one or more media files that need to be uploaded to a physical iOS or Android device, prior to running your Appium test. This functionality allows you to run tests that require media files (photos and videos) from the iOS or Android photo gallery.
There are two options to use this feature, depending on your usecase. We suggest using the first option, which will use TestingBot's custom `uploadMedia` capability.
- [TestingBot Upload Capability](https://testingbot.com#testingbot)
- [Appium Push and Pull](https://testingbot.com#appium)
## TestingBot Upload Capability
To use this functionality, you can use the `uploadMedia` capability, specifying an array of URLs to image files or video files. You can specify URLs to publicly downloadable media files, or you can use TestingBot Storage to upload the files to TestingBot and use the `tb://` URLs.
### Upload files to TestingBot
[cURL](https://testingbot.com#)
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/storage" \
-F "file=@/path/to/app/file/media.png"
The API response will include a `tb://` URL, which you can use in the `uploadMedia` capability.
{
"app_url": "tb://edb4f4f3ed95a4f46714f3df"
}
Uploads to our TestingBot Storage are automatically deleted after 62 days.
### Add the capability to your Appium script
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
Map tbOptions = new HashMap<>();
tbOptions.put("uploadMedia", new String[]{"tb://edb4f4f3ed95a4f46714f3df"});
options.setCapability("tb:options", tbOptions);
const capabilities = {
'tb:options': {
'uploadMedia': ['tb://edb4f4f3ed95a4f46714f3df']
}
}
capabilities = {
"tb:options" : {
"uploadMedia" : ["tb://edb4f4f3ed95a4f46714f3df"]
}
}
capabilities = {
"tb:options" => {
"uploadMedia" => ["tb://edb4f4f3ed95a4f46714f3df"]
}
}
AppiumOptions options = new AppiumOptions();
var tbOptions = new Dictionary();
tbOptions.Add("uploadMedia", new string[] { "tb://edb4f4f3ed95a4f46714f3df" });
options.AddAdditionalAppiumOption("tb:options", tbOptions);
TestingBot will add these files to the Android or iOS device. The metadata of the gallery on the device will be updated automatically, which means the media file will immediately appear in the device's gallery.
- **Android:** the media files will be added to the Default Gallery app. `/sdcard/Pictures` for images and `/sdcard/Movies` for videos.
- **iOS:** the media files will be added to the iOS Camera Roll: `com.apple.mobileslideshow`
## Appium Push and Pull
Appium provides both [pushFile](https://appium.io/docs/en/2.0/reference/interfaces/appium_types.ExternalDriver/#pushfile) and [pullFile](https://appium.io/docs/en/2.0/reference/interfaces/appium_types.ExternalDriver/#pullfile) functionality. This is supported since [Appium Version](https://testingbot.com/support/app-automate/appium/options#appiumVersion) 1.15.0.
With this approach, the file is pushed when the test has already started. Pushing media files to the iOS or Android device does not guarantee the updating of the metadata in the iOS or Android gallery, which means your media file might not appear (immediately).
### Android Example
Push a file to the SD Card of the Android device:
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
// Push a file to the sdcard
driver.pushFile("/sdcard/Download/sample.png", new File(System.getProperty("user.home") + "/sample.png"));
// Pull file from sdcard
byte[] fileBase64 = driver.pullFile("/sdcard/Download/sample.png");
// Push file - WebdriverIO syntax
const data = Buffer.from("TestingBot").toString('base64');
await driver.pushFile('/sdcard/Download/sample.txt', data);
// Pull file WebdriverIO example
const fileData = await driver.pullFile('/sdcard/Download/sample.txt');
# Push a file
dest_path = '/sdcard/Download/sample.txt'
driver.push_file(dest_path, 'TestingBot'.encode("utf-8"))
# Pull file
file_base64 = driver.pull_file(dest_path)
# Push a file
driver.push_file('/sdcard/Download/sample.png', File.read('/Users/testingbot/Desktop/sample.png'))
# Pull file
driver.pull_file('/sdcard/Download/sample.png')
// Push a file
driver.PushFile("/sdcard/Download/sample.png", new FileInfo("/Users/testingbot/Desktop/sample.png"));
// Pull file
byte[] fileBase64 = driver.PullFile("/sdcard/Download/sample.png");
### iOS Example
Before using the pull and push functionality, you'll need to ensure that:
- Your iOS app must have set the `UIFileSharingEnabled` key to `true` in the `Info.plist` file, which enables file sharing.
- Alternatively, you can set the `LSSupportsOpeningDocumentsInPlace` key to `true` in the `Info.plist` to expose your app's folder in the **Files** app on the iOS device.
The destination path to push a file is in the following format: `@:documents/.`
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
// Push an image file
driver.pushFile("@com.testingbot.Sample-App:documents/sample.png", new File("/Users/testingbot/Desktop/sample.png"));
// Pull file
byte[] fileBase64 = driver.pullFile("@com.testingbot.Sample-App:documents/sample.png");
// Push file WebdriverIO example
const data = Buffer.from("TestingBot").toString('base64');
await driver.pushFile('@com.testingbot.Sample-App:documents/sample.txt', data);
// Pull file WebdriverIO example
const fileData = await driver.pullFile('@com.testingbot.Sample-App:documents/sample.txt');
# Push an image file
driver.push_file('@com.testingbot.Sample-App:documents/sample.png', source_path='/Users/testingbot/Desktop/sample.png')
# Push a text file
dest_path = '@com.testingbot.Sample-App:documents/sample.txt'
driver.push_file(dest_path, 'TestingBot'.encode("utf-8"))
# Pull file
file_base64 = driver.pull_file(dest_path)
# Push an image file
driver.push_file('@com.testingbot.Sample-App:documents/sample.png', File.read('/Users/testingbot/Desktop/sample.png'))
# Pull file
driver.pull_file('@com.testingbot.Sample-App:documents/image.png')
// Push an image file
driver.PushFile("@com.testingbot.Sample-App:documents/image.png", new FileInfo("/Users/testingbot/Desktop/sample.png"));
// Pull file
byte[] fileBase64 = driver.PullFile("@com.testingbot.Sample-App:documents/sample.png");
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/permission-popups
# Handling permission pop-ups
While testing your mobile app, it may ask for specific permissions during the test. Various system dialogs or popups may appear, asking the user to grant access.
The mobile app might ask the user permission to access the contacts, notifications, media, ... on the device.
Below we'll go over the various techniques to handle these events during your mobile testing:
- [Allow or Deny all permissions](https://testingbot.com#allPermissions)
- [Allow or Deny specific permissions](https://testingbot.com#specificPermissions)
## Allow or Deny all permissions
Appium allows you to automatically accept all permission and alert requests.
### Allow or Deny all permissions on Android
You can use the `autoGrantPermissions` capability.
Appium will inspect the [Android Manifest](https://developer.android.com/guide/topics/manifest/manifest-intro) of the app, extract the required permissions and grant these.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps["appium:autoGrantPermissions"] = true
options.setCapability("appium:autoGrantPermissions", true);
capabilities = {
"appium:autoGrantPermissions": True
}
const capabilities = {
"appium:autoGrantPermissions": true
}
options.AddAdditionalAppiumOption("appium:autoGrantPermissions", true);
### Allow or Deny all permissions on iOS
On iOS, Appium offers two capabilities which you can use: `autoAcceptAlerts` and `autoDismissAlerts`.
`autoAcceptAlerts` will automatically accept (grant) any alerts (including permission popups) that might appear during your test.
`autoDismissAlerts` will automatically dismiss (deny) any alerts (including permission popups) that might appear during your test.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps["appium:autoAcceptAlerts"] = true # to accept all alerts
caps["appium:autoDismissAlerts"] = true # to dismiss all alerts
options.setCapability("appium:autoAcceptAlerts", true); // to accept all alerts
options.setCapability("appium:autoDismissAlerts", true); // to dismiss all alerts
# Set one of the following capabilities:
capabilities["appium:autoAcceptAlerts"] = True # to accept all alerts
capabilities["appium:autoDismissAlerts"] = True # to dismiss all alerts
// Set one of the following capabilities:
capabilities["appium:autoAcceptAlerts"] = true; // to accept all alerts
capabilities["appium:autoDismissAlerts"] = true; // to dismiss all alerts
options.AddAdditionalAppiumOption("appium:autoAcceptAlerts", true); // to accept all alerts
options.AddAdditionalAppiumOption("appium:autoDismissAlerts", true); // to dismiss all alerts
### Customizing the alert button selector
There may be dialogs that appear during your automated tests which have 3 buttons instead of 2, or there's a specific button you want to press instead of the default (allow) button.
In this case, you might want to consider using the `acceptAlertButtonSelector` Appium setting, which allows you to pass a selector (a [class-chain expression](https://github.com/facebookarchive/WebDriverAgent/wiki/Class-Chain-Queries-Construction-Rules)) to tell Appium which button to click.
For example, you might want to click the 'Allow Once' button in a location permission dialog. To instruct Appium to do this, you can change the `acceptAlertButtonSelector` setting during your test:
driver.updateSettings({
acceptAlertButtonSelector: "**/XCUIElementTypeButton[`label CONTAINS[c] 'Allow Once'`]"
});
Alternatively, you can set this setting at the beginning of your test with a capability:
const capabilities = {
"appium:settings": {
"acceptAlertButtonSelector": "**/XCUIElementTypeButton[`label CONTAINS[c] 'Allow Once'`]"
}
}
## Allow or Deny specific permissions
You can also choose to accept or deny alerts and popups during your test.
Your test will need to find the element of the alert or popup and perform a `click` action on that element.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
# For Android
driver.find_element(:xpath, ".//android.widget.Button[@text='Allow']").click
# For iOS
driver.find_element(:id, "Allow").click
// For Android
driver.findElement(By.xpath(".//android.widget.Button[@text='Allow']")).click();
// For iOS
driver.findElement(By.id("Allow")).click();
from selenium.webdriver.common.by import By
# For Android
driver.find_element(By.XPATH, ".//android.widget.Button[@text='Allow']").click()
# For iOS
driver.find_element(By.ID, "Allow").click()
// For Android
const element = await driver.$(".//android.widget.Button[@text='Allow']");
await element.click();
// For iOS
const allowBtn = await driver.$("#Allow");
await allowBtn.click();
// For Android
IWebElement ll = driver.FindElement(By.XPath(".//android.widget.Button[@text='Allow']"));
ll.Click();
// For iOS
IWebElement ll = driver.FindElement(By.Id("Allow"));
ll.Click();
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/screen-orientation
# Change screen orientation
Appium allows you to change the screen orientation of your iOS and Android devices.
You can choose to either change the orientation [at the start of the test](https://testingbot.com#caps) or [during a test](https://testingbot.com#during).
## Set screen orientation at the start of your test
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[Node](https://testingbot.com#)[C#](https://testingbot.com#)
capabilities = {
"appium:orientation" => "LANDSCAPE"
}
options.setCapability("appium:orientation", "LANDSCAPE");
$capabilities = [
"appium:orientation" => "LANDSCAPE"
];
capabilities = {
"appium:orientation": "LANDSCAPE"
}
const capabilities = {
"appium:orientation": "LANDSCAPE"
};
options.AddAdditionalAppiumOption("appium:orientation", "LANDSCAPE");
## Set screen orientation during a test
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[Node](https://testingbot.com#)[C#](https://testingbot.com#)
@driver.rotation = :landscape
((AppiumDriver) driver).rotate(ScreenOrientation.LANDSCAPE);
$driver->rotate("LANDSCAPE");
driver.orientation = "LANDSCAPE"
await driver.setOrientation('landscape');
driver.Orientation = ScreenOrientation.Landscape;
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/localization-options
# Localization testing on iOS and Android
When running tests with Appium, you can choose to set a specific locale for the device or emulator, by using the `appium:locale` capability.
Specifying this capability will make sure the device/emulator and app will display or format data such as dates, time, decimal separators and calendars according to the specified country's regional conventions.
**The expected value format depends on the platform:**
- **Android:** two-letter region code, e.g. `ES` for Spain (see the Android table below). The example below uses `"ES"`.
- **iOS:** language\_region locale identifier, e.g. `es_ES`, `fr_CA` (see the iOS table below). On iOS, the example below would be `"es_ES"` instead of `"ES"`.
You can also change the Simulator UI locale at runtime using the script command `mobile: configureLocalization`.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
capabilities = {
"appium:locale" => "ES"
}
options.setCapability("appium:locale", "ES");
$capabilities = [
"appium:locale" => "ES"
];
capabilities = {
"appium:locale": "ES"
}
const capabilities = {
"appium:locale": "ES"
};
options.AddAdditionalAppiumOption("appium:locale", "ES");
## Android locale codes
| Locale | Locale Code |
| --- | --- |
| Australia | AU |
| Austria | AT |
| Belgium | BE |
| Brazil | BR |
| Britain | GB |
| Bulgaria | BG |
| Canada | CA |
| Croatia | HR |
| Czech Republic | CZ |
| Denmark | DK |
| Egypt | EG |
| Finland | FI |
| France | FR |
| Germany | DE |
| Greece | GR |
| Hong-Kong | HK |
| Hungary | HU |
| India | IN |
| Indonesia | ID |
| Ireland | IE |
| Israel | IL |
| Italy | IT |
| Japan | JP |
| Korea | KR |
| Latvia | LV |
| Liechtenstein | LI |
| Lithuania | LT |
| Mexico | MX |
| Netherlands | NL |
| New Zealand | NZ |
| Norway | NO |
| Philippines | PH |
| Poland | PL |
| Portugal | PT |
| PRC | CN |
| Romania | RO |
| Russia | RU |
| Serbia | RS |
| Singapore | SG |
| Slovakia | SK |
| Slovenia | SI |
| Spain | ES |
| Sweden | SE |
| Switzerland | CH |
| Taiwan | TW |
| Thailand | TH |
| Turkey | TR |
| Ukraine | UA |
| US | US |
| USA | US |
| Vietnam | VN |
| Zimbabwe | ZW |
## iOS locale codes
| Locale | Locale Code |
| --- | --- |
| Australia | en\_AU |
| Belgium | nl\_BE |
| Belgium | fr\_BE |
| Brunei Darussalam | ms\_BN |
| Canada | en\_CA |
| Canada | fr\_CA |
| Czech Republic | cs\_CZ |
| Finland | fi\_FI |
| Germany | de\_DE |
| Greece | el\_GR |
| Hungary | hu\_HU |
| India | hi\_IN |
| Indonesia | id\_ID |
| Israel | he\_IL |
| Italy | it\_IT |
| Japan | ja\_JP |
| Malaysia | ms\_MY |
| Netherlands | nl\_NL |
| New Zealand | en\_NZ |
| Norway | nb\_NO |
| Philippines | tl\_PH |
| Poland | pl\_PL |
| PRC | zh\_CN |
| Romania | ro\_RO |
| Russia | ru\_RU |
| Singapore | en\_SG |
| Slovakia | sk\_SK |
| Korea | ko\_KR |
| Sweden | sv\_SE |
| Taiwan | zh\_TW |
| Thailand | th\_TH |
| Turkey | tr\_TR |
| UK | en\_GB |
| Ukraine | uk\_UA |
| US | es\_US |
| USA | en\_US |
| Vietnam | vi\_VN |
| Brazil | pt-BR |
| China (Simplified) | zh-Hans |
| China (Traditional) | zh-Hant |
| Hong Kong | zh-HK |
| India | en-IN |
| Ireland | en-IE |
| Latin America | es-419 |
| Mexico | es-MX |
| South Africa | en-ZA |
## Change Language
Specify the `appium:language` capability to set the language of the app under test, and the device/emulator.
On iOS, you can set the language to for example French: `fr`. More information is available in the [Language IDs documentation](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html).
You can also change the Simulator UI language at runtime using the following script command: `mobile: configureLocalization`.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
capabilities = {
"appium:language" => "es"
}
options.setCapability("appium:language", "es");
$capabilities = [
"appium:language" => "es"
];
capabilities = {
"appium:language": "es"
}
const capabilities = {
"appium:language": "es"
};
options.AddAdditionalAppiumOption("appium:language", "es");
### Android language codes
| Language | Language Code |
| --- | --- |
| Arabic | ar |
| Bulgarian | bg |
| Catalan | ca |
| Chinese | zh |
| Croatian | hr |
| Czech | cs |
| Danish | da |
| Dutch | nl |
| English | en |
| Finnish | fi |
| French | fr |
| German | de |
| Greek | el |
| Hebrew | he |
| Hindi | hi |
| Hungarian | hu |
| Indonesian | id |
| Italian | it |
| Japanese | ja |
| Korean | ko |
| Latvian | lv |
| Lithuanian | lt |
| Norwegian-Bokmol | nb |
| Polish | pl |
| Portuguese | pt |
| Romanian | ro |
| Russian | ru |
| Serbian | sr |
| Slovak | sk |
| Slovenian | sl |
| Spanish | es |
| Swedish | sv |
| Tagalog | tl |
| Thai | th |
| Turkish | tr |
| Ukrainian | uk |
| Vietnamese | vi |
### iOS language codes
| Language | Language Code |
| --- | --- |
| Chinese | zh |
| Czech | cs |
| Dutch | nl |
| English | en |
| Finnish | fi |
| French | fr |
| German | de |
| Greek | el |
| Hebrew | he |
| Hindi | hi |
| Hungarian | hu |
| Indonesian | id |
| Italian | it |
| Japanese | ja |
| Korean | ko |
| Malay | ms |
| Norwegian (Bokmal) | nb |
| Polish | pl |
| Portuguese (Brazil) | pt |
| Romanian | ro |
| Russian | ru |
| Slovak | sk |
| Spanish | es |
| Swedish | sv |
| Tagalog | tl |
| Thai | th |
| Turkish | tr |
| Ukrainian | uk |
| Vietnamese | vi |
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/dark-mode
# Dark Mode testing with Appium
Dark mode is a setting, available on both Android and iOS, which toggles the interface appearance of the phone to use either the standard light mode setting or a dark mode.
When dark mode is enabled, the phone will switch to a dark background with lighter foreground colors, which enables better viewing in low-light conditions.
## Changing Dark Mode
This example will use `mobile: setAppearance` to set the appearance to dark (dark mode).
[Android](https://testingbot.com#)[iOS](https://testingbot.com#)
const { remote } = require('webdriverio');
const capabilities = {
'platformName': 'Android',
'appium:app': 'https://testingbot.com/appium/sample.apk',
'appium:deviceName': 'Pixel 8',
'appium:platformVersion': '14.0',
'appium:automationName': 'UiAutomator2',
'tb:options': {
'name': 'Dark Mode Test',
'realDevice': true
}
};
const driver = await remote({
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: 'api_key',
key: 'api_secret',
capabilities
});
// Enable dark mode
await driver.execute('mobile: setAppearance', { style: 'dark' });
const inputA = await driver.$('~inputA');
await inputA.setValue('10');
const inputB = await driver.$('~inputB');
await inputB.setValue('5');
const sum = await driver.$('~sum');
const value = await sum.getText();
console.assert(value === '15', 'Sum should be 15');
await driver.deleteSession();
Make sure to always stop your test (`driver.deleteSession()`), otherwise it will continue running, leading to a timeout.
const { remote } = require('webdriverio');
const capabilities = {
'platformName': 'iOS',
'appium:app': 'https://testingbot.com/appium/sample.ipa',
'appium:deviceName': 'iPhone 15',
'appium:platformVersion': '17.0',
'appium:automationName': 'XCUITest',
'tb:options': {
'name': 'Dark Mode Test',
'realDevice': true
}
};
const driver = await remote({
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: 'api_key',
key: 'api_secret',
capabilities
});
// Enable dark mode
await driver.execute('mobile: setAppearance', { style: 'dark' });
const inputA = await driver.$('~inputA');
await inputA.setValue('10');
const inputB = await driver.$('~inputB');
await inputB.setValue('5');
await driver.deleteSession();
Make sure to always stop your test (`driver.deleteSession()`), otherwise it will continue running, leading to a timeout.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/push-notifications
# Testing Push Notifications with Appium
Push notifications are a critical part of many mobile applications. With TestingBot, you can simulate and interact with push notifications on both Android and iOS devices during your Appium tests.
**Prerequisite:** Your app must request push notification permission and the user must allow it before notifications can be displayed. To handle this automatically during tests, set the `autoGrantPermissions` capability (Android) or `autoAcceptAlerts` capability (iOS). If your app has not registered for notifications, none of the approaches below will show a visible notification.
// Android: auto-grant all permissions including notifications
options.setCapability("appium:autoGrantPermissions", true);
// iOS: auto-accept permission dialogs (e.g. "Allow Notifications")
options.setCapability("appium:autoAcceptAlerts", true);
## Which method should I use?
- **[Android](https://testingbot.com#android)**, Trigger real push notifications via FCM or use ADB shell commands via Appium's `mobile: shell` on emulators and real devices.
- **[iOS Simulators](https://testingbot.com#ios-simulator)**, Use Appium's `mobile: pushNotification` command to send simulated notifications with a JSON payload.
- **[iOS Real Devices](https://testingbot.com#ios-real)**, Trigger push notifications via your backend server or a third-party service (APNs, Firebase) to the device under test.
- **[Interact with Notifications](https://testingbot.com#interact)**, Open the notification shade or banner and tap on notifications during your test.
## Android Push Notifications
There are two approaches to test push notifications on Android with TestingBot:
Trigger via your backend (recommended)Send a real push notification through FCM (Firebase Cloud Messaging) to the app running on the device. The notification appears with the correct app icon and package attribution, exactly as your end users would see it.Simulate via ADB shellUse Appium's `mobile: shell` command to post a system notification. This is a quick way to test notification interaction (tapping, swiping), but the notification appears as a generic system notification without your app's icon.
### Option 1: Trigger a real notification via FCM (recommended)
If your app uses Firebase Cloud Messaging, you can send a test push notification by calling the FCM HTTP v1 API during your test. The notification will be delivered to your app on the TestingBot device, showing the correct app icon and notification channel.
You'll need a **Firebase service account key** (JSON file) for your project and the **FCM registration token** from the device. Most apps log the registration token on startup, you can retrieve it from logcat or have your app expose it via a test endpoint.
Your app must already be set up to receive FCM notifications. This approach sends a real notification through your existing push infrastructure.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import java.net.URL;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
import java.util.Map;
public class PushNotificationTest {
public static void main(String[] args) throws Exception {
UiAutomator2Options options = new UiAutomator2Options();
options.setDeviceName("Pixel 8");
options.setPlatformVersion("14.0");
options.setApp("tb://your_app_id");
Map tbOptions = new HashMap<>();
tbOptions.put("key", "api_key");
tbOptions.put("secret", "api_secret");
tbOptions.put("name", "Push Notification Test");
tbOptions.put("realDevice", true);
options.setCapability("tb:options", tbOptions);
AndroidDriver driver = new AndroidDriver(
new URL("https://hub.testingbot.com/wd/hub"), options);
// Retrieve the FCM registration token from your app
// (e.g. via a test endpoint or logcat)
String fcmToken = "DEVICE_FCM_REGISTRATION_TOKEN";
// Send a real push notification via FCM HTTP v1 API
String projectId = "your-firebase-project-id";
String accessToken = "YOUR_OAUTH2_ACCESS_TOKEN";
String json = "{"
+ "\"message\": {"
+ " \"token\": \"" + fcmToken + "\","
+ " \"notification\": {"
+ " \"title\": \"Order Update\","
+ " \"body\": \"Your order has been shipped!\""
+ " }"
+ "}"
+ "}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://fcm.googleapis.com/v1/projects/"
+ projectId + "/messages:send"))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpClient.newHttpClient().send(request,
HttpResponse.BodyHandlers.ofString());
Thread.sleep(3000);
driver.quit();
}
}
from appium import webdriver
from appium.options.android import UiAutomator2Options
import requests
import time
options = UiAutomator2Options()
options.device_name = 'Pixel 8'
options.platform_version = '14.0'
options.app = 'tb://your_app_id'
options.set_capability('tb:options', {
'key': 'api_key',
'secret': 'api_secret',
'name': 'Push Notification Test',
'realDevice': True
})
driver = webdriver.Remote(
command_executor='https://hub.testingbot.com/wd/hub',
options=options)
# Retrieve the FCM registration token from your app
fcm_token = 'DEVICE_FCM_REGISTRATION_TOKEN'
# Send a real push notification via FCM HTTP v1 API
project_id = 'your-firebase-project-id'
access_token = 'YOUR_OAUTH2_ACCESS_TOKEN'
response = requests.post(
f'https://fcm.googleapis.com/v1/projects/{project_id}/messages:send',
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
},
json={
'message': {
'token': fcm_token,
'notification': {
'title': 'Order Update',
'body': 'Your order has been shipped!'
}
}
}
)
time.sleep(3)
driver.quit()
const { remote } = require('webdriverio');
const capabilities = {
'platformName': 'Android',
'appium:app': 'tb://your_app_id',
'appium:deviceName': 'Pixel 8',
'appium:platformVersion': '14.0',
'appium:automationName': 'UiAutomator2',
'tb:options': {
'name': 'Push Notification Test',
'realDevice': true
}
};
const driver = await remote({
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: 'api_key',
key: 'api_secret',
capabilities
});
// Retrieve the FCM registration token from your app
const fcmToken = 'DEVICE_FCM_REGISTRATION_TOKEN';
// Send a real push notification via FCM HTTP v1 API
const projectId = 'your-firebase-project-id';
const accessToken = 'YOUR_OAUTH2_ACCESS_TOKEN';
await fetch(
`https://fcm.googleapis.com/v1/projects/${projectId}/messages:send`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: {
token: fcmToken,
notification: {
title: 'Order Update',
body: 'Your order has been shipped!'
}
}
})
}
);
await driver.pause(3000);
await driver.deleteSession();
require 'appium_lib'
require 'net/http'
require 'json'
caps = {
'platformName' => 'Android',
'appium:app' => 'tb://your_app_id',
'appium:deviceName' => 'Pixel 8',
'appium:platformVersion' => '14.0',
'appium:automationName' => 'UiAutomator2',
'tb:options' => {
'key' => 'api_key',
'secret' => 'api_secret',
'name' => 'Push Notification Test',
'realDevice' => true
}
}
driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: 'https://hub.testingbot.com/wd/hub'
}
}).start_driver
# Retrieve the FCM registration token from your app
fcm_token = 'DEVICE_FCM_REGISTRATION_TOKEN'
# Send a real push notification via FCM HTTP v1 API
project_id = 'your-firebase-project-id'
access_token = 'YOUR_OAUTH2_ACCESS_TOKEN'
uri = URI("https://fcm.googleapis.com/v1/projects/#{project_id}/messages:send")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{access_token}"
request['Content-Type'] = 'application/json'
request.body = {
message: {
token: fcm_token,
notification: {
title: 'Order Update',
body: 'Your order has been shipped!'
}
}
}.to_json
http.request(request)
sleep 3
driver.quit
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using System.Text;
using System.Text.Json;
var options = new AppiumOptions();
options.PlatformName = "Android";
options.AddAdditionalAppiumOption("appium:app", "tb://your_app_id");
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 8");
options.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
options.AddAdditionalAppiumOption("appium:automationName", "UiAutomator2");
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = "Push Notification Test",
["realDevice"] = true
};
options.AddAdditionalAppiumOption("tb:options", tbOptions);
var driver = new AndroidDriver(
new Uri("https://hub.testingbot.com/wd/hub"), options);
// Retrieve the FCM registration token from your app
var fcmToken = "DEVICE_FCM_REGISTRATION_TOKEN";
// Send a real push notification via FCM HTTP v1 API
var projectId = "your-firebase-project-id";
var accessToken = "YOUR_OAUTH2_ACCESS_TOKEN";
var payload = JsonSerializer.Serialize(new {
message = new {
token = fcmToken,
notification = new {
title = "Order Update",
body = "Your order has been shipped!"
}
}
});
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
var content = new StringContent(payload, Encoding.UTF8, "application/json");
await client.PostAsync(
$"https://fcm.googleapis.com/v1/projects/{projectId}/messages:send",
content);
Thread.Sleep(3000);
driver.Quit();
### Option 2: Quick test with ADB shell
For quick testing of notification _interaction_ (tapping, swiping away), you can post a system notification via ADB. This uses Appium's `mobile: shell` command.
Notifications created with `cmd notification post` are generic system notifications. They will **not** display your app's icon or appear as coming from your app. Use the [FCM approach](https://testingbot.com#android-fcm) above if you need app-branded notifications.
// Post a generic system notification
Map shellArgs = new HashMap<>();
shellArgs.put("command", "cmd");
shellArgs.put("args", new String[]{
"notification", "post", "-S", "messaging",
"-t", "Test Title", "Test Message"});
driver.executeScript("mobile: shell", shellArgs);
// Post a notification targeting a specific package (shows app icon on some Android versions)
Map packageArgs = new HashMap<>();
packageArgs.put("command", "cmd");
packageArgs.put("args", new String[]{
"notification", "post", "-S", "messaging",
"--package", "com.example.myapp",
"-t", "Order Update", "Your order has been shipped!"});
driver.executeScript("mobile: shell", packageArgs);
To use `mobile: shell` on TestingBot, set the `adbExecTimeout` capability if your command takes longer than the default timeout.
## iOS Simulator Notifications
On iOS simulators, Appium provides the `mobile: pushNotification` command that accepts a JSON payload matching the Apple Push Notification format. This allows you to test how your app handles push notifications without needing a real APNs connection.
The `mobile: pushNotification` command only works on **iOS simulators** , not on real iOS devices. For real device testing, see the [iOS Real Devices](https://testingbot.com#ios-real) section.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class IOSPushNotificationTest {
public static void main(String[] args) throws Exception {
XCUITestOptions options = new XCUITestOptions();
options.setDeviceName("iPhone 16");
options.setPlatformVersion("18.0");
options.setApp("tb://your_app_id");
Map tbOptions = new HashMap<>();
tbOptions.put("key", "api_key");
tbOptions.put("secret", "api_secret");
tbOptions.put("name", "iOS Push Notification Test");
options.setCapability("tb:options", tbOptions);
IOSDriver driver = new IOSDriver(
new URL("https://hub.testingbot.com/wd/hub"), options);
// Simulate a push notification
Map payload = new HashMap<>();
payload.put("bundleId", "com.example.myapp");
Map alert = new HashMap<>();
alert.put("title", "New Message");
alert.put("body", "You have a new message from TestingBot");
alert.put("subtitle", "Testing");
Map aps = new HashMap<>();
aps.put("alert", alert);
aps.put("badge", 1);
aps.put("sound", "default");
Map payloadData = new HashMap<>();
payloadData.put("aps", aps);
payload.put("payload", payloadData);
driver.executeScript("mobile: pushNotification", payload);
Thread.sleep(2000);
driver.quit();
}
}
from appium import webdriver
from appium.options.ios import XCUITestOptions
import time
options = XCUITestOptions()
options.device_name = 'iPhone 16'
options.platform_version = '18.0'
options.app = 'tb://your_app_id'
options.set_capability('tb:options', {
'key': 'api_key',
'secret': 'api_secret',
'name': 'iOS Push Notification Test'
})
driver = webdriver.Remote(
command_executor='https://hub.testingbot.com/wd/hub',
options=options)
# Simulate a push notification
driver.execute_script('mobile: pushNotification', {
'bundleId': 'com.example.myapp',
'payload': {
'aps': {
'alert': {
'title': 'New Message',
'body': 'You have a new message from TestingBot',
'subtitle': 'Testing'
},
'badge': 1,
'sound': 'default'
}
}
})
time.sleep(2)
driver.quit()
const { remote } = require('webdriverio');
const capabilities = {
'platformName': 'iOS',
'appium:app': 'tb://your_app_id',
'appium:deviceName': 'iPhone 16',
'appium:platformVersion': '18.0',
'appium:automationName': 'XCUITest',
'tb:options': {
'name': 'iOS Push Notification Test'
}
};
const driver = await remote({
hostname: 'hub.testingbot.com',
port: 443,
protocol: 'https',
path: '/wd/hub',
user: 'api_key',
key: 'api_secret',
capabilities
});
// Simulate a push notification
await driver.execute('mobile: pushNotification', {
bundleId: 'com.example.myapp',
payload: {
aps: {
alert: {
title: 'New Message',
body: 'You have a new message from TestingBot',
subtitle: 'Testing'
},
badge: 1,
sound: 'default'
}
}
});
await driver.pause(2000);
await driver.deleteSession();
require 'appium_lib'
caps = {
'platformName' => 'iOS',
'appium:app' => 'tb://your_app_id',
'appium:deviceName' => 'iPhone 16',
'appium:platformVersion' => '18.0',
'appium:automationName' => 'XCUITest',
'tb:options' => {
'key' => 'api_key',
'secret' => 'api_secret',
'name' => 'iOS Push Notification Test'
}
}
driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: 'https://hub.testingbot.com/wd/hub'
}
}).start_driver
# Simulate a push notification
driver.execute_script('mobile: pushNotification', {
bundleId: 'com.example.myapp',
payload: {
aps: {
alert: {
title: 'New Message',
body: 'You have a new message from TestingBot',
subtitle: 'Testing'
},
badge: 1,
sound: 'default'
}
}
})
sleep 2
driver.quit
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.iOS;
var options = new AppiumOptions();
options.PlatformName = "iOS";
options.AddAdditionalAppiumOption("appium:app", "tb://your_app_id");
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 16");
options.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
options.AddAdditionalAppiumOption("appium:automationName", "XCUITest");
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = "iOS Push Notification Test"
};
options.AddAdditionalAppiumOption("tb:options", tbOptions);
var driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"), options);
// Simulate a push notification
var payload = new Dictionary
{
["bundleId"] = "com.example.myapp",
["payload"] = new Dictionary
{
["aps"] = new Dictionary
{
["alert"] = new Dictionary
{
["title"] = "New Message",
["body"] = "You have a new message from TestingBot",
["subtitle"] = "Testing"
},
["badge"] = 1,
["sound"] = "default"
}
}
};
driver.ExecuteScript("mobile: pushNotification", payload);
Thread.Sleep(2000);
driver.Quit();
### Payload format
The `mobile: pushNotification` command accepts a payload that follows the [Apple Push Notification format](https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification). The key fields are:
`bundleId`The bundle identifier of the target app (e.g. `com.example.myapp`).`payload.aps.alert.title`The title text shown in the notification banner.`payload.aps.alert.body`The main message text of the notification.`payload.aps.badge`The badge number to display on the app icon.`payload.aps.sound`The notification sound. Use `"default"` for the standard notification sound.
You can also include custom key-value pairs in the payload for your app to process:
Map alert = new HashMap<>();
alert.put("title", "Order Update");
alert.put("body", "Your order #1234 has been shipped");
Map aps = new HashMap<>();
aps.put("alert", alert);
Map payload = new HashMap<>();
payload.put("aps", aps);
payload.put("orderId", "1234");
payload.put("deepLink", "/orders/1234");
Map pushArgs = new HashMap<>();
pushArgs.put("bundleId", "com.example.myapp");
pushArgs.put("payload", payload);
driver.executeScript("mobile: pushNotification", pushArgs);
### Wait for a notification with `mobile: expectNotification`
The XCUITest driver also provides [`mobile: expectNotification`](https://appium.github.io/appium-xcuitest-driver/latest/execute-methods/#mobile-expectnotification), which blocks test execution until a specific notification is delivered. This is useful when you want to verify that your app posts a particular notification in response to an action, without polling for UI elements.
`name` (required)The notification name to wait for (e.g. `com.example.orderComplete`).`type``"plain"` (default) for standard NSNotificationCenter notifications, or `"darwin"` for system-level Darwin notifications.`timeoutSeconds`Maximum time to wait in seconds. Defaults to 60. Throws a timeout error if the notification is not received.
// Wait up to 10 seconds for a specific notification
Map expectArgs = new HashMap<>();
expectArgs.put("name", "com.example.myapp.orderComplete");
expectArgs.put("timeoutSeconds", 10);
driver.executeScript("mobile: expectNotification", expectArgs);
`mobile: expectNotification` listens for _internal_ notifications (NSNotificationCenter / Darwin), not push notification banners. Use it to verify that your app's code reacts correctly to events, alongside `mobile: pushNotification` which triggers the visible notification.
## iOS Real Device Notifications
Appium's `mobile: pushNotification` command does not work on real iOS devices because Apple only allows push notifications through APNs (Apple Push Notification service). To test push notifications on real iOS devices, you need to trigger them from your backend or via FCM/APNs.
### Disable app re-signing
When you upload an iOS app to TestingBot, it is automatically [re-signed with a TestingBot provisioning profile](https://testingbot.com/support/app-automate/help/app-resigning) so it can be installed on our devices. This re-signing **removes your app's push notification entitlements** , which means APNs will reject push tokens from the re-signed app and notifications will not be delivered.
To test push notifications on real iOS devices, you must **disable re-signing** by setting `resigningEnabled` to `false`. This requires your app to be signed with an [Apple Developer Enterprise certificate](https://developer.apple.com/programs/enterprise/), which allows installation on any device without TestingBot's provisioning profile.
// Disable re-signing so push notification entitlements are preserved
Map tbOptions = new HashMap<>();
tbOptions.put("key", "api_key");
tbOptions.put("secret", "api_secret");
tbOptions.put("resigningEnabled", false);
options.setCapability("tb:options", tbOptions);
Without `"resigningEnabled": false`, your app's push entitlements will be stripped during re-signing and push notifications will silently fail on real iOS devices.
### Trigger the notification
Once re-signing is disabled and your app retains its push entitlements, trigger notifications from your backend:
1. **Use your app's backend API** , If your app has an endpoint that triggers push notifications, call it from your test to send a real notification to the device under test.
2. **Use Firebase Cloud Messaging (FCM)**, Send a push notification via the FCM API. Your app must be configured to receive FCM messages.
3. **Use APNs directly** , Send a notification via the Apple Push Notification service using a valid APNs certificate or key.
### Example: Trigger via your backend API
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
// After your app is running and the device token is registered,
// trigger a notification from your backend
String json = "{\"device_token\": \"DEVICE_TOKEN_FROM_APP\","
+ "\"title\": \"New Message\","
+ "\"body\": \"You have a new message\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://your-api.example.com/send-notification"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpClient.newHttpClient().send(request,
HttpResponse.BodyHandlers.ofString());
// Wait for the notification to arrive
Thread.sleep(3000);
// Now interact with the notification in your Appium test
// (see the "Interact with Notifications" section below)
import requests
import time
# After your app is running and the device token is registered,
# trigger a notification from your backend
requests.post('https://your-api.example.com/send-notification', json={
'device_token': 'DEVICE_TOKEN_FROM_APP',
'title': 'New Message',
'body': 'You have a new message'
})
# Wait for the notification to arrive
time.sleep(3)
# Now interact with the notification in your Appium test
# (see the "Interact with Notifications" section below)
// After your app is running and the device token is registered,
// trigger a notification from your backend
await fetch('https://your-api.example.com/send-notification', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
device_token: 'DEVICE_TOKEN_FROM_APP',
title: 'New Message',
body: 'You have a new message'
})
});
// Wait for the notification to arrive
await driver.pause(3000);
// Now interact with the notification in your Appium test
require 'net/http'
require 'json'
# After your app is running and the device token is registered,
# trigger a notification from your backend
uri = URI('https://your-api.example.com/send-notification')
Net::HTTP.post(uri, {
device_token: 'DEVICE_TOKEN_FROM_APP',
title: 'New Message',
body: 'You have a new message'
}.to_json, 'Content-Type' => 'application/json')
# Wait for the notification to arrive
sleep 3
# Now interact with the notification in your Appium test
## Interact with Notifications
After a notification is displayed, you can interact with it in your Appium test. The approach differs between Android and iOS.
### Android: Open the notification shade
On Android, use `openNotifications()` to pull down the notification shade, then find and tap the notification element.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
// Open the notification shade
driver.openNotifications();
// Wait for the notification to appear
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement notification = wait.until(ExpectedConditions
.presenceOfElementLocated(By.xpath(
"//*[contains(@text, 'Test Title')]")));
// Tap the notification
notification.click();
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Open the notification shade
driver.open_notifications()
# Wait for the notification to appear
wait = WebDriverWait(driver, 10)
notification = wait.until(EC.presence_of_element_located((
AppiumBy.XPATH, "//*[contains(@text, 'Test Title')]"
)))
# Tap the notification
notification.click()
// Open the notification shade (WebdriverIO)
await driver.execute('mobile: openNotifications');
// Wait for the notification to appear
const notification = await driver.$('//*[contains(@text, "Test Title")]');
await notification.waitForExist({ timeout: 10000 });
// Tap the notification
await notification.click();
# Open the notification shade
driver.open_notifications
# Wait for the notification to appear
wait = Selenium::WebDriver::Wait.new(timeout: 10)
notification = wait.until {
driver.find_element(:xpath, "//*[contains(@text, 'Test Title')]")
}
# Tap the notification
notification.click
// Open the notification shade
driver.OpenNotifications();
// Wait for the notification to appear
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
var notification = wait.Until(d =>
d.FindElement(By.XPath("//*[contains(@text, 'Test Title')]")));
// Tap the notification
notification.Click();
### iOS: Tap the notification banner
On iOS, notifications appear as banners at the top of the screen. You can interact with them using the Notification Center or by tapping the banner directly.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)
// Wait for the notification banner to appear
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement banner = wait.until(ExpectedConditions
.presenceOfElementLocated(By.name("NotificationShortLookView")));
// Tap the banner to open the notification
banner.click();
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Wait for the notification banner to appear
wait = WebDriverWait(driver, 10)
banner = wait.until(EC.presence_of_element_located((
AppiumBy.ACCESSIBILITY_ID, 'NotificationShortLookView'
)))
# Tap the banner to open the notification
banner.click()
// Wait for the notification banner to appear
const banner = await driver.$('~NotificationShortLookView');
await banner.waitForExist({ timeout: 10000 });
// Tap the banner to open the notification
await banner.click();
# Wait for the notification banner to appear
wait = Selenium::WebDriver::Wait.new(timeout: 10)
banner = wait.until {
driver.find_element(:accessibility_id, 'NotificationShortLookView')
}
# Tap the banner to open the notification
banner.click
## Tips & Best Practices
### Grant notification permissions
Your app needs notification permissions before it can display push notifications. Use the `autoGrantPermissions` capability (Android) or `autoAcceptAlerts` capability (iOS) to automatically handle permission dialogs. See the [Permission Popups](https://testingbot.com/support/app-automate/appium/permission-popups) documentation for more details.
// Android: auto-grant all permissions
options.setCapability("appium:autoGrantPermissions", true);
// iOS: auto-accept permission dialogs
options.setCapability("appium:autoAcceptAlerts", true);
### Wait for notifications to arrive
Push notifications are asynchronous. Always use explicit waits instead of fixed delays when checking for notifications. Use `WebDriverWait` to poll for the notification element rather than `sleep()`. On iOS simulators, you can also use [`mobile: expectNotification`](https://testingbot.com#ios-expect) to block until an internal notification is delivered.
### Use unique notification content
When searching for a notification in the notification shade, use unique text content (such as a timestamp or test ID) to avoid matching stale or system notifications.
### Dismiss notifications after testing
After interacting with a notification, dismiss the notification shade (Android) or return to the app to continue your test flow.
// Android: press Back to close the notification shade
((AndroidDriver) driver).pressKey(new KeyEvent(AndroidKey.BACK));
// iOS: swipe up on the notification banner to dismiss
Map swipeArgs = new HashMap<>();
swipeArgs.put("direction", "up");
driver.executeScript("mobile: swipe", swipeArgs);
## Frequently Asked Questions
### Can I test push notifications on real iOS devices?
Yes, but you cannot simulate them using Appium's `mobile: pushNotification` command, that only works on iOS simulators. For real iOS devices, you need to trigger actual push notifications through your backend, Firebase Cloud Messaging (FCM), or APNs (Apple Push Notification service). Your test can then interact with the notification banner or Notification Center using Appium's standard element interaction commands. See the [iOS Real Devices](https://testingbot.com#ios-real) section for examples.
### How do I simulate a push notification on Android with Appium?
On Android, use Appium's `mobile: shell` command with the ADB `cmd notification post` command. This sends a notification to the Android notification shade. Example: `driver.execute_script('mobile: shell', {'command': 'cmd', 'args': ['notification', 'post', '-S', 'messaging', '-t', 'Title', 'Message']})`. This works on both emulators and real Android devices on TestingBot.
### How do I open the notification shade in an Appium test?
On Android, use `driver.openNotifications()` (Java/Python/Ruby/C#) or `driver.execute('mobile: openNotifications')` (WebdriverIO). This pulls down the system notification shade. On iOS, notifications appear as banners at the top of the screen, wait for the banner element and tap it directly using its accessibility identifier.
### Why is my push notification not appearing during the test?
Common causes:
1. The app has not been granted notification permissions, use `autoGrantPermissions` (Android) or `autoAcceptAlerts` (iOS) to handle permission dialogs.
2. On iOS real devices, `mobile: pushNotification` only works on simulators, use your backend or FCM/APNs instead.
3. The notification may take a few seconds to appear, use explicit waits instead of fixed sleeps.
See the [Tips](https://testingbot.com#tips) section for more details.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/options
# Appium tb:options Reference
Reference for the capabilities you can pass to Appium tests on TestingBot: pick a device, pin an Appium version, change timezone or geolocation, throttle the network, record video, group tests under a build, attach custom metadata, and more. Each section includes code examples in Java, Node.js, Python, Ruby and C#.
The [Appium Capabilities generator](https://testingbot.com/support/app-automate/appium/capabilities) allows you to easily generate the necessary capabilities for your mobile tests.
## Appium Options
Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.
Some important options that might help:
**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.
**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: fr\_CA)
- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session
## Specifying Appium Version
TestingBot will use the most recent, compatible, Appium version according to the device, OS and version you specify.
If you'd like to specify your own Appium version, you can do this with the `appiumVersion` capability.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["appiumVersion"] = "2.0"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("appiumVersion", "2.0");
$caps = array(
"appiumVersion" => "2.0"
);
capabilities = {
"appiumVersion" : "2.0"
}
const capabilities = {
"appiumVersion" : "2.0"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("appiumVersion", "2.0");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "iOS",
deviceName: "iPhone 13",
browserVersion: "16.0",
app: 'https://testingbot.com/appium/sample.ipa',
"tb:options" => {
"appiumVersion" : "2.0"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("appiumVersion", "2.0");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "iOS");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("deviceName", "iPhone 13");
caps.setCapability("browserVersion", "16.0");
caps.setCapability("app", "https://testingbot.com/appium/sample.ipa");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::safari();
$capabilities->setPlatform('iOS');
$capabilities->setCapability('tb:options', array(
'appiumVersion' => "2.0"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'appiumVersion': "2.0"
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.ipa",
'platformName': "iOS",
'browserVersion': "16.0",
'deviceName': "iPhone 13",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.ipa',
"platformName": 'iOS',
"deviceName": 'iPhone 13',
"version": '16.0',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"appiumVersion": "2.0"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "16.0",
PlatformName = "iOS",
DeviceName = "iPhone 13",
App = "https://testingbot.com/appium/sample.ipa",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["appiumVersion"] = "2.0"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
### Appium 2 Versions
We currently support these Appium 2 versions:
- 2.11.3
- 2.10.3
- 2.9.0
- 2.5.1
- 2.4.1
- 2.3.0
- 2.2.1
If you specify `"appiumVersion": "latest"`, TestingBot will automatically use the latest Appium version. You can also use `latest-1`, `latest-2`, ... to test on the next most recent versions of Appium.
## Change Device Timezone
TestingBot allows you to change the timezone of the mobile device (physical or emu/sim) to a timezone that you specify. Simply pass in a `timeZone` capability specifying a timezone from the list. You can find a [list of timezones on Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["timeZone"] = "Etc/UTC"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("timeZone", "Etc/UTC");
$caps = array(
"timeZone" => "Etc/UTC"
);
capabilities = {
"timeZone" : "Etc/UTC"
}
const capabilities = {
"timeZone" : "Etc/UTC"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("timeZone", "Etc/UTC");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
deviceName: 'Galaxy S23',
"tb:options" => {
"timeZone" : "Etc/UTC"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("timeZone", "Etc/UTC");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'timeZone' => "Etc/UTC"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'timeZone': "Etc/UTC"
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"timeZone": "Etc/UTC"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var appiumOptions = new AppiumOptions();
appiumOptions.PlatformName = "Android";
appiumOptions.AddAdditionalAppiumOption("appium:app", "https://testingbot.com/appium/sample.apk");
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["timeZone"] = "Etc/UTC"
};
appiumOptions.AddAdditionalAppiumOption("tb:options", tbOptions);
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value | Possible Values: || string | "Etc/UTC" | [List of timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) |
### Localization Options
Please see the [Localization Options documentation](https://testingbot.com/support/app-automate/appium/localization-options) to learn how to set the locale and language of the device during your automated tests.
### Geolocation Testing
TestingBot provides an option where you can specify from which country you'd like to run the test from.
Once you specify this option, the device we provision for your test will be configured to use a proxy in the country you specified.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["testingbot.geoCountryCode"] = "DE"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("testingbot.geoCountryCode", "DE");
$caps = array(
"testingbot.geoCountryCode" => "DE"
);
capabilities = {
"testingbot.geoCountryCode" : "DE"
}
const capabilities = {
"testingbot.geoCountryCode" : "DE"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("testingbot.geoCountryCode", "DE");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
deviceName: "Galaxy S23",
version: "13.0",
"tb:options" => {
"testingbot.geoCountryCode" : "DE"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("testingbot.geoCountryCode", "DE");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'testingbot.geoCountryCode' => "DE"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'testingbot.geoCountryCode': "DE"
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"testingbot.geoCountryCode": "DE"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "13.0",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["testingbot.geoCountryCode"] = "DE"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Specify `testingbot.geoCountryCode` with one of the following country codes:
- **'\*'** : this will take a random country from the list below
- **'AU'** : Australia
- **'BE'** : Belgium
- **'BR'** : Brazil
- **'CA'** : Canada
- **'CL'** : Chile
- **'FR'** : France
- **'DE'** : Germany
- **'IN'** : India
- **'IT'** : Italy
- **'JP'** : Japan
- **'NO'** : Norway
- **'SG'** : Singapore
- **'ZA'** : South Africa
- **'SE'** : Sweden
- **'CH'** : Switzerland
- **'AE'** : United Arab Emirates
- **'GB'** : United Kingdom
- **'US'** : United States
**Important:** this does not work on Android 4.4
## Upload files to device
You can upload files to the remote iOS or Android device, prior to starting a test. More information is available on this page: [upload files to device](https://testingbot.com/support/app-automate/appium/upload-files).
## Throttle Network
TestingBot provides a throttle network feature, which will simulate specific network conditions during a test.
We offer various presets:
- **Edge** :
- 250kbps download
- 150kbps upload
- 0% Loss
- 300ms Latency
- **3G** :
- 400kbps download
- 100kbps upload
- 0% Loss
- 100ms Latency
- **4G** :
- 18000kbps download
- 9000kbps upload
- 0% Loss
- 100ms Latency
- **airplane** :
Simulates Airplane Mode (no network connectivity)
- **disable** :
Revert the throttling to no throttling.
We also offer the possibility to specify your own `download`, `upload`, `loss` and `latency` values.
You can pass a preset or specific options at the start of your test (through the `capabilities`), or during the test (with `execute_script`).
**At the start of a test:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
throttle_network: "3G",
throttle_network: {
uploadSpeed: 10 * 1024,
downloadSpeed: 10 * 1024,
latency: 0,
loss: 0
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("throttle_network", "3G");
MutableCapabilities throttleOptions = new MutableCapabilities();
throttleOptions.setCapability("uploadSpeed", 10 * 1024);
throttleOptions.setCapability("downloadSpeed", 10 * 1024);
throttleOptions.setCapability("latency", 0);
throttleOptions.setCapability("loss", 0);
tbOptions.setCapability("throttle_network", throttleOptions);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'throttle_network' => '3G',
'throttle_network' => array(
'uploadSpeed' => 10 * 1024,
'downloadSpeed' => 10 * 1024,
'latency' => 0,
'loss' => 0
)
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'throttle_network': '3G',
'throttle_network': {
'uploadSpeed': 10 * 1024,
'downloadSpeed': 10 * 1024,
'latency': 0,
'loss': 0
}
}
chromeOpts = {
'browserName': "chrome",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"throttle_network": "3G",
"throttle_network": {
"uploadSpeed": 10 * 1024,
"downloadSpeed": 10 * 1024,
"latency": 0,
"loss": 0
}
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var appiumOptions = new AppiumOptions();
appiumOptions.PlatformName = "Android";
appiumOptions.AddAdditionalAppiumOption("appium:app", "https://testingbot.com/appium/sample.apk");
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name,
["throttle_network"] = "3G"
};
appiumOptions.AddAdditionalAppiumOption("tb:options", tbOptions);
var driver = new AndroidDriver(new Uri("https://hub.testingbot.com/wd/hub"),
appiumOptions, TimeSpan.FromSeconds(600));
**During a test:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
driver.execute_script("tb:throttle", "3G")
# or
driver.execute_script("tb:throttle", {
"downloadSpeed": 10 * 1024,
"uploadSpeed": 10 * 1024,
"latency": 0,
"loss": 0
})
((JavascriptExecutor) driver).executeScript("tb:throttle", "3G");
// or
Map throttleMap = new HashMap<>();
throttleMap.put("downloadSpeed", 10 * 1024);
throttleMap.put("uploadSpeed", 10 * 1024);
throttleMap.put("latency", 0);
throttleMap.put("loss", 0);
((JavascriptExecutor) driver).executeScript("tb:throttle", throttleMap);
$driver->executeScript("tb:throttle", ["3G"]);
// or
$driver->executeScript("tb:throttle", [
"downloadSpeed" => 10 * 1024,
"uploadSpeed" => 10 * 1024,
"latency" => 0,
"loss" => 0
]);
driver.execute_script("tb:throttle", "3G")
# or
driver.execute_script("tb:throttle", {
"downloadSpeed": 10 * 1024,
"uploadSpeed": 10 * 1024,
"latency": 0,
"loss": 0
})
browser.execute('tb:throttle', '3G')
// or
browser.execute('tb:throttle', {
"downloadSpeed": 10 * 1024,
"uploadSpeed": 10 * 1024,
"latency": 0,
"loss": 0
})
((IJavaScriptExecutor)driver).ExecuteScript("tb:throttle", "3G");
// or
((IJavaScriptExecutor)driver).ExecuteScript("tb:throttle", "{\"downloadSpeed\":10*1024, \"uploadSpeed\": 10*1024, \"latency\": 0, \"loss\": 0}");
To stop network throttling during your test, pass `disable: true` in the `tb:throttle` options via the `execute_script`.
## mobile: shell
You can execute ADB shell commands during your automated test, with Appium's `mobile: shell` functionality.
This allows you to fetch or set various settings for the device or emulator, during your automated test. For example, send a custom `input` event, or use `dumpsys`, `pm clear` or others.
When running a test on an emulator, all ADB shell commands are allowed.
For tests running on physical devices, we maintain a whitelist of allowed commands
**Whitelisted ADB commands for physical devices** :
- am start
- am force-stop
- pm clear
- input
- (ls|cp|mkdir|echo|grep|cut|pwd|dumpsys)
- getprop
- am compat enable
- cmd connectivity airplane-mode (enable|disable)
- settings put global (animator\_duration\_scale|transition\_animation\_scale|window\_animation\_scale) \
- setprop debug.firebase.analytics.app \
- setprop (log.tag.FA|log.tag.FA-SVC) (ERROR|WARN|INFO|DEBUG|VERBOSE)
- setprop (log.tag.FA|log.tag.FA-SVC|log.tag.GAv4|log.tag.GAv4-SVC) (ERROR|WARN|INFO|DEBUG|VERBOSE)
- appops set \ REQUEST\_INSTALL\_PACKAGES allow
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
driver.execute_script('mobile: shell', { command: 'input', args: ['keyevent', 3] })
driver.executeScript("mobile: shell", ImmutableMap.of(
"command", "input",
"args", ImmutableList.of("keyevent", "3")
));
$args = [
"command" => "input",
"args" => ["keyevent", "3"]
];
$driver->executeScript("mobile: shell", $args);
args = {
"command": "input",
"args": ["keyevent", "3"]
}
driver.execute_script("mobile: shell", args)
await driver.executeScript("mobile: shell", {
command: "input",
args: ["keyevent", "3"]
});
var args = new Dictionary
{
{ "command", "input" },
{ "args", new List { "keyevent", "3" } }
};
((IJavaScriptExecutor)driver).ExecuteScript("mobile: shell", args);
## Selecting the device
Capabilities for picking the device, OS version and form factor your Appium test runs on.
### Device Name
When running a mobile automated test, you'll need to specify on which [mobile device](https://testingbot.com/support/app-automate/devices) (or [simulator/emulator](https://testingbot.com/support/web-automate/browsers)) you want to test.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
deviceName: "Pixel 9"
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("deviceName", "Pixel 9");
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'deviceName': "Pixel 9",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"deviceName": 'Pixel 9',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name,
["deviceName"] = "Pixel 9"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Physical Devices Only
We offer special parameters which you can use to allocate a device:
Regex Input | Result || `"iPhone.*"` | This will allocate any available iPhone device (phone) |
| `".*Galaxy.*"` | This will allocate any of the available Galaxy devices (phone or tablet) |
| `"*"` | This will allocate a random available device, either iOS or Android device |
| `"iPhone [8-11]"` | This will allocate either an iPhone 8 or 11 |
| `"iPhone 6.*"` | This will allocate either an iPhone 6 or 6S |
Some Examples:
// find any iPhone, except 6 or 6s
capabilities.setCapability("deviceName", "^(iPhone.*)(?!6|6S)$");
// find any device which name starts with Pixel
capabilities.setCapability("deviceName", "Pixel.*");
### Device Version
You can specify the version of the device you want to target. In case of physical devices, you might want to use regex/wildcard patterns to target a broad range of devices. This way your chance of hitting an occupied device during your automated test decreases significantly.
Instead of only targetting a specific iOS or Android device, you can target a wider range of devices.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
version: "(16|17).*"
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("appium:platformName", "Android");
caps.setCapability("appium:deviceName", "*");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("appium:platformVersion", "(13|14).*");
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'appium:deviceName': "*",
'appium:platformVersion': '(13|14).*',
'platformName': "Android",
'goog:chromeOptions': {'w3c': True}
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"appium:deviceName": '*',
"appium:platformVersion": '(13|14).*',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
PlatformName = "Android"
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name,
["deviceName"] = "*",
["version"] = "(13|14).*"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Some Examples:
// target multiple specific versions of an iPhone device
capabilities.setCapability("appium:deviceName", "iPhone.*");
capabilities.setCapability("appium:platformVersion", "18.0|15.0|14.2|14");
// find any Pixel device with Android version 15 or 16
capabilities.setCapability("appium:deviceName", "Pixel.*");
capabilities.setCapability("appium:platformVersion", "(15|16).*");
### Tablet Only
You can specify this capability when you only want to allocate a tablet device.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
deviceName: "*",
tabletOnly: true
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("tabletOnly", true);
tbOptions.setCapability("deviceName", "*");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'deviceName': "*",
'tabletOnly': true,
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"deviceName": '*',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"tabletOnly": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name,
["deviceName"] = "*",
["tabletOnly"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
### Phone Only
You can specify this capability when you only want to allocate a phone device.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
deviceName: "*",
phoneOnly: true
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("phoneOnly", true);
tbOptions.setCapability("deviceName", "*");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'deviceName': "*",
'phoneOnly': true,
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"deviceName": '*',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"phoneOnly": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name,
["deviceName"] = "*",
["phoneOnly"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
## Customizing the test
Capabilities for naming, grouping, timing out and attaching metadata to your Appium test sessions.
### Change Test Name
Add a name to this test, which will show up in the TestingBot member area and API.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["name"] = "My Test Name"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("name", "My Test Name");
$caps = array(
"name" => "My Test Name"
);
capabilities = {
"name" : "My Test Name"
}
const capabilities = {
"name" : "My Test Name"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("name", "My Test Name");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
deviceName: "Galaxy S23",
"tb:options" => {
"name" : "My Test Name"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("name", "My Test Name");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'name' => "My Test Name"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': "My Test Name"
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'deviceName': "Galaxy S23",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"name": "My Test Name"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "13.0",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = "My Test Name"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || string | unnamed test |
### Group Tests
A key you can use to group certain tests in the same build (for example in Jenkins).
The builds will appear in our member area.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["build"] = "My First Build"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("build", "My First Build");
$caps = array(
"build" => "My First Build"
);
capabilities = {
"build" : "My First Build"
}
const capabilities = {
"build" : "My First Build"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("build", "My First Build");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
deviceName: "Galaxy S23",
"tb:options" => {
"build" : "My First Build"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("build", "My First Build");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'build' => "My First Build"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'build': "My First Build"
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'deviceName': "Galaxy S23",
'version': '13.0',
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"build": "My First Build"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "13.0",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["build"] = "My First Build"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type || string |
### Idle Timeout
The maximum amount of time a device will wait before proceeding to the next step in your test.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["idletimeout"] = 130
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("idletimeout", 130);
$caps = array(
"idletimeout" => 130
);
capabilities = {
"idletimeout" : 130
}
const capabilities = {
"idletimeout" : 130
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("idletimeout", 130);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
deviceName: "Galaxy S23",
"tb:options" => {
"idletimeout" : 130
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("idletimeout", 130);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'idletimeout' => 130
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'idletimeout': 130
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"idletimeout": 130
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "13.0",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["idletimeout"] = 130
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || int (specify number of seconds) | 130 seconds |
### Maximum Test Duration
The maximum duration for a single test. This is a safeguard to prevent bad tests from using up your credits.
We generally recommend to keep tests short (less than 10 minutes). It's better to split up large tests in smaller individual tests.
This keeps your tests fast and allows for more parallelization of your tests.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["maxduration"] = 1800
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("maxduration", 1800);
$caps = array(
"maxduration" => 1800
);
capabilities = {
"maxduration" : 1800
}
const capabilities = {
"maxduration" : 1800
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("maxduration", 1800);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
deviceName: "Galaxy S23",
"tb:options" => {
"maxduration" : 1800
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("maxduration", 1800);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'maxduration' => 1800
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'maxduration': 1800
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"maxduration": 1800
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "13.0",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["maxduration"] = 1800
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || int (specify number of seconds) | 1800 seconds (30 minutes) |
### Custom Metadata
Send along custom data, for example your release, server, commit hash, ...
This will show up on the test detail page in the TestingBot member area.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["extra"] = "Extra Information"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("extra", "Extra Information");
$caps = array(
"extra" => "Extra Information"
);
capabilities = {
"extra" : "Extra Information"
}
const capabilities = {
"extra" : "Extra Information"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("extra", "Extra Information");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
deviceName: "Galaxy S23",
"tb:options" => {
"extra" : "Extra Information"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("extra", "Extra Information");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'extra' => "Extra Information"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'extra': "Extra Information"
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"extra": "Extra Information"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "13.0",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["extra"] = "Extra Information"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type || string |
## Privacy and session recording
Capabilities for controlling screenshots, video recording, log capture and dashboard privacy.
### Taking screenshots during your tests
By default, TestingBot does not capture screenshots at every step of your mobile test. If you wish to take a screenshot for every step, please add this capability (set to `true`) to your request.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["screenshot"] = true
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("screenshot", true);
$caps = array(
"screenshot" => true
);
capabilities = {
"screenshot" : True
}
const capabilities = {
"screenshot" : true
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("screenshot", true);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
deviceName: "Galaxy S23",
"tb:options" => {
screenshot: true
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("screenshot", true);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("appium:app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'screenshot' => true
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'screenshot': True
}
chromeOpts = {
'appium:app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"screenshot": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "23",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["screenshot"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || boolean | false |
### Make a video of your tests
By default we record a video of your test, which is accessible in the member area. If you do not wish to have this, you can disable it with this option.
Video should not slow down your test considerably.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["screenrecorder"] = true
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("screenrecorder", true);
$caps = array(
"screenrecorder" => true
);
capabilities = {
"screenrecorder" : True
}
const capabilities = {
"screenrecorder" : true
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("screenrecorder", true);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
"tb:options" => {
screenrecorder: true
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("screenrecorder", true);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'screenrecorder' => true
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'screenrecorder': True
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"screenrecorder": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "13.0",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["screenrecorder"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || boolean | true |
### Test Privacy
Make the test results for this test public so that everyone can access the results.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["public"] = false
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("public", false);
$caps = array(
"public" => false
);
capabilities = {
"public" : False
}
const capabilities = {
"public" : false
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("public", false);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
"tb:options" => {
"public" : false
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("public", false);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'public' => false
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'public': False
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"public": false
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "13.0",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["public"] = false
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value || boolean | false |
### Customize Logging
By default, TestingBot records logs of all test actions and its drivers.
Set this option to `false` if you don't want TestingBot to record anything (for example, if you have sensitive data).
You will not see any test logs in our member dashboard.
Set to `strip-parameters` to prevent the POST/GET parameters from being logged on the TestingBot test detail page (does not affect other logs like Appium logs, Chromedriver logs, ...).
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["recordLogs"] = true
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("recordLogs", true);
$caps = array(
"recordLogs" => true
);
capabilities = {
"recordLogs" : True
}
const capabilities = {
"recordLogs" : true
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("recordLogs", true);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "Android",
app: "https://testingbot.com/appium/sample.apk",
version: "13.0",
deviceName: "Galaxy S23",
"tb:options" => {
"recordLogs" : true
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("recordLogs", true);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("app", "https://testingbot.com/appium/sample.apk");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('Android');
$capabilities->setCapability('tb:options', array(
'recordLogs' => true
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'recordLogs': True
}
chromeOpts = {
'app': "https://testingbot.com/appium/sample.apk",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"app": 'https://testingbot.com/appium/sample.apk',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"recordLogs": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
Version = "13.0",
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["recordLogs"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Value Type | Default Value | Possible Values: || string | "true" | `true`, `false`, or `strip-parameters` |
## Frequently asked questions
How do I select a specific device for my Appium test?
Set the `appium:deviceName` capability (for example `"Pixel 9"`) plus `platformName` and `appium:platformVersion`. See the [Device Name section](https://testingbot.com#devicename) for code snippets in Java, Python, Node.js, Ruby and C#.
How do I change the test name shown in the TestingBot dashboard?
Set `name` inside `tb:options`. See the [Change Test Name section](https://testingbot.com#name) for examples per language.
How do I group multiple Appium tests under one build?
Set `build` inside `tb:options` with the same identifier across all tests. See the [Group Tests section](https://testingbot.com#build) for examples.
What is the default Appium idle timeout on TestingBot?
130 seconds. The session ends if no Appium command is received within that window. Override with the `idleTimeout` capability inside `tb:options`, see the [Idle Timeout section](https://testingbot.com#idletimeout).
What is the default maximum Appium test duration?
1800 seconds (30 minutes). After that the session is terminated. Override with `maxDuration` inside `tb:options`, see the [Maximum Test Duration section](https://testingbot.com#maxduration).
How do I attach custom metadata to my Appium test?
Pass a `custom-data` object inside `tb:options`. The dashboard and the REST API expose it back unchanged. See the [Custom Metadata section](https://testingbot.com#customdata).
Can I disable screenshots or screen recording for privacy?
Yes. Set `screenshot: false` and/or `screenrecorder: false` inside `tb:options`. For sensitive tests use the `private` capability to redact session metadata. See the [Privacy section](https://testingbot.com#privacy).
Can I change the timezone, locale or geolocation of the device?
Yes. Pass `timeZone`, `appium:locale`, `appium:language` or `geoCountryCode` inside the capability map. See the [timezone section](https://testingbot.com#timezone) and [geolocation section](https://testingbot.com#geo).
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/appium/instrumentation
# App Instrumentation with Appium
App instrumentation means adding extra hooks to your app while it runs on TestingBot's devices. It lets your automated tests interact with things that normally require human interaction, such as authentication, taking a specific photo or making a call.
With app instrumentation, you can perform the following tasks in an automated Appium test script:
- [Inject images into the camera](https://testingbot.com#image-injection): feed a test image to the virtual camera instead of using a real one.
- [Biometric authentication](https://testingbot.com#biometric): simulate fingerprint or Touch ID prompts in your tests.
- [Receive calls and SMS messages](https://testingbot.com#calls-sms): emulate incoming phone calls or text messages to see how your app responds.
## Injecting images into camera
Android Emulators Only
Image injection allows you to specify a base64 encoded image to the virtual camera of the Android emulator.
The test script will fetch the content of the image, convert it to base64 and send it to the TestingBot grid. The image will be used by the remote device to inject into the camera.
Make sure to include the `images` Appium plugin, as it is required for this to function correctly.
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
import java.util.Base64;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class ImageInjectionExample {
public static String imageToBase64(String url) throws Exception {
byte[] data = java.net.URI.create(url).toURL().openStream().readAllBytes();
return Base64.getEncoder().encodeToString(data);
}
public static void main(String[] args) throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("browserName", "chrome");
caps.setCapability("browserVersion", "15");
caps.setCapability("appium:deviceName", "Pixel 9");
caps.setCapability("tb:appiumPlugins", new String[]{"images"});
caps.setCapability("tb:appiumVersion", "2.11.3");
caps.setCapability("appium:injectedImageProperties", new HashMap<>());
AppiumDriver driver = new AppiumDriver(new URL("https://key:secret@hub.testingbot.com/wd/hub"), caps);
String base64Image = imageToBase64("https://upload.wikimedia.org/wikipedia/commons/5/5e/QR_Code_example.png");
driver.executeScript("mobile: injectEmulatorCameraImage", new HashMap() {{
put("payload", base64Image);
}});
Thread.sleep(30000);
base64Image = imageToBase64("https://testingbot.com/assets/about.png");
driver.executeScript("mobile: injectEmulatorCameraImage", new HashMap() {{
put("payload", base64Image);
}});
Thread.sleep(30000);
driver.quit();
}
}
const { remote } = require('webdriverio');
const fs = require('fs');
const fetch = require('node-fetch');
async function imageToBase64(url) {
const res = await fetch(url);
const buffer = await res.buffer();
return buffer.toString('base64');
}
(async () => {
const caps = {
platformName: 'Android',
'appium:deviceName': 'Pixel 9',
browserName: 'chrome',
browserVersion: '15',
'tb:options': { 'appiumVersion': '2.11.3', 'appiumPlugins': ['images'] },
'appium:injectedImageProperties': {}
};
const driver = await remote({
protocol: 'https',
hostname: 'hub.testingbot.com',
port: 443,
path: '/wd/hub',
user: 'key',
key: 'secret',
capabilities: caps
});
let base64Image = await imageToBase64('https://upload.wikimedia.org/wikipedia/commons/5/5e/QR_Code_example.png');
await driver.executeScript('mobile: injectEmulatorCameraImage', [{ payload: base64Image }]);
console.log("Injected first image");
await driver.pause(30000);
base64Image = await imageToBase64('https://testingbot.com/assets/about.png');
await driver.executeScript('mobile: injectEmulatorCameraImage', [{ payload: base64Image }]);
console.log("Injected second image");
await driver.pause(30000);
await driver.deleteSession();
})();
from appium import webdriver
import base64
import requests
import time
def image_to_base64(url):
response = requests.get(url)
return base64.b64encode(response.content).decode('utf-8')
caps = {
"platformName": "Android",
"browserName": "chrome",
"browserVersion": "15",
"appium:deviceName": "Pixel 9",
"tb:appiumPlugins": ["images"],
"tb:appiumVersion": "2.11.3",
"appium:injectedImageProperties": {}
}
driver = webdriver.Remote("https://key:secret@hub.testingbot.com/wd/hub", caps)
base64_image = image_to_base64("https://upload.wikimedia.org/wikipedia/commons/5/5e/QR_Code_example.png")
driver.execute_script("mobile: injectEmulatorCameraImage", { "payload": base64_image })
time.sleep(30)
print("Changing image")
base64_image = image_to_base64("https://testingbot.com/assets/about.png")
driver.execute_script("mobile: injectEmulatorCameraImage", { "payload": base64_image })
time.sleep(30)
driver.quit()
#!/usr/bin/env ruby
require 'rubygems'
require 'appium_lib'
require 'base64'
require 'open-uri'
def image_to_base64(image_url)
image_data = URI.parse(image_url).open.read
Base64.strict_encode64(image_data)
end
caps = {
"browserName" => "chrome",
"browserVersion" => "15",
"appium:deviceName" => "Pixel 9",
"platformName" => "Android",
"tb:appiumPlugins" => ["images"],
"tb:appiumVersion" => "2.11.3",
"appium:injectedImageProperties" => {}
}
appium_driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://key:secret@hub.testingbot.com/wd/hub"
}
}, true)
driver = appium_driver.start_driver
base64_image = image_to_base64("https://upload.wikimedia.org/wikipedia/commons/5/5e/QR_Code_example.png")
driver.execute_script('mobile: injectEmulatorCameraImage', { payload: base64_image })
sleep 30
puts "Changing image"
base64_image = image_to_base64("https://testingbot.com/assets/about.png")
driver.execute_script('mobile: injectEmulatorCameraImage', { payload: base64_image })
sleep 30
driver.quit
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Remote;
using System;
using System.Net.Http;
using System.Threading.Tasks;
class ImageInjectionExample
{
static async Task ImageToBase64(string url)
{
using (HttpClient client = new HttpClient())
{
var data = await client.GetByteArrayAsync(url);
return Convert.ToBase64String(data);
}
}
static async Task Main(string[] args)
{
var options = new AppiumOptions();
options.PlatformName = "Android";
options.AddAdditionalAppiumOption("browserName", "chrome");
options.AddAdditionalAppiumOption("browserVersion", "15");
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 9");
options.AddAdditionalAppiumOption("tb:appiumPlugins", new string[] { "images" });
options.AddAdditionalAppiumOption("tb:appiumVersion", "2.11.3");
options.AddAdditionalAppiumOption("appium:injectedImageProperties", new { });
var driver = new RemoteWebDriver(
new Uri("https://key:secret@hub.testingbot.com/wd/hub"),
options
);
var base64Image = await ImageToBase64("https://upload.wikimedia.org/wikipedia/commons/5/5e/QR_Code_example.png");
driver.ExecuteScript("mobile: injectEmulatorCameraImage", new Dictionary { { "payload", base64Image } });
Console.WriteLine("Injected first image");
System.Threading.Thread.Sleep(30000);
base64Image = await ImageToBase64("https://testingbot.com/assets/about.png");
driver.ExecuteScript("mobile: injectEmulatorCameraImage", new Dictionary { { "payload", base64Image } });
Console.WriteLine("Injected second image");
System.Threading.Thread.Sleep(30000);
driver.Quit();
}
}
## Biometric authentication (Finger Print, Touch Id)
### Android fingerprint authentication
Emulate fingerprints on Android Emulator, with the [mobile:fingerprint](https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#mobile-fingerprint) command. Only works on API 23+ with [Appium version 2 or higher](https://testingbot.com/support/app-automate/appium/appium-versions).
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
import java.util.HashMap;
public class FingerprintExample {
public static void main(String[] args) throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("appium:deviceName", "Pixel 9");
caps.setCapability("browserName", "chrome");
caps.setCapability("browserVersion", "15");
caps.setCapability("tb:appiumVersion", "2.11.3");
AppiumDriver driver = new AppiumDriver(new URL("https://key:secret@hub.testingbot.com/wd/hub"), caps);
HashMap args1 = new HashMap<>();
args1.put("fingerprintId", 1);
driver.executeScript("mobile: fingerprint", args1);
Thread.sleep(5000);
driver.quit();
}
}
const { remote } = require('webdriverio');
(async () => {
const caps = {
platformName: 'Android',
'appium:deviceName': 'Pixel 9',
browserName: 'chrome',
browserVersion: '15',
'tb:appiumVersion': '2.11.3'
};
const driver = await remote({
protocol: 'https',
hostname: 'hub.testingbot.com',
port: 443,
path: '/wd/hub',
user: 'key',
key: 'secret',
capabilities: caps
});
await driver.executeScript('mobile: fingerprint', [{ fingerprintId: 1 }]);
await driver.pause(5000);
await driver.deleteSession();
})();
from appium import webdriver
import time
caps = {
"platformName": "Android",
"appium:deviceName": "Pixel 9",
"browserName": "chrome",
"browserVersion": "15",
"tb:appiumVersion": "2.11.3"
}
driver = webdriver.Remote("https://key:secret@hub.testingbot.com/wd/hub", caps)
# Simulate fingerprint auth with ID 1
driver.execute_script("mobile: fingerprint", { "fingerprintId": 1 })
time.sleep(5)
driver.quit()
require 'appium_lib'
caps = {
"platformName" => "Android",
"appium:deviceName" => "Pixel 9",
"browserName" => "chrome",
"browserVersion" => "15",
"tb:appiumVersion" => "2.11.3"
}
driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://key:secret@hub.testingbot.com/wd/hub"
}
}, true).start_driver
driver.execute_script("mobile: fingerprint", { fingerprintId: 1 })
sleep 5
driver.quit
using OpenQA.Selenium.Remote;
using System;
using System.Collections.Generic;
using OpenQA.Selenium.Appium;
class FingerprintExample {
static void Main(string[] args) {
var options = new AppiumOptions();
options.PlatformName = "Android";
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 9");
options.AddAdditionalAppiumOption("browserName", "chrome");
options.AddAdditionalAppiumOption("browserVersion", "15");
options.AddAdditionalAppiumOption("tb:appiumVersion", "2.11.3");
var driver = new RemoteWebDriver(
new Uri("https://key:secret@hub.testingbot.com/wd/hub"), options);
driver.ExecuteScript("mobile: fingerprint", new Dictionary {
{ "fingerprintId", 1 }
});
System.Threading.Thread.Sleep(5000);
driver.Quit();
}
}
### iOS TouchID/FaceID authentication
You can simulate a biometric match/non-match event with TouchID and FaceID, with the [mobile: sendBiometricMatch](https://appium.github.io/appium-xcuitest-driver/latest/reference/execute-methods/#mobile-sendbiometricmatch) command.
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
import java.util.HashMap;
public class TouchIDExample {
public static void main(String[] args) throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "iOS");
caps.setCapability("appium:deviceName", "iPhone 15");
caps.setCapability("appium:platformVersion", "18.0");
caps.setCapability("browserName", "safari");
caps.setCapability("tb:appiumVersion", "2.11.3");
AppiumDriver driver = new AppiumDriver(new URL("https://key:secret@hub.testingbot.com/wd/hub"), caps);
HashMap args1 = new HashMap<>();
args1.put("type", "touchId");
args1.put("match", true);
driver.executeScript("mobile: sendBiometricMatch", args1);
Thread.sleep(5000);
HashMap args2 = new HashMap<>();
args2.put("type", "touchId");
args2.put("match", false);
driver.executeScript("mobile: sendBiometricMatch", args2);
Thread.sleep(5000);
driver.quit();
}
}
const { remote } = require('webdriverio');
(async () => {
const caps = {
platformName: 'iOS',
'appium:deviceName': 'iPhone 15',
'appium:platformVersion': '18.0',
browserName: 'safari',
'tb:appiumVersion': '2.11.3'
};
const driver = await remote({
protocol: 'https',
hostname: 'hub.testingbot.com',
port: 443,
path: '/wd/hub',
user: 'key',
key: 'secret',
capabilities: caps
});
// Successful Touch ID
await driver.executeScript('mobile: sendBiometricMatch', [{ type: 'touchId', match: true }]);
await driver.pause(5000);
// Failed Touch ID
await driver.executeScript('mobile: sendBiometricMatch', [{ type: 'touchId', match: false }]);
await driver.pause(5000);
await driver.deleteSession();
})();
from appium import webdriver
import time
caps = {
"platformName": "iOS",
"appium:deviceName": "iPhone 15",
"appium:platformVersion": "18.0",
"browserName": "safari",
"tb:appiumVersion": "2.11.3"
}
driver = webdriver.Remote("https://key:secret@hub.testingbot.com/wd/hub", caps)
# Successful Touch ID
driver.execute_script("mobile: sendBiometricMatch", { "type": "touchId", "match": True })
time.sleep(5)
# Failed Touch ID
driver.execute_script("mobile: sendBiometricMatch", { "type": "touchId", "match": False })
time.sleep(5)
driver.quit()
require 'appium_lib'
caps = {
"platformName" => "iOS",
"appium:deviceName" => "iPhone 15",
"appium:platformVersion" => "18.0",
"browserName" => "safari",
"tb:appiumVersion" => "2.11.3"
}
driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://key:secret@hub.testingbot.com/wd/hub"
}
}, true).start_driver
# Success
driver.execute_script("mobile: sendBiometricMatch", { type: "touchId", match: true })
sleep 5
# Failure
driver.execute_script("mobile: sendBiometricMatch", { type: "touchId", match: false })
sleep 5
driver.quit
using OpenQA.Selenium.Remote;
using System;
using System.Collections.Generic;
using OpenQA.Selenium.Appium;
class TouchIDExample {
static void Main(string[] args) {
var options = new AppiumOptions();
options.PlatformName = "iOS";
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 15");
options.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
options.AddAdditionalAppiumOption("browserName", "safari");
options.AddAdditionalAppiumOption("tb:appiumVersion", "2.11.3");
var driver = new RemoteWebDriver(
new Uri("https://key:secret@hub.testingbot.com/wd/hub"), options);
driver.ExecuteScript("mobile: sendBiometricMatch", new Dictionary {
{ "type", "touchId" },
{ "match", true }
});
System.Threading.Thread.Sleep(5000);
driver.ExecuteScript("mobile: sendBiometricMatch", new Dictionary {
{ "type", "touchId" },
{ "match", false }
});
System.Threading.Thread.Sleep(5000);
driver.Quit();
}
}
## Receiving calls and text messages (SMS)
You can simulate an incoming call or text message with Appium. Please see the examples below.
[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Python](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.connection.GsmCallActions;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
public class AndroidGsmExample {
public static void main(String[] args) throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("appium:deviceName", "Pixel 9");
caps.setCapability("browserName", "chrome");
caps.setCapability("browserVersion", "15");
caps.setCapability("tb:appiumVersion", "2.11.3");
AndroidDriver driver = new AndroidDriver(new URL("https://key:secret@hub.testingbot.com/wd/hub"), caps);
// Send SMS
driver.sendSMS("5555555555", "Your message here!");
// Make and handle a phone call
driver.makeGsmCall("5555555555", GsmCallActions.CALL);
Thread.sleep(2000);
driver.makeGsmCall("5555555555", GsmCallActions.ACCEPT);
Thread.sleep(2000);
driver.makeGsmCall("5555555555", GsmCallActions.CANCEL);
driver.quit();
}
}
const { remote } = require('webdriverio');
(async () => {
const caps = {
platformName: 'Android',
'appium:deviceName': 'Pixel 9',
browserName: 'chrome',
browserVersion: '15',
'tb:appiumVersion': '2.11.3'
};
const driver = await remote({
protocol: 'https',
hostname: 'hub.testingbot.com',
port: 443,
path: '/wd/hub',
user: 'key',
key: 'secret',
capabilities: caps
});
// Send SMS
await driver.sendSms('5555555555', 'Your message here!');
// Phone call
await driver.makeGsmCall('5555555555', 'call');
await driver.pause(2000);
await driver.makeGsmCall('5555555555', 'accept');
await driver.pause(2000);
await driver.makeGsmCall('5555555555', 'cancel');
await driver.deleteSession();
})();
from appium import webdriver
import time
caps = {
"platformName": "Android",
"appium:deviceName": "Pixel 9",
"browserName": "chrome",
"browserVersion": "15",
"tb:appiumVersion": "2.11.3"
}
driver = webdriver.Remote("https://key:secret@hub.testingbot.com/wd/hub", caps)
# Send SMS
driver.send_sms("5555555555", "Your message here!")
# Phone call
driver.make_gsm_call("5555555555", "call")
time.sleep(2)
driver.make_gsm_call("5555555555", "accept")
time.sleep(2)
driver.make_gsm_call("5555555555", "cancel")
driver.quit()
require 'appium_lib'
caps = {
"platformName" => "Android",
"appium:deviceName" => "Pixel 9",
"browserName" => "chrome",
"browserVersion" => "15",
"tb:appiumVersion" => "2.11.3"
}
driver = Appium::Driver.new({
caps: caps,
appium_lib: {
server_url: "https://key:secret@hub.testingbot.com/wd/hub"
}
}, true).start_driver
# Send SMS
driver.send_sms("5555555555", "Your message here!")
# Phone call
driver.make_gsm_call("5555555555", "call")
sleep 2
driver.make_gsm_call("5555555555", "accept")
sleep 2
driver.make_gsm_call("5555555555", "cancel")
driver.quit
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using System;
class AndroidGsmExample {
static void Main(string[] args) {
var options = new AppiumOptions();
options.PlatformName = "Android";
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 9");
options.AddAdditionalAppiumOption("browserName", "chrome");
options.AddAdditionalAppiumOption("browserVersion", "15");
options.AddAdditionalAppiumOption("tb:appiumVersion", "2.11.3");
var driver = new AndroidDriver(new Uri("https://key:secret@hub.testingbot.com/wd/hub"), options);
// Send SMS
driver.SendSMS("5555555555", "Your message here!");
// Phone call
driver.MakeGsmCall("5555555555", "call");
System.Threading.Thread.Sleep(2000);
driver.MakeGsmCall("5555555555", "accept");
System.Threading.Thread.Sleep(2000);
driver.MakeGsmCall("5555555555", "cancel");
driver.Quit();
}
}
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/xcuitest
# XCUITest Intro
TestingBot provides a grid of physical iOS devices in the cloud, ready to run your XCUITests.
XCUITest (a framework within the larger XCTest Testing framework) is Apple's automation framework, which comes with Apple's XCode IDE, capable of running automated UI tests on iPhone and iPad devices.
## Setup
XCUITest is packaged in a `zip` file. You will need two files to perform XCUITesting on our cloud:
- An `.ipa` file of the actual mobile application that you want to test.
- Another `.zip` file of the XCUITests.
If you just want to give this a try, we have a [TestingBot XCUITest Demo App](https://github.com/testingbot/xcuitest-example-app) which you can use with the following steps.
## TestingBot CLI
The easiest way to run XCUITests on TestingBot is using our CLI tool. It handles uploading your app, test ZIP, and running tests in a single command.
### Installation
npm install -g @testingbot/cli
### Authentication
Authenticate using this command:
testingbot login
This opens your browser for authentication. After logging in, your credentials are saved to `~/.testingbot` and you can start using the CLI.
#### Other Authentication Methods
- **Command-line options** : `--api-key` and `--api-secret`
- **Environment variables** : `TB_KEY` and `TB_SECRET`
- **Config file** : Create `~/.testingbot` with content `key:secret`
### Quick Start
Run XCUITests with a single command:
testingbot xcuitest app.ipa app-test.zip --device "iPhone 16" --real-device
The CLI will:
1. Upload your app IPA to TestingBot
2. Upload your test ZIP
3. Start the test run
4. Show real-time progress and results
## Upload iOS App
You can upload your `.ipa` file to TestingBot storage with our [REST-API](https://testingbot.com/support/api).
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
When using the CLI, the app upload is handled automatically as part of the `testingbot xcuitest` command. Simply specify your app IPA file as the first argument:
testingbot xcuitest /path/to/app/sample-debug.ipa /path/to/test/app-test.zip --device "iPhone 16" --real-device
Alternatively, you can use the `--app` option:
testingbot xcuitest --app /path/to/app/sample-debug.ipa --test-app /path/to/test/app-test.zip --device "iPhone 16" --real-device
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/xcuitest/app" \
-F "file=@/path/to/app/file/sample-debug.ipa"
The API response will include an `id` response, which you can use in the other API calls.
{
"id": 4012
}
## Upload XCUITest Suite
If you do not have a `.zip` file yet, please see our [Build Testsuite](https://testingbot.com/support/app-automate/xcuitest/build-testsuite) documentation.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
When using the CLI, the test ZIP upload is handled automatically. Specify your test ZIP as the second argument:
testingbot xcuitest app.ipa app-test.zip --device "iPhone 16" --real-device
Or use the `--test-app` option:
testingbot xcuitest --app app.ipa --test-app app-test.zip --device "iPhone 16" --real-device
To upload the XCUITest suite, in `.zip` file, please use our [REST-API](https://testingbot.com/support/api).
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/xcuitest/:id/tests" \
-F "file=@/path/to/app/file/application-debug-test.zip"
Replace the `:id` with the identifier you received during the app upload call. (In this example 4012)
## Run XCUI tests
Once you've uploaded both your app and test suite, you can start a XCUITest on the TestingBot cloud.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
Use the CLI to run tests with device selection options:
# Basic usage with device selection
testingbot xcuitest app.ipa app-test.zip --device "iPhone 16" --real-device
# With test name and build identifier
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 16" \
--real-device \
--name "Smoke Tests" \
--build "v2.1.0"
### Device Options
| Option | Description |
| --- | --- |
| `--device ` | Device name (e.g., "iPhone 16", "iPad.\*") |
| `--platform-version ` | iOS version (e.g., "17.0", "18.2") |
| `--real-device` | Use a real device instead of simulator |
| `--tablet-only` | Only allocate tablet devices (iPads) |
| `--phone-only` | Only allocate phone devices (iPhones) |
| `--orientation ` | Screen orientation: PORTRAIT or LANDSCAPE |
#### Localization & Network
# With localization settings
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 16" \
--real-device \
--locale "DE" \
--language "de" \
--timezone "Europe/Berlin"
# With network throttling and geolocation
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 16" \
--real-device \
--throttle-network "3G" \
--geo-location "DE"
Specify one or more capabilities, to indicate on which iOS devices you want to run your tests on.
 iOS 18.2 › iPhone 16
Loading environments...
Please wait while we load the available browsers and platforms.
Replace the `:id` with the identifier you received during the app upload call. (In this example 4012)
We offer special parameters which you can use to allocate a device:
| Regex Input | Result |
| --- | --- |
| `"iPhone.*"` | This will allocate any available iPhone device (phone) |
| `"*"` | This will allocate a random available device |
| `"iPhone (8|11)"` | This will allocate either an iPhone 8 or 11 |
| `"iPhone 6S?$"` | This will allocate either an iPhone 6 or 6S |
Some Examples:
# find any iPhone, except 6 or 6S
-d '{"capabilities":[{"deviceName":"^iPhone(?! 6S?$).*", "platformName":"iOS", "realDevice": true}]}'
# find any device which name starts with iPad
-d '{"capabilities":[{"deviceName":"iPad.*", "platformName":"iOS", "realDevice": true}]}'
### Tablet Only
You can specify the `tabletOnly` capability when you only want to allocate a tablet device.
### Phone Only
You can specify the `phoneOnly` capability when you only want to allocate a phone device.
-d '{"capabilities":[{"deviceName":"*", "tabletOnly": true, "platformName":"iOS", "realDevice": true}]}'
-d '{"capabilities":[{"deviceName":"*", "phoneOnly": true, "platformName":"iOS", "realDevice": true}]}'
## View XCUITest Results
The XCUITest results will be available in the [TestingBot dashboard](https://testingbot.com/members).
Each test result contains a video, test logs and other meta data generated during the test run.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
By default, the CLI waits for test completion and displays real-time progress:
testingbot xcuitest app.ipa app-test.zip --device "iPhone 16" --real-device
The CLI will show:
- Upload progress for app and test ZIP
- Device allocation status
- Live output from XCUITests
- Final pass/fail status
### Async Mode
Use `--async` to start tests without waiting for results:
testingbot xcuitest app.ipa app-test.zip --device "iPhone 16" --real-device --async
#### Download Reports
Download a JUnit or HTML report after test completion:
testingbot xcuitest app.ipa app-test.zip --device "iPhone 16" --real-device \
--report junit \
--report-output-dir ./reports
You can also use an API call to get the results from the run(s):
curl -u api_key:api_secret \
"https://api.testingbot.com/v1/app-automate/xcuitest/:id"
Replace the `:id` with the identifier you received during the app upload call.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/xcuitest/build-testsuite
# Building XCUITest
To test with XCUITest on TestingBot, you will need to build and export the XCUITests you have created.
If you do not have a `zip` file of the XCUITest suite, you can follow the examples below.
The `zip` file should contain a folder similar to this:
xcuitest-sampleUITests-Runner.app
## Using XCode (13 and higher)
1. From the XCode schemes dropdown menu, select your app scheme and **Any iOS Device** as device
2. Click **Product -\> Clean** (`Cmd+Shift+K`)
3. Click **Product -\> Build For -\> Testing** (`Cmd+Shift+U`)
4. Go to **Product -\> Show Build Folder in Finder**
5. In Finder, navigate to **Products -\> Debug-iphoneos**. The directory should contain a file that ends with `-Runner.app`.
Right click and choose the compress option to generate a `zip` file.
## Using XCode (Lower than 13.0)
1. From the XCode schemes dropdown menu, select your app scheme and **Generic iOS device** as device
2. Click **Product -\> Clean** (`Cmd+Shift+K`)
3. Click **Product -\> Build For -\> Testing** (`Cmd+Shift+U`)
4. Right click the `.app` file using the Project Navigator, and select **Show in Finder**
5. In Finder, the directory should contain a file called `-Runner.app`.
Right click and choose the compress option to generate a `zip` file.
## Using Command Line
To build the `.zip` file from the command line, please follow these steps:
1. Change to the project directory and build for testing:
xcodebuild -scheme build-for-testing
2. Go to the **DerivedData** directory. To know the location of this directory, open Xcode and go to **Preferences -\> Locations**.
3. Now you can create the zip file. In the **DerivedData** directory, go to the directory containing the name of your app and some random characters.
cd appname-random_characters
cd Build/Products
cd Debug-iphoneos
zip --symlinks -r .zip -Runner.app
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/xcuitest/change-screen-orientation
# Change Device Orientation
You can change the iOS device's screen orientation before an XCUITest session starts by passing in a `orientation` option.
## Device Orientation Example
See the example below on how to change the orientation. You can choose between `PORTRAIT` (default) and `LANDSCAPE`.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 13" \
--real-device \
--orientation LANDSCAPE
The default orientation is set to `PORTRAIT` for all XCUITests.
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/xcuitest/:id/run" \
-d '{ "options": { "orientation": "LANDSCAPE" }, "capabilities":[{"version":"15.5", "deviceName":"iPhone 13", "platformName":"iOS"}]}' \
-H "Content-Type: application/json"
The default orientation is set to `PORTRAIT` for all XCUITests.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/xcuitest/instrumentation
# XCUITest Instrumentation Logs
Instrumentation logs are logs generated by XCUITest. These logs list all the steps that were performed during the test, with any errors or meta data available from the XCUITest runner.
These logs are available in the [TestingBot dashboard](https://testingbot.com/members) as well as through the [REST API](https://testingbot.com/support/api).
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/xcuitest/simulate-network-conditions
# Simulate Network Conditions
TestingBot allows you to test your mobile apps under various network conditions, including download speed, upload speed, packet loss and latency.
## Simulate network conditions using a predefined network profile
TestingBot provides a set of predefined network profiles which you can choose from.
| Profile Name | Bandwidth down/up (kbps) | Packet Loss (%) | Latency (ms) |
| --- | --- | --- | --- |
| Edge | 250/150 | 0 | 300 |
| 3G | 400/100 | 0 | 100 |
| 4G | 18000/9000 | 0 | 100 |
| airplane | 0 | 100 | 0 |
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 13" \
--real-device \
--throttle-network "3G"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/xcuitest/:id/run" \
-d '{ "options": { "throttle_network": "3G" }, "capabilities":[{"version":"15.5", "deviceName":"iPhone 13", "platformName":"iOS"}]}' \
-H "Content-Type: application/json"
## Simulate network conditions using a custom network profile
You can specify a custom set of parameters, including:
- Download rate (kbps)
- Upload rate (kbps)
- Packet loss (%)
- Latency (ms)
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
The CLI supports predefined network profiles. For custom network profiles, use the cURL API directly.
# Predefined profiles: 4G, 3G, Edge, airplane
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 13" \
--real-device \
--throttle-network "Edge"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/xcuitest/:id/run" \
-d '{ "options": { "throttle_network": { "uploadSpeed": 10240, "downloadSpeed": 10240, "latency": 0, "loss": 0 } }, "capabilities":[{"version":"15.5", "deviceName":"iPhone 13", "platformName":"iOS"}]}' \
-H "Content-Type: application/json"
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/xcuitest/set-localization-options
# Set localization options
TestingBot provides an option to run XCUITests on a localized version of your iOS mobile application.
Configure the language, locale and timezone of the remote iOS device before running your XCUITest suite.
## Setting Language for iOS XCUI Tests
Test a localized version of your mobile app with iOS XCUITest, by changing the language of the application under test.
You will need to specify a `language` option with the ISO code of the language you want to use.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 13" \
--real-device \
--language "fr"
Use an [ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to set the language of your app.
Your app needs to be compiled with support for this language to notice a change.
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/xcuitest/:id/run" \
-d '{ "options": { "language": "fr" }, "capabilities":[{"version":"15.5", "deviceName":"iPhone 13", "platformName":"iOS"}]}' \
-H "Content-Type: application/json"
Use an [ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to set the language of your app.
Your app needs to be compiled with support for this language to notice a change.
## Set a locale for XCUI Testing
You can change the locale before running your XCUITest with the `locale` parameter.
Please use the CA format (country name abbreviation). For example: `DE` for Germany.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 13" \
--real-device \
--locale "DE"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/xcuitest/:id/run" \
-d '{ "options": { "locale": "DE" }, "capabilities":[{"version":"15.5", "deviceName":"iPhone 13", "platformName":"iOS"}]}' \
-H "Content-Type: application/json"
## Change timezone for XCUITests
If you'd like to change the timezone before starting your XCUITests, you can use the `timeZone` option.
Please use a timezone format from the [list of timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 13" \
--real-device \
--timezone "America/New_York"
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/xcuitest/:id/run" \
-d '{ "options": { "timeZone": "America/New_York" }, "capabilities":[{"version":"15.5", "deviceName":"iPhone 13", "platformName":"iOS"}]}' \
-H "Content-Type: application/json"
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/xcuitest/set-ip-geolocation
# Use IP geolocation
TestingBot's GeoIP feature allows you to test your app from various parts of the world, by using IP addresses in [over 20 different countries](https://testingbot.com/support/web-automate/selenium/test-options#geo).
When you enable GeoIP, traffic originating from the iOS device which is running your XCUITest will come from an IP address in the country you specified.
## GeoIP example
To get started with location testing, you can specify the geoLocation option when starting a XCUITest.
Please see the example below where we specify that the app should connect through Germany.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 13" \
--real-device \
--geo-location "DE"
The `DE` abbreviation is the ISO code of Germany. You can find other [GeoIP country codes](https://testingbot.com/support/web-automate/selenium/test-options#geo) to use.
curl -u api_key:api_secret \
-X POST "https://api.testingbot.com/v1/app-automate/xcuitest/:id/run" \
-d '{ "options": { "geoLocation": "DE" }, "capabilities":[{"version":"15.5", "deviceName":"iPhone 13", "platformName":"iOS"}]}' \
-H "Content-Type: application/json"
The `DE` abbreviation is the ISO code of Germany. You can find other [GeoIP country codes](https://testingbot.com/support/web-automate/selenium/test-options#geo) to use.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/app-automate/xcuitest/test-reports
# XCUITest Reports
TestingBot currently offers both test reports from inside the TestingBot member dashboard, or through REST-API where you can use a [JUnit formatted XML report](https://testingbot.com#junit).
## Dashboard Report
When you run XCUITests with TestingBot, you will find the test results, together with a video of the test and various other logs, in the [TestingBot member dashboard](https://testingbot.com/members).
## API report
You can query the TestingBot API to fetch the result of the XCUITest run(s).
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
By default, the CLI waits for test completion and displays results in real-time:
testingbot xcuitest app.ipa app-test.zip --device "iPhone 16" --real-device
The CLI will show:
- Upload progress for app and test ZIP
- Device allocation status
- Live output from XCUITests
- Final pass/fail status
Use `--async` to start tests without waiting for results:
testingbot xcuitest app.ipa app-test.zip --device "iPhone 16" --real-device --async
curl -u api_key:api_secret \
"https://api.testingbot.com/v1/app-automate/xcuitest/:id/:run_id"
Replace the `:id` and `:run_id` with the identifiers you received during the app upload call and the run call.
You will get back a response similar to this one:
{"id":216,"created_at":"2024-04-04T15:49:47.000Z","status":"DONE","capabilities":{"deviceName":"iPhone .*","platformName":"iOS"},"success":false,"test":{"sessionId":"ffe-4b453ea45667-61a0a28dbf36-171224576223-3e","environment":{"name":"XCUITest","os":"iPhone 15 - 17.0","version":"17.0"}},"version":"1.0"}
## JUnit XML report
You can query our API to fetch a JUnit XML report which you can use a post-build step in your CI/CD environment.
**Report Structure**
In the XML report, each session maps to a `testsuite` tag. Inside that tag, multiple `testcase` items are available, each mapping to a single XCUITest.
If a test failed, a `failure` or `error` tag will be available.
[CLI](https://testingbot.com#)[cURL](https://testingbot.com#)
Download the JUnit XML report after test completion:
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 16" \
--real-device \
--report junit \
--report-output-dir ./reports
You can also download an HTML report:
testingbot xcuitest app.ipa app-test.zip \
--device "iPhone 16" \
--real-device \
--report html \
--report-output-dir ./reports
curl -u api_key:api_secret \
"https://api.testingbot.com/v1/app-automate/xcuitest/:id/report"
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/web-automate
# Web Automation with TestingBot
TestingBot provides a cloud-based platform for running automated tests on web applications, using various popular testing frameworks and tools.

## [Selenium](https://testingbot.com/support/web-automate/selenium)
Selenium is a popular test-framework, designed to automate website testing.
- [Java](https://testingbot.com/support/web-automate/selenium/java)
- [C#/.NET](https://testingbot.com/support/web-automate/selenium/csharp)
- [NodeJS](https://testingbot.com/support/web-automate/selenium/nodejs)
- [Python](https://testingbot.com/support/web-automate/selenium/python)
- [PHP](https://testingbot.com/support/web-automate/selenium/php)
- [Ruby](https://testingbot.com/support/web-automate/selenium/ruby)

## [Playwright](https://testingbot.com/support/web-automate/playwright)
Playwright is a NodeJS library to automate Chromium, Firefox and WebKit.
- [Java](https://testingbot.com/support/web-automate/playwright/java)
- [C#/.NET](https://testingbot.com/support/web-automate/playwright/dotnet)
- [Python](https://testingbot.com/support/web-automate/playwright/pytest)
- [Jest](https://testingbot.com/support/web-automate/playwright/jest)

## [Puppeteer](https://testingbot.com/support/web-automate/puppeteer)
Puppeteer is a NodeJS library to automate Chrome and Firefox browsers.
- [Jest](https://testingbot.com/support/web-automate/puppeteer/jest)
- [WebdriverIO](https://testingbot.com/support/web-automate/puppeteer/webdriverio)
- [CodeceptJS](https://testingbot.com/support/web-automate/puppeteer/codeceptjs)
- [Scraping](https://testingbot.com/support/web-automate/puppeteer/scraping)

## [Cypress](https://testingbot.com/support/web-automate/cypress)
Cypress is a JavaScript-based end-to-end testing framework.
- [Tutorial](https://testingbot.com/support/web-automate/cypress/tutorial)
---
URL: https://testingbot.com/support/web-automate/headless
# Headless Testing Documentation
Headless testing **significantly minimizes the time it takes to run a test**. Because the browser runs in headless mode, it is not necessary to render the graphical user interface.
This means the browser starts faster and uses less resources, resulting in quicker test execution.
You can still take screenshots during headless mode. Video recording of the test however is not available in headless mode.
## Chrome Headless Testing
To run headless tests on Chrome, simply set `headless: true` in `tb:options`. This tells TestingBot to launch the browser in headless mode and automatically disables video recording.
We recommend using the `LINUX` platform for the fastest headless testing experience.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[WebdriverIO](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)[PHP](https://testingbot.com#)[Robot Framework](https://testingbot.com#)
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class HeadlessChromeTest {
public static void main(String[] args) throws Exception {
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setPlatformName("LINUX");
chromeOptions.setBrowserVersion("latest");
Map tbOptions = new HashMap<>();
tbOptions.put("key", "API_KEY");
tbOptions.put("secret", "API_SECRET");
tbOptions.put("name", "Chrome Headless Test");
tbOptions.put("headless", true);
chromeOptions.setCapability("tb:options", tbOptions);
WebDriver driver = new RemoteWebDriver(
new URL("https://hub.testingbot.com/wd/hub"),
chromeOptions
);
driver.get("https://www.google.com");
System.out.println("Title: " + driver.getTitle());
driver.quit();
}
}
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.set_capability("platformName", "LINUX")
chrome_options.set_capability("browserVersion", "latest")
chrome_options.set_capability("tb:options", {
"key": "API_KEY",
"secret": "API_SECRET",
"name": "Chrome Headless Test",
"headless": True
})
driver = webdriver.Remote(
command_executor="https://hub.testingbot.com/wd/hub",
options=chrome_options
)
driver.get("https://www.google.com")
print("Title:", driver.title)
driver.quit()
const { Builder } = require('selenium-webdriver');
async function runHeadlessTest() {
const driver = await new Builder()
.forBrowser('chrome')
.withCapabilities({
'browserName': 'chrome',
'browserVersion': 'latest',
'platformName': 'LINUX',
'tb:options': {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Chrome Headless Test',
'headless': true
}
})
.usingServer('https://hub.testingbot.com/wd/hub')
.build();
try {
await driver.get('https://www.google.com');
const title = await driver.getTitle();
console.log('Title:', title);
} finally {
await driver.quit();
}
}
runHeadlessTest();
// wdio.conf.js
exports.config = {
user: 'API_KEY',
key: 'API_SECRET',
capabilities: [{
browserName: 'chrome',
browserVersion: 'latest',
platformName: 'LINUX',
'tb:options': {
name: 'Chrome Headless Test',
headless: true
}
}],
services: [['testingbot']],
// Your test configuration
specs: ['./test/specs/**/*.js'],
framework: 'mocha',
reporters: ['spec']
};
// test/specs/example.js
describe('Headless Chrome Test', () => {
it('should load Google', async () => {
await browser.url('https://www.google.com');
const title = await browser.getTitle();
console.log('Title:', title);
});
});
require 'selenium-webdriver'
options = Selenium::WebDriver::Chrome::Options.new
options.platform_name = 'LINUX'
options.browser_version = 'latest'
caps = Selenium::WebDriver::Remote::Capabilities.new(
'tb:options': {
key: 'API_KEY',
secret: 'API_SECRET',
name: 'Chrome Headless Test',
headless: true
}
)
driver = Selenium::WebDriver.for(
:remote,
url: 'https://hub.testingbot.com/wd/hub',
capabilities: [options, caps]
)
driver.get('https://www.google.com')
puts "Title: #{driver.title}"
driver.quit
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
var chromeOptions = new ChromeOptions();
chromeOptions.PlatformName = "LINUX";
chromeOptions.BrowserVersion = "latest";
var tbOptions = new Dictionary
{
["key"] = "API_KEY",
["secret"] = "API_SECRET",
["name"] = "Chrome Headless Test",
["headless"] = true
};
chromeOptions.AddAdditionalOption("tb:options", tbOptions);
IWebDriver driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(),
TimeSpan.FromSeconds(600)
);
driver.Navigate().GoToUrl("https://www.google.com");
Console.WriteLine("Title: " + driver.Title);
driver.Quit();
setCapability('browserVersion', 'latest');
$capabilities->setCapability('platformName', 'LINUX');
$capabilities->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'Chrome Headless Test',
'headless' => true
]);
$driver = RemoteWebDriver::create(
'https://hub.testingbot.com/wd/hub',
$capabilities,
60000,
60000
);
$driver->get('https://www.google.com');
echo "Title: " . $driver->getTitle() . "\n";
$driver->quit();
?>
Save the example below in a `headless_chrome.robot` file:
***Settings***
Library SeleniumLibrary
Library TestingBot
Library Collections
Test Setup Open Headless Chrome Browser
Test Teardown Close Test Browser
***Variables***
${TB_KEY} API_KEY
${TB_SECRET} API_SECRET
***Test Cases***
Chrome Headless Test
Go To https://www.google.com
Title Should Be Google
***Keywords***
Open Headless Chrome Browser
${chrome_options}= Evaluate selenium.webdriver.ChromeOptions() modules=selenium.webdriver
# Set W3C capabilities
Call Method ${chrome_options} set_capability platformName LINUX
Call Method ${chrome_options} set_capability browserVersion latest
# Set TestingBot options with headless enabled
${tb_options}= Create Dictionary name=Chrome Headless Test headless=${True}
Call Method ${chrome_options} set_capability tb:options ${tb_options}
Open Browser about:blank
... remote_url=https://${TB_KEY}:${TB_SECRET}@hub.testingbot.com/wd/hub
... options=${chrome_options}
Close Test Browser
Report TestingBot Status
... ${SUITE_NAME} | ${TEST_NAME}
... ${TEST_STATUS}
... ${TB_KEY}:${TB_SECRET}
Close All Browsers
Save the code below in a `TestingBot.py` file:
import requests
from robot.libraries.BuiltIn import BuiltIn
def report_testingbot_status(name, status, credentials):
selenium = BuiltIn().get_library_instance('SeleniumLibrary')
session_id = selenium.driver.session_id
payload = {'test[name]': name, 'test[success]': int(status == 'PASS')}
key, secret = credentials.split(':')
url = 'https://api.testingbot.com/v1/tests/{0}'.format(session_id)
response = requests.put(url, data=payload, auth=(key, secret))
assert response.status_code == 200, response.text
To run the test:
pip install robotframework-seleniumlibrary requests
PYTHONPATH=$PYTHONPATH:. robot headless_chrome.robot
## Firefox Headless Testing
To run headless tests on Firefox, simply set `headless: true` in `tb:options`. This tells TestingBot to launch the browser in headless mode and automatically disables video recording.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[WebdriverIO](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)[PHP](https://testingbot.com#)[Robot Framework](https://testingbot.com#)
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class HeadlessFirefoxTest {
public static void main(String[] args) throws Exception {
FirefoxOptions firefoxOptions = new FirefoxOptions();
firefoxOptions.setPlatformName("LINUX");
firefoxOptions.setBrowserVersion("latest");
Map tbOptions = new HashMap<>();
tbOptions.put("key", "API_KEY");
tbOptions.put("secret", "API_SECRET");
tbOptions.put("name", "Firefox Headless Test");
tbOptions.put("headless", true);
firefoxOptions.setCapability("tb:options", tbOptions);
WebDriver driver = new RemoteWebDriver(
new URL("https://hub.testingbot.com/wd/hub"),
firefoxOptions
);
driver.get("https://www.google.com");
System.out.println("Title: " + driver.getTitle());
driver.quit();
}
}
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
firefox_options = Options()
firefox_options.set_capability("platformName", "LINUX")
firefox_options.set_capability("browserVersion", "latest")
firefox_options.set_capability("tb:options", {
"key": "API_KEY",
"secret": "API_SECRET",
"name": "Firefox Headless Test",
"headless": True
})
driver = webdriver.Remote(
command_executor="https://hub.testingbot.com/wd/hub",
options=firefox_options
)
driver.get("https://www.google.com")
print("Title:", driver.title)
driver.quit()
const { Builder } = require('selenium-webdriver');
async function runHeadlessTest() {
const driver = await new Builder()
.forBrowser('firefox')
.withCapabilities({
'browserName': 'firefox',
'browserVersion': 'latest',
'platformName': 'LINUX',
'tb:options': {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Firefox Headless Test',
'headless': true
}
})
.usingServer('https://hub.testingbot.com/wd/hub')
.build();
try {
await driver.get('https://www.google.com');
const title = await driver.getTitle();
console.log('Title:', title);
} finally {
await driver.quit();
}
}
runHeadlessTest();
// wdio.conf.js
exports.config = {
user: 'API_KEY',
key: 'API_SECRET',
capabilities: [{
browserName: 'firefox',
browserVersion: 'latest',
platformName: 'LINUX',
'tb:options': {
name: 'Firefox Headless Test',
headless: true
}
}],
services: [['testingbot']],
// Your test configuration
specs: ['./test/specs/**/*.js'],
framework: 'mocha',
reporters: ['spec']
};
// test/specs/example.js
describe('Headless Firefox Test', () => {
it('should load Google', async () => {
await browser.url('https://www.google.com');
const title = await browser.getTitle();
console.log('Title:', title);
});
});
require 'selenium-webdriver'
options = Selenium::WebDriver::Firefox::Options.new
options.platform_name = 'LINUX'
options.browser_version = 'latest'
caps = Selenium::WebDriver::Remote::Capabilities.new(
'tb:options': {
key: 'API_KEY',
secret: 'API_SECRET',
name: 'Firefox Headless Test',
headless: true
}
)
driver = Selenium::WebDriver.for(
:remote,
url: 'https://hub.testingbot.com/wd/hub',
capabilities: [options, caps]
)
driver.get('https://www.google.com')
puts "Title: #{driver.title}"
driver.quit
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Remote;
var firefoxOptions = new FirefoxOptions();
firefoxOptions.PlatformName = "LINUX";
firefoxOptions.BrowserVersion = "latest";
var tbOptions = new Dictionary
{
["key"] = "API_KEY",
["secret"] = "API_SECRET",
["name"] = "Firefox Headless Test",
["headless"] = true
};
firefoxOptions.AddAdditionalOption("tb:options", tbOptions);
IWebDriver driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
firefoxOptions.ToCapabilities(),
TimeSpan.FromSeconds(600)
);
driver.Navigate().GoToUrl("https://www.google.com");
Console.WriteLine("Title: " + driver.Title);
driver.Quit();
setCapability('browserVersion', 'latest');
$capabilities->setCapability('platformName', 'LINUX');
$capabilities->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'Firefox Headless Test',
'headless' => true
]);
$driver = RemoteWebDriver::create(
'https://hub.testingbot.com/wd/hub',
$capabilities,
60000,
60000
);
$driver->get('https://www.google.com');
echo "Title: " . $driver->getTitle() . "\n";
$driver->quit();
?>
Save the example below in a `headless_firefox.robot` file:
***Settings***
Library SeleniumLibrary
Library TestingBot
Library Collections
Test Setup Open Headless Firefox Browser
Test Teardown Close Test Browser
***Variables***
${TB_KEY} API_KEY
${TB_SECRET} API_SECRET
***Test Cases***
Firefox Headless Test
Go To https://www.google.com
Title Should Be Google
***Keywords***
Open Headless Firefox Browser
${firefox_options}= Evaluate selenium.webdriver.FirefoxOptions() modules=selenium.webdriver
# Set W3C capabilities
Call Method ${firefox_options} set_capability platformName LINUX
Call Method ${firefox_options} set_capability browserVersion latest
# Set TestingBot options with headless enabled
${tb_options}= Create Dictionary name=Firefox Headless Test headless=${True}
Call Method ${firefox_options} set_capability tb:options ${tb_options}
Open Browser about:blank
... remote_url=https://${TB_KEY}:${TB_SECRET}@hub.testingbot.com/wd/hub
... options=${firefox_options}
Close Test Browser
Report TestingBot Status
... ${SUITE_NAME} | ${TEST_NAME}
... ${TEST_STATUS}
... ${TB_KEY}:${TB_SECRET}
Close All Browsers
Save the code below in a `TestingBot.py` file:
import requests
from robot.libraries.BuiltIn import BuiltIn
def report_testingbot_status(name, status, credentials):
selenium = BuiltIn().get_library_instance('SeleniumLibrary')
session_id = selenium.driver.session_id
payload = {'test[name]': name, 'test[success]': int(status == 'PASS')}
key, secret = credentials.split(':')
url = 'https://api.testingbot.com/v1/tests/{0}'.format(session_id)
response = requests.put(url, data=payload, auth=(key, secret))
assert response.status_code == 200, response.text
To run the test:
pip install robotframework-seleniumlibrary requests
PYTHONPATH=$PYTHONPATH:. robot headless_firefox.robot
## Microsoft Edge Headless Testing
To run headless tests on Microsoft Edge, simply set `headless: true` in `tb:options`. This tells TestingBot to launch the browser in headless mode and automatically disables video recording.
We recommend using the `WIN11` platform for Edge headless testing.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[WebdriverIO](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)[PHP](https://testingbot.com#)[Robot Framework](https://testingbot.com#)
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class HeadlessEdgeTest {
public static void main(String[] args) throws Exception {
EdgeOptions edgeOptions = new EdgeOptions();
edgeOptions.setPlatformName("WIN11");
edgeOptions.setBrowserVersion("latest");
Map tbOptions = new HashMap<>();
tbOptions.put("key", "API_KEY");
tbOptions.put("secret", "API_SECRET");
tbOptions.put("name", "Edge Headless Test");
tbOptions.put("headless", true);
edgeOptions.setCapability("tb:options", tbOptions);
WebDriver driver = new RemoteWebDriver(
new URL("https://hub.testingbot.com/wd/hub"),
edgeOptions
);
driver.get("https://www.google.com");
System.out.println("Title: " + driver.getTitle());
driver.quit();
}
}
from selenium import webdriver
from selenium.webdriver.edge.options import Options
edge_options = Options()
edge_options.set_capability("platformName", "WIN11")
edge_options.set_capability("browserVersion", "latest")
edge_options.set_capability("tb:options", {
"key": "API_KEY",
"secret": "API_SECRET",
"name": "Edge Headless Test",
"headless": True
})
driver = webdriver.Remote(
command_executor="https://hub.testingbot.com/wd/hub",
options=edge_options
)
driver.get("https://www.google.com")
print("Title:", driver.title)
driver.quit()
const { Builder } = require('selenium-webdriver');
async function runHeadlessTest() {
const driver = await new Builder()
.forBrowser('MicrosoftEdge')
.withCapabilities({
'browserName': 'MicrosoftEdge',
'browserVersion': 'latest',
'platformName': 'WIN11',
'tb:options': {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Edge Headless Test',
'headless': true
}
})
.usingServer('https://hub.testingbot.com/wd/hub')
.build();
try {
await driver.get('https://www.google.com');
const title = await driver.getTitle();
console.log('Title:', title);
} finally {
await driver.quit();
}
}
runHeadlessTest();
// wdio.conf.js
exports.config = {
user: 'API_KEY',
key: 'API_SECRET',
capabilities: [{
browserName: 'MicrosoftEdge',
browserVersion: 'latest',
platformName: 'WIN11',
'tb:options': {
name: 'Edge Headless Test',
headless: true
}
}],
services: [['testingbot']],
// Your test configuration
specs: ['./test/specs/**/*.js'],
framework: 'mocha',
reporters: ['spec']
};
// test/specs/example.js
describe('Headless Edge Test', () => {
it('should load Google', async () => {
await browser.url('https://www.google.com');
const title = await browser.getTitle();
console.log('Title:', title);
});
});
require 'selenium-webdriver'
options = Selenium::WebDriver::Edge::Options.new
options.platform_name = 'WIN11'
options.browser_version = 'latest'
caps = Selenium::WebDriver::Remote::Capabilities.new(
'tb:options': {
key: 'API_KEY',
secret: 'API_SECRET',
name: 'Edge Headless Test',
headless: true
}
)
driver = Selenium::WebDriver.for(
:remote,
url: 'https://hub.testingbot.com/wd/hub',
capabilities: [options, caps]
)
driver.get('https://www.google.com')
puts "Title: #{driver.title}"
driver.quit
using OpenQA.Selenium;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Remote;
var edgeOptions = new EdgeOptions();
edgeOptions.PlatformName = "WIN11";
edgeOptions.BrowserVersion = "latest";
var tbOptions = new Dictionary
{
["key"] = "API_KEY",
["secret"] = "API_SECRET",
["name"] = "Edge Headless Test",
["headless"] = true
};
edgeOptions.AddAdditionalOption("tb:options", tbOptions);
IWebDriver driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
edgeOptions.ToCapabilities(),
TimeSpan.FromSeconds(600)
);
driver.Navigate().GoToUrl("https://www.google.com");
Console.WriteLine("Title: " + driver.Title);
driver.Quit();
setCapability('browserVersion', 'latest');
$capabilities->setCapability('platformName', 'WIN11');
$capabilities->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'Edge Headless Test',
'headless' => true
]);
$driver = RemoteWebDriver::create(
'https://hub.testingbot.com/wd/hub',
$capabilities,
60000,
60000
);
$driver->get('https://www.google.com');
echo "Title: " . $driver->getTitle() . "\n";
$driver->quit();
?>
Save the example below in a `headless_edge.robot` file:
***Settings***
Library SeleniumLibrary
Library TestingBot
Library Collections
Test Setup Open Headless Edge Browser
Test Teardown Close Test Browser
***Variables***
${TB_KEY} API_KEY
${TB_SECRET} API_SECRET
***Test Cases***
Edge Headless Test
Go To https://www.google.com
Title Should Be Google
***Keywords***
Open Headless Edge Browser
${edge_options}= Evaluate selenium.webdriver.EdgeOptions() modules=selenium.webdriver
# Set W3C capabilities
Call Method ${edge_options} set_capability platformName WIN11
Call Method ${edge_options} set_capability browserVersion latest
# Set TestingBot options with headless enabled
${tb_options}= Create Dictionary name=Edge Headless Test headless=${True}
Call Method ${edge_options} set_capability tb:options ${tb_options}
Open Browser about:blank
... remote_url=https://${TB_KEY}:${TB_SECRET}@hub.testingbot.com/wd/hub
... options=${edge_options}
Close Test Browser
Report TestingBot Status
... ${SUITE_NAME} | ${TEST_NAME}
... ${TEST_STATUS}
... ${TB_KEY}:${TB_SECRET}
Close All Browsers
Save the code below in a `TestingBot.py` file:
import requests
from robot.libraries.BuiltIn import BuiltIn
def report_testingbot_status(name, status, credentials):
selenium = BuiltIn().get_library_instance('SeleniumLibrary')
session_id = selenium.driver.session_id
payload = {'test[name]': name, 'test[success]': int(status == 'PASS')}
key, secret = credentials.split(':')
url = 'https://api.testingbot.com/v1/tests/{0}'.format(session_id)
response = requests.put(url, data=payload, auth=(key, secret))
assert response.status_code == 200, response.text
To run the test:
pip install robotframework-seleniumlibrary requests
PYTHONPATH=$PYTHONPATH:. robot headless_edge.robot
## Safari Headless Testing
**Safari does not support headless mode.** Apple has not implemented headless functionality in Safari or the SafariDriver.
If you need to run Safari tests, they will always run in headed (visible) mode. For headless testing requirements, consider using Chrome, Firefox or Edge as alternatives.
At TestingBot we run all Safari 17 and higher versions on Apple Silicon for maximum performance.
## Mobile Headless Testing
TestingBot uses Appium to run mobile tests. Appium supports running your iOS and Android tests in headless mode (no screen rendering).
To enable headless mode for mobile testing, specify the `isHeadless` capability in your test configuration.
### iOS Headless Testing
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class HeadlessIOSTest {
public static void main(String[] args) throws Exception {
XCUITestOptions options = new XCUITestOptions();
options.setPlatformName("iOS");
options.setPlatformVersion("18.0");
options.setDeviceName("iPhone 16");
options.setBrowserName("safari");
options.setCapability("appium:isHeadless", true);
Map tbOptions = new HashMap<>();
tbOptions.put("key", "API_KEY");
tbOptions.put("secret", "API_SECRET");
tbOptions.put("name", "iOS Headless Test");
options.setCapability("tb:options", tbOptions);
IOSDriver driver = new IOSDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
driver.get("https://www.google.com");
System.out.println("Title: " + driver.getTitle());
driver.quit();
}
}
from appium import webdriver
from appium.options.ios import XCUITestOptions
options = XCUITestOptions()
options.platform_name = "iOS"
options.platform_version = "18.0"
options.device_name = "iPhone 16"
options.browser_name = "safari"
options.set_capability("appium:isHeadless", True)
options.set_capability("tb:options", {
"key": "API_KEY",
"secret": "API_SECRET",
"name": "iOS Headless Test"
})
driver = webdriver.Remote(
command_executor="https://hub.testingbot.com/wd/hub",
options=options
)
driver.get("https://www.google.com")
print("Title:", driver.title)
driver.quit()
const { remote } = require('webdriverio');
async function runHeadlessIOSTest() {
const driver = await remote({
hostname: 'hub.testingbot.com',
port: 443,
path: '/wd/hub',
protocol: 'https',
capabilities: {
'platformName': 'iOS',
'appium:platformVersion': '18.0',
'appium:deviceName': 'iPhone 16',
'browserName': 'safari',
'appium:isHeadless': true,
'tb:options': {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'iOS Headless Test'
}
}
});
await driver.url('https://www.google.com');
const title = await driver.getTitle();
console.log('Title:', title);
await driver.deleteSession();
}
runHeadlessIOSTest();
require 'appium_lib'
caps = {
platformName: 'iOS',
'appium:platformVersion': '18.0',
'appium:deviceName': 'iPhone 16',
browserName: 'safari',
'appium:isHeadless': true,
'tb:options': {
key: 'API_KEY',
secret: 'API_SECRET',
name: 'iOS Headless Test'
}
}
opts = {
caps: caps,
appium_lib: {
server_url: 'https://hub.testingbot.com/wd/hub'
}
}
driver = Appium::Driver.new(opts, true)
driver.start_driver
driver.get('https://www.google.com')
puts "Title: #{driver.title}"
driver.quit
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.iOS;
var options = new AppiumOptions();
options.PlatformName = "iOS";
options.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 16");
options.AddAdditionalAppiumOption("browserName", "safari");
options.AddAdditionalAppiumOption("appium:isHeadless", true);
var tbOptions = new Dictionary
{
["key"] = "API_KEY",
["secret"] = "API_SECRET",
["name"] = "iOS Headless Test"
};
options.AddAdditionalAppiumOption("tb:options", tbOptions);
var driver = new IOSDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options,
TimeSpan.FromSeconds(600)
);
driver.Navigate().GoToUrl("https://www.google.com");
Console.WriteLine("Title: " + driver.Title);
driver.Quit();
### Android Headless Testing
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[C#](https://testingbot.com#)
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class HeadlessAndroidTest {
public static void main(String[] args) throws Exception {
UiAutomator2Options options = new UiAutomator2Options();
options.setPlatformName("Android");
options.setPlatformVersion("14.0");
options.setDeviceName("Pixel 8");
options.setBrowserName("chrome");
options.setCapability("appium:isHeadless", true);
Map tbOptions = new HashMap<>();
tbOptions.put("key", "API_KEY");
tbOptions.put("secret", "API_SECRET");
tbOptions.put("name", "Android Headless Test");
options.setCapability("tb:options", tbOptions);
AndroidDriver driver = new AndroidDriver(
new URL("https://hub.testingbot.com/wd/hub"),
options
);
driver.get("https://www.google.com");
System.out.println("Title: " + driver.getTitle());
driver.quit();
}
}
from appium import webdriver
from appium.options.android import UiAutomator2Options
options = UiAutomator2Options()
options.platform_name = "Android"
options.platform_version = "14.0"
options.device_name = "Pixel 8"
options.browser_name = "chrome"
options.set_capability("appium:isHeadless", True)
options.set_capability("tb:options", {
"key": "API_KEY",
"secret": "API_SECRET",
"name": "Android Headless Test"
})
driver = webdriver.Remote(
command_executor="https://hub.testingbot.com/wd/hub",
options=options
)
driver.get("https://www.google.com")
print("Title:", driver.title)
driver.quit()
const { remote } = require('webdriverio');
async function runHeadlessAndroidTest() {
const driver = await remote({
hostname: 'hub.testingbot.com',
port: 443,
path: '/wd/hub',
protocol: 'https',
capabilities: {
'platformName': 'Android',
'appium:platformVersion': '14.0',
'appium:deviceName': 'Pixel 8',
'browserName': 'chrome',
'appium:isHeadless': true,
'tb:options': {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Android Headless Test'
}
}
});
await driver.url('https://www.google.com');
const title = await driver.getTitle();
console.log('Title:', title);
await driver.deleteSession();
}
runHeadlessAndroidTest();
require 'appium_lib'
caps = {
platformName: 'Android',
'appium:platformVersion': '14.0',
'appium:deviceName': 'Pixel 8',
browserName: 'chrome',
'appium:isHeadless': true,
'tb:options': {
key: 'API_KEY',
secret: 'API_SECRET',
name: 'Android Headless Test'
}
}
opts = {
caps: caps,
appium_lib: {
server_url: 'https://hub.testingbot.com/wd/hub'
}
}
driver = Appium::Driver.new(opts, true)
driver.start_driver
driver.get('https://www.google.com')
puts "Title: #{driver.title}"
driver.quit
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
var options = new AppiumOptions();
options.PlatformName = "Android";
options.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 8");
options.AddAdditionalAppiumOption("browserName", "chrome");
options.AddAdditionalAppiumOption("appium:isHeadless", true);
var tbOptions = new Dictionary
{
["key"] = "API_KEY",
["secret"] = "API_SECRET",
["name"] = "Android Headless Test"
};
options.AddAdditionalAppiumOption("tb:options", tbOptions);
var driver = new AndroidDriver(
new Uri("https://hub.testingbot.com/wd/hub"),
options,
TimeSpan.FromSeconds(600)
);
driver.Navigate().GoToUrl("https://www.google.com");
Console.WriteLine("Title: " + driver.Title);
driver.Quit();
## Benefits of Headless Testing
- **Faster Test Execution:** Without rendering the UI, tests start and complete more quickly.
- **Lower Resource Usage:** Headless browsers consume less memory and CPU.
- **Ideal for CI/CD:** Perfect for automated pipelines where visual output is not needed.
- **Screenshots Supported:** You can still capture screenshots during headless tests.
## Limitations
- **No Video Recording:** Video recording is not available during headless tests.
- **Debugging:** Debugging can be more challenging without seeing the browser UI.
For debugging purposes, we recommend switching to headed mode temporarily to visually inspect the browser during test execution.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/web-automate/browsers
- [Desktop Browsers & Emulators](https://testingbot.com#)
- [Physical Mobile Devices](https://testingbot.com/support/app-automate/devices)
# List of available browsers
Below is a list of **6187 combinations** available for testing. Click a combination to see how to run an automated or manual test.
## Test Configuration
[Run Manual Test](https://testingbot.com/members/manual/new)
JavaPythonRubyNodeJSC#
Jump to:|[Win 11](https://testingbot.com#os-win11)[Win 10](https://testingbot.com#os-win10)[Win 8.1](https://testingbot.com#os-win81)[Win 8](https://testingbot.com#os-win8)[Win 7](https://testingbot.com#os-win7)|[Tahoe](https://testingbot.com#os-tahoe)[Sequoia](https://testingbot.com#os-sequoia)[Sonoma](https://testingbot.com#os-sonoma)[Ventura](https://testingbot.com#os-ventura)[Monterey](https://testingbot.com#os-monterey)[Big Sur](https://testingbot.com#os-bigsur)[Catalina](https://testingbot.com#os-catalina)[Mojave](https://testingbot.com#os-mojave)[High Sierra](https://testingbot.com#os-highsierra)|[Linux](https://testingbot.com#os-linux)|[iOS](https://testingbot.com#os-ios)[Android](https://testingbot.com#os-android)

## Windows 11
(64-bit)

### Chrome
[beta](https://testingbot.com# "Chrome")[dev](https://testingbot.com# "Chrome")[147](https://testingbot.com# "Chrome 147")[146](https://testingbot.com# "Chrome 146")[145](https://testingbot.com# "Chrome 145")[144](https://testingbot.com# "Chrome 144")[143](https://testingbot.com# "Chrome 143")[142](https://testingbot.com# "Chrome 142")[141](https://testingbot.com# "Chrome 141")[140](https://testingbot.com# "Chrome 140")[139](https://testingbot.com# "Chrome 139")[138](https://testingbot.com# "Chrome 138")[137](https://testingbot.com# "Chrome 137")[136](https://testingbot.com# "Chrome 136")[135](https://testingbot.com# "Chrome 135")[134](https://testingbot.com# "Chrome 134")[133](https://testingbot.com# "Chrome 133")[132](https://testingbot.com# "Chrome 132")[131](https://testingbot.com# "Chrome 131")[130](https://testingbot.com# "Chrome 130")[129](https://testingbot.com# "Chrome 129")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[37](https://testingbot.com# "Chrome 37")[36](https://testingbot.com# "Chrome 36")

### Firefox
[dev](https://testingbot.com# "FF dev")[beta](https://testingbot.com# "FF beta")[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### Edge
[dev](https://testingbot.com# "Edge")[beta](https://testingbot.com# "Edge")[147](https://testingbot.com# "Edge 147")[146](https://testingbot.com# "Edge 146")[145](https://testingbot.com# "Edge 145")[144](https://testingbot.com# "Edge 144")[143](https://testingbot.com# "Edge 143")[142](https://testingbot.com# "Edge 142")[141](https://testingbot.com# "Edge 141")[140](https://testingbot.com# "Edge 140")[139](https://testingbot.com# "Edge 139")[138](https://testingbot.com# "Edge 138")[137](https://testingbot.com# "Edge 137")[136](https://testingbot.com# "Edge 136")[135](https://testingbot.com# "Edge 135")[134](https://testingbot.com# "Edge 134")[133](https://testingbot.com# "Edge 133")[132](https://testingbot.com# "Edge 132")[131](https://testingbot.com# "Edge 131")[130](https://testingbot.com# "Edge 130")[129](https://testingbot.com# "Edge 129")[128](https://testingbot.com# "Edge 128")[127](https://testingbot.com# "Edge 127")[126](https://testingbot.com# "Edge 126")[125](https://testingbot.com# "Edge 125")[124](https://testingbot.com# "Edge 124")[123](https://testingbot.com# "Edge 123")[122](https://testingbot.com# "Edge 122")[121](https://testingbot.com# "Edge 121")[120](https://testingbot.com# "Edge 120")[119](https://testingbot.com# "Edge 119")[118](https://testingbot.com# "Edge 118")[117](https://testingbot.com# "Edge 117")[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")[79](https://testingbot.com# "Edge 79")

### Opera
[130](https://testingbot.com# "Opera 130")[129](https://testingbot.com# "Opera 129")[128](https://testingbot.com# "Opera 128")[127](https://testingbot.com# "Opera 127")[126](https://testingbot.com# "Opera 126")[125](https://testingbot.com# "Opera 125")[124](https://testingbot.com# "Opera 124")[123](https://testingbot.com# "Opera 123")[122](https://testingbot.com# "Opera 122")[121](https://testingbot.com# "Opera 121")[120](https://testingbot.com# "Opera 120")[119](https://testingbot.com# "Opera 119")[118](https://testingbot.com# "Opera 118")[117](https://testingbot.com# "Opera 117")[116](https://testingbot.com# "Opera 116")[115](https://testingbot.com# "Opera 115")[114](https://testingbot.com# "Opera 114")[113](https://testingbot.com# "Opera 113")[112](https://testingbot.com# "Opera 112")[111](https://testingbot.com# "Opera 111")[110](https://testingbot.com# "Opera 110")[109](https://testingbot.com# "Opera 109")[108](https://testingbot.com# "Opera 108")[107](https://testingbot.com# "Opera 107")[106](https://testingbot.com# "Opera 106")[105](https://testingbot.com# "Opera 105")[104](https://testingbot.com# "Opera 104")[103](https://testingbot.com# "Opera 103")[102](https://testingbot.com# "Opera 102")[101](https://testingbot.com# "Opera 101")[100](https://testingbot.com# "Opera 100")[99](https://testingbot.com# "Opera 99")[98](https://testingbot.com# "Opera 98")[97](https://testingbot.com# "Opera 97")[96](https://testingbot.com# "Opera 96")[95](https://testingbot.com# "Opera 95")[94](https://testingbot.com# "Opera 94")[93](https://testingbot.com# "Opera 93")[92](https://testingbot.com# "Opera 92")[91](https://testingbot.com# "Opera 91")[90](https://testingbot.com# "Opera 90")[89](https://testingbot.com# "Opera 89")[88](https://testingbot.com# "Opera 88")[87](https://testingbot.com# "Opera 87")[86](https://testingbot.com# "Opera 86")[85](https://testingbot.com# "Opera 85")[84](https://testingbot.com# "Opera 84")[83](https://testingbot.com# "Opera 83")[82](https://testingbot.com# "Opera 82")[81](https://testingbot.com# "Opera 81")[80](https://testingbot.com# "Opera 80")[79](https://testingbot.com# "Opera 79")[78](https://testingbot.com# "Opera 78")[77](https://testingbot.com# "Opera 77")[76](https://testingbot.com# "Opera 76")[75](https://testingbot.com# "Opera 75")[74](https://testingbot.com# "Opera 74")[73](https://testingbot.com# "Opera 73")[72](https://testingbot.com# "Opera 72")[71](https://testingbot.com# "Opera 71")[70](https://testingbot.com# "Opera 70")[69](https://testingbot.com# "Opera 69")[68](https://testingbot.com# "Opera 68")[67](https://testingbot.com# "Opera 67")[66](https://testingbot.com# "Opera 66")[65](https://testingbot.com# "Opera 65")[64](https://testingbot.com# "Opera 64")[63](https://testingbot.com# "Opera 63")[62](https://testingbot.com# "Opera 62")[60](https://testingbot.com# "Opera 60")[59](https://testingbot.com# "Opera 59")[58](https://testingbot.com# "Opera 58")[57](https://testingbot.com# "Opera 57")[56](https://testingbot.com# "Opera 56")[55](https://testingbot.com# "Opera 55")[54](https://testingbot.com# "Opera 54")[53](https://testingbot.com# "Opera 53")[52](https://testingbot.com# "Opera 52")[51](https://testingbot.com# "Opera 51")[50](https://testingbot.com# "Opera 50")

## Windows 10
(64-bit)

### Chrome
[beta](https://testingbot.com# "Chrome")[dev](https://testingbot.com# "Chrome")[147](https://testingbot.com# "Chrome 147")[146](https://testingbot.com# "Chrome 146")[145](https://testingbot.com# "Chrome 145")[144](https://testingbot.com# "Chrome 144")[143](https://testingbot.com# "Chrome 143")[142](https://testingbot.com# "Chrome 142")[141](https://testingbot.com# "Chrome 141")[140](https://testingbot.com# "Chrome 140")[139](https://testingbot.com# "Chrome 139")[138](https://testingbot.com# "Chrome 138")[137](https://testingbot.com# "Chrome 137")[136](https://testingbot.com# "Chrome 136")[135](https://testingbot.com# "Chrome 135")[134](https://testingbot.com# "Chrome 134")[133](https://testingbot.com# "Chrome 133")[132](https://testingbot.com# "Chrome 132")[131](https://testingbot.com# "Chrome 131")[130](https://testingbot.com# "Chrome 130")[129](https://testingbot.com# "Chrome 129")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[37](https://testingbot.com# "Chrome 37")[36](https://testingbot.com# "Chrome 36")

### Firefox
[dev](https://testingbot.com# "FF dev")[beta](https://testingbot.com# "FF beta")[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### IE
[11](https://testingbot.com# "IE11")

### Edge
[dev](https://testingbot.com# "Edge")[beta](https://testingbot.com# "Edge")[147](https://testingbot.com# "Edge 147")[146](https://testingbot.com# "Edge 146")[145](https://testingbot.com# "Edge 145")[144](https://testingbot.com# "Edge 144")[143](https://testingbot.com# "Edge 143")[142](https://testingbot.com# "Edge 142")[141](https://testingbot.com# "Edge 141")[140](https://testingbot.com# "Edge 140")[139](https://testingbot.com# "Edge 139")[138](https://testingbot.com# "Edge 138")[137](https://testingbot.com# "Edge 137")[136](https://testingbot.com# "Edge 136")[135](https://testingbot.com# "Edge 135")[134](https://testingbot.com# "Edge 134")[133](https://testingbot.com# "Edge 133")[132](https://testingbot.com# "Edge 132")[131](https://testingbot.com# "Edge 131")[130](https://testingbot.com# "Edge 130")[129](https://testingbot.com# "Edge 129")[128](https://testingbot.com# "Edge 128")[127](https://testingbot.com# "Edge 127")[126](https://testingbot.com# "Edge 126")[125](https://testingbot.com# "Edge 125")[124](https://testingbot.com# "Edge 124")[123](https://testingbot.com# "Edge 123")[122](https://testingbot.com# "Edge 122")[121](https://testingbot.com# "Edge 121")[120](https://testingbot.com# "Edge 120")[119](https://testingbot.com# "Edge 119")[118](https://testingbot.com# "Edge 118")[117](https://testingbot.com# "Edge 117")[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")[79](https://testingbot.com# "Edge 79")

### Opera
[130](https://testingbot.com# "Opera 130")[129](https://testingbot.com# "Opera 129")[128](https://testingbot.com# "Opera 128")[127](https://testingbot.com# "Opera 127")[126](https://testingbot.com# "Opera 126")[125](https://testingbot.com# "Opera 125")[124](https://testingbot.com# "Opera 124")[123](https://testingbot.com# "Opera 123")[122](https://testingbot.com# "Opera 122")[121](https://testingbot.com# "Opera 121")[120](https://testingbot.com# "Opera 120")[119](https://testingbot.com# "Opera 119")[118](https://testingbot.com# "Opera 118")[117](https://testingbot.com# "Opera 117")[116](https://testingbot.com# "Opera 116")[115](https://testingbot.com# "Opera 115")[114](https://testingbot.com# "Opera 114")[113](https://testingbot.com# "Opera 113")[112](https://testingbot.com# "Opera 112")[111](https://testingbot.com# "Opera 111")[110](https://testingbot.com# "Opera 110")[109](https://testingbot.com# "Opera 109")[108](https://testingbot.com# "Opera 108")[107](https://testingbot.com# "Opera 107")[106](https://testingbot.com# "Opera 106")[105](https://testingbot.com# "Opera 105")[104](https://testingbot.com# "Opera 104")[103](https://testingbot.com# "Opera 103")[102](https://testingbot.com# "Opera 102")[101](https://testingbot.com# "Opera 101")[100](https://testingbot.com# "Opera 100")[99](https://testingbot.com# "Opera 99")[98](https://testingbot.com# "Opera 98")[97](https://testingbot.com# "Opera 97")[96](https://testingbot.com# "Opera 96")[95](https://testingbot.com# "Opera 95")[94](https://testingbot.com# "Opera 94")[93](https://testingbot.com# "Opera 93")[92](https://testingbot.com# "Opera 92")[91](https://testingbot.com# "Opera 91")[90](https://testingbot.com# "Opera 90")[89](https://testingbot.com# "Opera 89")[88](https://testingbot.com# "Opera 88")[87](https://testingbot.com# "Opera 87")[86](https://testingbot.com# "Opera 86")[85](https://testingbot.com# "Opera 85")[84](https://testingbot.com# "Opera 84")[83](https://testingbot.com# "Opera 83")[82](https://testingbot.com# "Opera 82")[81](https://testingbot.com# "Opera 81")[80](https://testingbot.com# "Opera 80")[79](https://testingbot.com# "Opera 79")[78](https://testingbot.com# "Opera 78")[77](https://testingbot.com# "Opera 77")[76](https://testingbot.com# "Opera 76")[75](https://testingbot.com# "Opera 75")[74](https://testingbot.com# "Opera 74")[73](https://testingbot.com# "Opera 73")[72](https://testingbot.com# "Opera 72")[71](https://testingbot.com# "Opera 71")[70](https://testingbot.com# "Opera 70")[69](https://testingbot.com# "Opera 69")[68](https://testingbot.com# "Opera 68")[67](https://testingbot.com# "Opera 67")[66](https://testingbot.com# "Opera 66")[65](https://testingbot.com# "Opera 65")[64](https://testingbot.com# "Opera 64")[63](https://testingbot.com# "Opera 63")[62](https://testingbot.com# "Opera 62")[60](https://testingbot.com# "Opera 60")[59](https://testingbot.com# "Opera 59")[58](https://testingbot.com# "Opera 58")[57](https://testingbot.com# "Opera 57")[56](https://testingbot.com# "Opera 56")[55](https://testingbot.com# "Opera 55")[54](https://testingbot.com# "Opera 54")[53](https://testingbot.com# "Opera 53")[52](https://testingbot.com# "Opera 52")[51](https://testingbot.com# "Opera 51")[50](https://testingbot.com# "Opera 50")

## Windows 8.1
(64-bit)

### Chrome
[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[37](https://testingbot.com# "Chrome 37")[36](https://testingbot.com# "Chrome 36")

### Firefox
[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### IE
[11](https://testingbot.com# "IE11")

## Windows 8
(64-bit)

### Chrome
[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[37](https://testingbot.com# "Chrome 37")[36](https://testingbot.com# "Chrome 36")

### Firefox
[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### IE
[10](https://testingbot.com# "IE10")

## Windows 7
(64-bit)

### Chrome
[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[37](https://testingbot.com# "Chrome 37")[36](https://testingbot.com# "Chrome 36")

### Firefox
[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### IE
[11](https://testingbot.com# "IE11")[10](https://testingbot.com# "IE10")[9](https://testingbot.com# "IE9")[8](https://testingbot.com# "IE8")

### Opera
[95](https://testingbot.com# "Opera 95")[94](https://testingbot.com# "Opera 94")[93](https://testingbot.com# "Opera 93")[92](https://testingbot.com# "Opera 92")[91](https://testingbot.com# "Opera 91")[90](https://testingbot.com# "Opera 90")[89](https://testingbot.com# "Opera 89")[88](https://testingbot.com# "Opera 88")[87](https://testingbot.com# "Opera 87")[86](https://testingbot.com# "Opera 86")[85](https://testingbot.com# "Opera 85")[84](https://testingbot.com# "Opera 84")[83](https://testingbot.com# "Opera 83")[82](https://testingbot.com# "Opera 82")[81](https://testingbot.com# "Opera 81")[80](https://testingbot.com# "Opera 80")[79](https://testingbot.com# "Opera 79")[78](https://testingbot.com# "Opera 78")[77](https://testingbot.com# "Opera 77")[76](https://testingbot.com# "Opera 76")[75](https://testingbot.com# "Opera 75")[74](https://testingbot.com# "Opera 74")[73](https://testingbot.com# "Opera 73")[72](https://testingbot.com# "Opera 72")[71](https://testingbot.com# "Opera 71")[70](https://testingbot.com# "Opera 70")[69](https://testingbot.com# "Opera 69")[68](https://testingbot.com# "Opera 68")[67](https://testingbot.com# "Opera 67")[66](https://testingbot.com# "Opera 66")[65](https://testingbot.com# "Opera 65")[64](https://testingbot.com# "Opera 64")[63](https://testingbot.com# "Opera 63")[62](https://testingbot.com# "Opera 62")[60](https://testingbot.com# "Opera 60")[59](https://testingbot.com# "Opera 59")[58](https://testingbot.com# "Opera 58")[57](https://testingbot.com# "Opera 57")[56](https://testingbot.com# "Opera 56")[55](https://testingbot.com# "Opera 55")[54](https://testingbot.com# "Opera 54")[53](https://testingbot.com# "Opera 53")[52](https://testingbot.com# "Opera 52")[51](https://testingbot.com# "Opera 51")[50](https://testingbot.com# "Opera 50")

## macOS Tahoe
(26.0) (ARM64)

### Chrome
[147](https://testingbot.com# "Chrome 147")[146](https://testingbot.com# "Chrome 146")[145](https://testingbot.com# "Chrome 145")[144](https://testingbot.com# "Chrome 144")[143](https://testingbot.com# "Chrome 143")[142](https://testingbot.com# "Chrome 142")[141](https://testingbot.com# "Chrome 141")[140](https://testingbot.com# "Chrome 140")[139](https://testingbot.com# "Chrome 139")[138](https://testingbot.com# "Chrome 138")[137](https://testingbot.com# "Chrome 137")[136](https://testingbot.com# "Chrome 136")[135](https://testingbot.com# "Chrome 135")[134](https://testingbot.com# "Chrome 134")[133](https://testingbot.com# "Chrome 133")[132](https://testingbot.com# "Chrome 132")[131](https://testingbot.com# "Chrome 131")[130](https://testingbot.com# "Chrome 130")[129](https://testingbot.com# "Chrome 129")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")

### Firefox
[dev](https://testingbot.com# "FF dev")[beta](https://testingbot.com# "FF beta")[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")

### Edge
[dev](https://testingbot.com# "Edge")[beta](https://testingbot.com# "Edge")[147](https://testingbot.com# "Edge 147")[146](https://testingbot.com# "Edge 146")[145](https://testingbot.com# "Edge 145")[144](https://testingbot.com# "Edge 144")[143](https://testingbot.com# "Edge 143")[142](https://testingbot.com# "Edge 142")[141](https://testingbot.com# "Edge 141")[140](https://testingbot.com# "Edge 140")[139](https://testingbot.com# "Edge 139")[138](https://testingbot.com# "Edge 138")[137](https://testingbot.com# "Edge 137")[136](https://testingbot.com# "Edge 136")[135](https://testingbot.com# "Edge 135")[134](https://testingbot.com# "Edge 134")[133](https://testingbot.com# "Edge 133")[132](https://testingbot.com# "Edge 132")[131](https://testingbot.com# "Edge 131")[130](https://testingbot.com# "Edge 130")[129](https://testingbot.com# "Edge 129")[128](https://testingbot.com# "Edge 128")[127](https://testingbot.com# "Edge 127")[126](https://testingbot.com# "Edge 126")[125](https://testingbot.com# "Edge 125")[124](https://testingbot.com# "Edge 124")[123](https://testingbot.com# "Edge 123")[122](https://testingbot.com# "Edge 122")[121](https://testingbot.com# "Edge 121")[120](https://testingbot.com# "Edge 120")[119](https://testingbot.com# "Edge 119")[118](https://testingbot.com# "Edge 118")[117](https://testingbot.com# "Edge 117")[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")

### Safari
[26](https://testingbot.com# "Safari 26")

## macOS Sequoia
(15.0) (ARM64)

### Chrome
[147](https://testingbot.com# "Chrome 147")[146](https://testingbot.com# "Chrome 146")[145](https://testingbot.com# "Chrome 145")[144](https://testingbot.com# "Chrome 144")[143](https://testingbot.com# "Chrome 143")[142](https://testingbot.com# "Chrome 142")[141](https://testingbot.com# "Chrome 141")[140](https://testingbot.com# "Chrome 140")[139](https://testingbot.com# "Chrome 139")[138](https://testingbot.com# "Chrome 138")[137](https://testingbot.com# "Chrome 137")[136](https://testingbot.com# "Chrome 136")[135](https://testingbot.com# "Chrome 135")[134](https://testingbot.com# "Chrome 134")[133](https://testingbot.com# "Chrome 133")[132](https://testingbot.com# "Chrome 132")[131](https://testingbot.com# "Chrome 131")[130](https://testingbot.com# "Chrome 130")[129](https://testingbot.com# "Chrome 129")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")

### Firefox
[dev](https://testingbot.com# "FF dev")[beta](https://testingbot.com# "FF beta")[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")

### Edge
[beta](https://testingbot.com# "Edge")[dev](https://testingbot.com# "Edge")[147](https://testingbot.com# "Edge 147")[146](https://testingbot.com# "Edge 146")[145](https://testingbot.com# "Edge 145")[144](https://testingbot.com# "Edge 144")[143](https://testingbot.com# "Edge 143")[142](https://testingbot.com# "Edge 142")[141](https://testingbot.com# "Edge 141")[140](https://testingbot.com# "Edge 140")[139](https://testingbot.com# "Edge 139")[138](https://testingbot.com# "Edge 138")[137](https://testingbot.com# "Edge 137")[136](https://testingbot.com# "Edge 136")[135](https://testingbot.com# "Edge 135")[134](https://testingbot.com# "Edge 134")[133](https://testingbot.com# "Edge 133")[132](https://testingbot.com# "Edge 132")[131](https://testingbot.com# "Edge 131")[130](https://testingbot.com# "Edge 130")[129](https://testingbot.com# "Edge 129")[128](https://testingbot.com# "Edge 128")[127](https://testingbot.com# "Edge 127")[126](https://testingbot.com# "Edge 126")[125](https://testingbot.com# "Edge 125")[124](https://testingbot.com# "Edge 124")[123](https://testingbot.com# "Edge 123")[122](https://testingbot.com# "Edge 122")[121](https://testingbot.com# "Edge 121")[120](https://testingbot.com# "Edge 120")[119](https://testingbot.com# "Edge 119")[118](https://testingbot.com# "Edge 118")[117](https://testingbot.com# "Edge 117")[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")

### Safari
[dev](https://testingbot.com# "Safari dev")[18](https://testingbot.com# "Safari 18")

## macOS Sonoma
(14.0) (ARM64)

### Chrome
[147](https://testingbot.com# "Chrome 147")[146](https://testingbot.com# "Chrome 146")[145](https://testingbot.com# "Chrome 145")[144](https://testingbot.com# "Chrome 144")[143](https://testingbot.com# "Chrome 143")[142](https://testingbot.com# "Chrome 142")[141](https://testingbot.com# "Chrome 141")[140](https://testingbot.com# "Chrome 140")[139](https://testingbot.com# "Chrome 139")[138](https://testingbot.com# "Chrome 138")[137](https://testingbot.com# "Chrome 137")[136](https://testingbot.com# "Chrome 136")[135](https://testingbot.com# "Chrome 135")[134](https://testingbot.com# "Chrome 134")[133](https://testingbot.com# "Chrome 133")[132](https://testingbot.com# "Chrome 132")[131](https://testingbot.com# "Chrome 131")[130](https://testingbot.com# "Chrome 130")[129](https://testingbot.com# "Chrome 129")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")

### Firefox
[dev](https://testingbot.com# "FF dev")[beta](https://testingbot.com# "FF beta")[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")

### Edge
[beta](https://testingbot.com# "Edge")[dev](https://testingbot.com# "Edge")[147](https://testingbot.com# "Edge 147")[146](https://testingbot.com# "Edge 146")[145](https://testingbot.com# "Edge 145")[144](https://testingbot.com# "Edge 144")[143](https://testingbot.com# "Edge 143")[142](https://testingbot.com# "Edge 142")[141](https://testingbot.com# "Edge 141")[140](https://testingbot.com# "Edge 140")[139](https://testingbot.com# "Edge 139")[138](https://testingbot.com# "Edge 138")[137](https://testingbot.com# "Edge 137")[136](https://testingbot.com# "Edge 136")[135](https://testingbot.com# "Edge 135")[134](https://testingbot.com# "Edge 134")[133](https://testingbot.com# "Edge 133")[132](https://testingbot.com# "Edge 132")[131](https://testingbot.com# "Edge 131")[130](https://testingbot.com# "Edge 130")[129](https://testingbot.com# "Edge 129")[128](https://testingbot.com# "Edge 128")[127](https://testingbot.com# "Edge 127")[126](https://testingbot.com# "Edge 126")[125](https://testingbot.com# "Edge 125")[124](https://testingbot.com# "Edge 124")[123](https://testingbot.com# "Edge 123")[122](https://testingbot.com# "Edge 122")[121](https://testingbot.com# "Edge 121")[120](https://testingbot.com# "Edge 120")[119](https://testingbot.com# "Edge 119")[118](https://testingbot.com# "Edge 118")[117](https://testingbot.com# "Edge 117")[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")

### Safari
[beta](https://testingbot.com# "Safari beta")[dev](https://testingbot.com# "Safari dev")[18](https://testingbot.com# "Safari 18")[17](https://testingbot.com# "Safari 17")

## macOS Ventura
(13.2) (ARM64)

### Chrome
[beta](https://testingbot.com# "Chrome")[147](https://testingbot.com# "Chrome 147")[146](https://testingbot.com# "Chrome 146")[145](https://testingbot.com# "Chrome 145")[144](https://testingbot.com# "Chrome 144")[143](https://testingbot.com# "Chrome 143")[142](https://testingbot.com# "Chrome 142")[141](https://testingbot.com# "Chrome 141")[140](https://testingbot.com# "Chrome 140")[139](https://testingbot.com# "Chrome 139")[138](https://testingbot.com# "Chrome 138")[137](https://testingbot.com# "Chrome 137")[136](https://testingbot.com# "Chrome 136")[135](https://testingbot.com# "Chrome 135")[134](https://testingbot.com# "Chrome 134")[133](https://testingbot.com# "Chrome 133")[132](https://testingbot.com# "Chrome 132")[131](https://testingbot.com# "Chrome 131")[130](https://testingbot.com# "Chrome 130")[129](https://testingbot.com# "Chrome 129")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")

### Firefox
[dev](https://testingbot.com# "FF dev")[beta](https://testingbot.com# "FF beta")[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")

### Edge
[dev](https://testingbot.com# "Edge")[beta](https://testingbot.com# "Edge")[147](https://testingbot.com# "Edge 147")[146](https://testingbot.com# "Edge 146")[145](https://testingbot.com# "Edge 145")[144](https://testingbot.com# "Edge 144")[143](https://testingbot.com# "Edge 143")[142](https://testingbot.com# "Edge 142")[141](https://testingbot.com# "Edge 141")[140](https://testingbot.com# "Edge 140")[139](https://testingbot.com# "Edge 139")[138](https://testingbot.com# "Edge 138")[137](https://testingbot.com# "Edge 137")[136](https://testingbot.com# "Edge 136")[135](https://testingbot.com# "Edge 135")[134](https://testingbot.com# "Edge 134")[133](https://testingbot.com# "Edge 133")[132](https://testingbot.com# "Edge 132")[131](https://testingbot.com# "Edge 131")[130](https://testingbot.com# "Edge 130")[129](https://testingbot.com# "Edge 129")[128](https://testingbot.com# "Edge 128")[127](https://testingbot.com# "Edge 127")[126](https://testingbot.com# "Edge 126")[125](https://testingbot.com# "Edge 125")[124](https://testingbot.com# "Edge 124")[123](https://testingbot.com# "Edge 123")[122](https://testingbot.com# "Edge 122")[121](https://testingbot.com# "Edge 121")[120](https://testingbot.com# "Edge 120")[119](https://testingbot.com# "Edge 119")[118](https://testingbot.com# "Edge 118")[117](https://testingbot.com# "Edge 117")[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")

### Safari
[beta](https://testingbot.com# "Safari beta")[dev](https://testingbot.com# "Safari dev")[16](https://testingbot.com# "Safari 16")

## macOS Monterey
(12.6)

### Chrome
[beta](https://testingbot.com# "Chrome")[dev](https://testingbot.com# "Chrome")[147](https://testingbot.com# "Chrome 147")[146](https://testingbot.com# "Chrome 146")[145](https://testingbot.com# "Chrome 145")[144](https://testingbot.com# "Chrome 144")[143](https://testingbot.com# "Chrome 143")[142](https://testingbot.com# "Chrome 142")[141](https://testingbot.com# "Chrome 141")[140](https://testingbot.com# "Chrome 140")[139](https://testingbot.com# "Chrome 139")[138](https://testingbot.com# "Chrome 138")[137](https://testingbot.com# "Chrome 137")[136](https://testingbot.com# "Chrome 136")[135](https://testingbot.com# "Chrome 135")[134](https://testingbot.com# "Chrome 134")[133](https://testingbot.com# "Chrome 133")[132](https://testingbot.com# "Chrome 132")[131](https://testingbot.com# "Chrome 131")[130](https://testingbot.com# "Chrome 130")[129](https://testingbot.com# "Chrome 129")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[36](https://testingbot.com# "Chrome 36")

### Firefox
[dev](https://testingbot.com# "FF dev")[beta](https://testingbot.com# "FF beta")[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### Edge
[dev](https://testingbot.com# "Edge")[147](https://testingbot.com# "Edge 147")[146](https://testingbot.com# "Edge 146")[145](https://testingbot.com# "Edge 145")[144](https://testingbot.com# "Edge 144")[143](https://testingbot.com# "Edge 143")[142](https://testingbot.com# "Edge 142")[141](https://testingbot.com# "Edge 141")[140](https://testingbot.com# "Edge 140")[139](https://testingbot.com# "Edge 139")[138](https://testingbot.com# "Edge 138")[137](https://testingbot.com# "Edge 137")[136](https://testingbot.com# "Edge 136")[135](https://testingbot.com# "Edge 135")[134](https://testingbot.com# "Edge 134")[133](https://testingbot.com# "Edge 133")[132](https://testingbot.com# "Edge 132")[131](https://testingbot.com# "Edge 131")[130](https://testingbot.com# "Edge 130")[129](https://testingbot.com# "Edge 129")[128](https://testingbot.com# "Edge 128")[127](https://testingbot.com# "Edge 127")[126](https://testingbot.com# "Edge 126")[125](https://testingbot.com# "Edge 125")[124](https://testingbot.com# "Edge 124")[123](https://testingbot.com# "Edge 123")[122](https://testingbot.com# "Edge 122")[121](https://testingbot.com# "Edge 121")[120](https://testingbot.com# "Edge 120")[119](https://testingbot.com# "Edge 119")[118](https://testingbot.com# "Edge 118")[117](https://testingbot.com# "Edge 117")[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")

### Opera
[130](https://testingbot.com# "Opera 130")[129](https://testingbot.com# "Opera 129")[128](https://testingbot.com# "Opera 128")[127](https://testingbot.com# "Opera 127")[126](https://testingbot.com# "Opera 126")[125](https://testingbot.com# "Opera 125")[124](https://testingbot.com# "Opera 124")[123](https://testingbot.com# "Opera 123")[122](https://testingbot.com# "Opera 122")[121](https://testingbot.com# "Opera 121")[120](https://testingbot.com# "Opera 120")[119](https://testingbot.com# "Opera 119")[118](https://testingbot.com# "Opera 118")[117](https://testingbot.com# "Opera 117")[116](https://testingbot.com# "Opera 116")[115](https://testingbot.com# "Opera 115")[114](https://testingbot.com# "Opera 114")[113](https://testingbot.com# "Opera 113")[112](https://testingbot.com# "Opera 112")[111](https://testingbot.com# "Opera 111")[110](https://testingbot.com# "Opera 110")[109](https://testingbot.com# "Opera 109")[108](https://testingbot.com# "Opera 108")[107](https://testingbot.com# "Opera 107")[106](https://testingbot.com# "Opera 106")[105](https://testingbot.com# "Opera 105")[104](https://testingbot.com# "Opera 104")[103](https://testingbot.com# "Opera 103")[102](https://testingbot.com# "Opera 102")[101](https://testingbot.com# "Opera 101")[100](https://testingbot.com# "Opera 100")[99](https://testingbot.com# "Opera 99")[98](https://testingbot.com# "Opera 98")[97](https://testingbot.com# "Opera 97")[96](https://testingbot.com# "Opera 96")[95](https://testingbot.com# "Opera 95")[94](https://testingbot.com# "Opera 94")[93](https://testingbot.com# "Opera 93")[92](https://testingbot.com# "Opera 92")[91](https://testingbot.com# "Opera 91")[90](https://testingbot.com# "Opera 90")[89](https://testingbot.com# "Opera 89")[88](https://testingbot.com# "Opera 88")[87](https://testingbot.com# "Opera 87")[86](https://testingbot.com# "Opera 86")[85](https://testingbot.com# "Opera 85")[84](https://testingbot.com# "Opera 84")[83](https://testingbot.com# "Opera 83")[82](https://testingbot.com# "Opera 82")[81](https://testingbot.com# "Opera 81")[80](https://testingbot.com# "Opera 80")[79](https://testingbot.com# "Opera 79")[78](https://testingbot.com# "Opera 78")[77](https://testingbot.com# "Opera 77")[76](https://testingbot.com# "Opera 76")[75](https://testingbot.com# "Opera 75")[74](https://testingbot.com# "Opera 74")[73](https://testingbot.com# "Opera 73")[72](https://testingbot.com# "Opera 72")[71](https://testingbot.com# "Opera 71")[70](https://testingbot.com# "Opera 70")[69](https://testingbot.com# "Opera 69")[68](https://testingbot.com# "Opera 68")[67](https://testingbot.com# "Opera 67")[66](https://testingbot.com# "Opera 66")[65](https://testingbot.com# "Opera 65")[64](https://testingbot.com# "Opera 64")[63](https://testingbot.com# "Opera 63")[62](https://testingbot.com# "Opera 62")[60](https://testingbot.com# "Opera 60")[59](https://testingbot.com# "Opera 59")[58](https://testingbot.com# "Opera 58")[57](https://testingbot.com# "Opera 57")[56](https://testingbot.com# "Opera 56")[55](https://testingbot.com# "Opera 55")[54](https://testingbot.com# "Opera 54")[53](https://testingbot.com# "Opera 53")[52](https://testingbot.com# "Opera 52")[51](https://testingbot.com# "Opera 51")[50](https://testingbot.com# "Opera 50")

### Safari
[beta](https://testingbot.com# "Safari beta")[dev](https://testingbot.com# "Safari dev")[15](https://testingbot.com# "Safari 15")

## macOS Big Sur
(11.6)

### Chrome
[beta](https://testingbot.com# "Chrome")[dev](https://testingbot.com# "Chrome")[138](https://testingbot.com# "Chrome 138")[137](https://testingbot.com# "Chrome 137")[136](https://testingbot.com# "Chrome 136")[135](https://testingbot.com# "Chrome 135")[134](https://testingbot.com# "Chrome 134")[133](https://testingbot.com# "Chrome 133")[132](https://testingbot.com# "Chrome 132")[131](https://testingbot.com# "Chrome 131")[130](https://testingbot.com# "Chrome 130")[129](https://testingbot.com# "Chrome 129")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[36](https://testingbot.com# "Chrome 36")

### Firefox
[dev](https://testingbot.com# "FF dev")[beta](https://testingbot.com# "FF beta")[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### Edge
[138](https://testingbot.com# "Edge 138")[137](https://testingbot.com# "Edge 137")[136](https://testingbot.com# "Edge 136")[135](https://testingbot.com# "Edge 135")[134](https://testingbot.com# "Edge 134")[133](https://testingbot.com# "Edge 133")[132](https://testingbot.com# "Edge 132")[131](https://testingbot.com# "Edge 131")[130](https://testingbot.com# "Edge 130")[129](https://testingbot.com# "Edge 129")[128](https://testingbot.com# "Edge 128")[127](https://testingbot.com# "Edge 127")[126](https://testingbot.com# "Edge 126")[125](https://testingbot.com# "Edge 125")[124](https://testingbot.com# "Edge 124")[123](https://testingbot.com# "Edge 123")[122](https://testingbot.com# "Edge 122")[121](https://testingbot.com# "Edge 121")[120](https://testingbot.com# "Edge 120")[119](https://testingbot.com# "Edge 119")[118](https://testingbot.com# "Edge 118")[117](https://testingbot.com# "Edge 117")[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")

### Opera
[122](https://testingbot.com# "Opera 122")[121](https://testingbot.com# "Opera 121")[120](https://testingbot.com# "Opera 120")[119](https://testingbot.com# "Opera 119")[118](https://testingbot.com# "Opera 118")[117](https://testingbot.com# "Opera 117")[116](https://testingbot.com# "Opera 116")[115](https://testingbot.com# "Opera 115")[114](https://testingbot.com# "Opera 114")[113](https://testingbot.com# "Opera 113")[112](https://testingbot.com# "Opera 112")[111](https://testingbot.com# "Opera 111")[110](https://testingbot.com# "Opera 110")[109](https://testingbot.com# "Opera 109")[108](https://testingbot.com# "Opera 108")[107](https://testingbot.com# "Opera 107")[106](https://testingbot.com# "Opera 106")[105](https://testingbot.com# "Opera 105")[104](https://testingbot.com# "Opera 104")[103](https://testingbot.com# "Opera 103")[102](https://testingbot.com# "Opera 102")[101](https://testingbot.com# "Opera 101")[100](https://testingbot.com# "Opera 100")[99](https://testingbot.com# "Opera 99")[98](https://testingbot.com# "Opera 98")[97](https://testingbot.com# "Opera 97")[96](https://testingbot.com# "Opera 96")[95](https://testingbot.com# "Opera 95")[94](https://testingbot.com# "Opera 94")[93](https://testingbot.com# "Opera 93")[92](https://testingbot.com# "Opera 92")[91](https://testingbot.com# "Opera 91")[90](https://testingbot.com# "Opera 90")[89](https://testingbot.com# "Opera 89")[88](https://testingbot.com# "Opera 88")[87](https://testingbot.com# "Opera 87")[86](https://testingbot.com# "Opera 86")[85](https://testingbot.com# "Opera 85")[84](https://testingbot.com# "Opera 84")[83](https://testingbot.com# "Opera 83")[82](https://testingbot.com# "Opera 82")[81](https://testingbot.com# "Opera 81")[80](https://testingbot.com# "Opera 80")[79](https://testingbot.com# "Opera 79")[78](https://testingbot.com# "Opera 78")[77](https://testingbot.com# "Opera 77")[76](https://testingbot.com# "Opera 76")[75](https://testingbot.com# "Opera 75")[74](https://testingbot.com# "Opera 74")[73](https://testingbot.com# "Opera 73")[72](https://testingbot.com# "Opera 72")[71](https://testingbot.com# "Opera 71")[70](https://testingbot.com# "Opera 70")[69](https://testingbot.com# "Opera 69")[68](https://testingbot.com# "Opera 68")[67](https://testingbot.com# "Opera 67")[66](https://testingbot.com# "Opera 66")[65](https://testingbot.com# "Opera 65")[64](https://testingbot.com# "Opera 64")[63](https://testingbot.com# "Opera 63")[62](https://testingbot.com# "Opera 62")[60](https://testingbot.com# "Opera 60")[59](https://testingbot.com# "Opera 59")[58](https://testingbot.com# "Opera 58")[57](https://testingbot.com# "Opera 57")[56](https://testingbot.com# "Opera 56")[55](https://testingbot.com# "Opera 55")[54](https://testingbot.com# "Opera 54")[53](https://testingbot.com# "Opera 53")[52](https://testingbot.com# "Opera 52")[51](https://testingbot.com# "Opera 51")[50](https://testingbot.com# "Opera 50")

### Safari
[14](https://testingbot.com# "Safari 14")

## macOS Catalina
(10.15)

### Chrome
[beta](https://testingbot.com# "Chrome")[dev](https://testingbot.com# "Chrome")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[36](https://testingbot.com# "Chrome 36")

### Firefox
[dev](https://testingbot.com# "FF dev")[beta](https://testingbot.com# "FF beta")[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### Edge
[122](https://testingbot.com# "Edge 122")[121](https://testingbot.com# "Edge 121")[120](https://testingbot.com# "Edge 120")[119](https://testingbot.com# "Edge 119")[118](https://testingbot.com# "Edge 118")[117](https://testingbot.com# "Edge 117")[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")

### Opera
[129](https://testingbot.com# "Opera 129")[128](https://testingbot.com# "Opera 128")[127](https://testingbot.com# "Opera 127")[126](https://testingbot.com# "Opera 126")[125](https://testingbot.com# "Opera 125")[124](https://testingbot.com# "Opera 124")[123](https://testingbot.com# "Opera 123")[122](https://testingbot.com# "Opera 122")[121](https://testingbot.com# "Opera 121")[120](https://testingbot.com# "Opera 120")[119](https://testingbot.com# "Opera 119")[118](https://testingbot.com# "Opera 118")[117](https://testingbot.com# "Opera 117")[116](https://testingbot.com# "Opera 116")[115](https://testingbot.com# "Opera 115")[114](https://testingbot.com# "Opera 114")[113](https://testingbot.com# "Opera 113")[112](https://testingbot.com# "Opera 112")[111](https://testingbot.com# "Opera 111")[110](https://testingbot.com# "Opera 110")[109](https://testingbot.com# "Opera 109")[108](https://testingbot.com# "Opera 108")[107](https://testingbot.com# "Opera 107")[106](https://testingbot.com# "Opera 106")[105](https://testingbot.com# "Opera 105")[104](https://testingbot.com# "Opera 104")[103](https://testingbot.com# "Opera 103")[102](https://testingbot.com# "Opera 102")[101](https://testingbot.com# "Opera 101")[100](https://testingbot.com# "Opera 100")[99](https://testingbot.com# "Opera 99")[98](https://testingbot.com# "Opera 98")[97](https://testingbot.com# "Opera 97")[96](https://testingbot.com# "Opera 96")[95](https://testingbot.com# "Opera 95")[94](https://testingbot.com# "Opera 94")[93](https://testingbot.com# "Opera 93")[92](https://testingbot.com# "Opera 92")[91](https://testingbot.com# "Opera 91")[90](https://testingbot.com# "Opera 90")[89](https://testingbot.com# "Opera 89")[88](https://testingbot.com# "Opera 88")[87](https://testingbot.com# "Opera 87")[86](https://testingbot.com# "Opera 86")[85](https://testingbot.com# "Opera 85")[84](https://testingbot.com# "Opera 84")[83](https://testingbot.com# "Opera 83")[82](https://testingbot.com# "Opera 82")[81](https://testingbot.com# "Opera 81")[80](https://testingbot.com# "Opera 80")[79](https://testingbot.com# "Opera 79")[78](https://testingbot.com# "Opera 78")[77](https://testingbot.com# "Opera 77")[76](https://testingbot.com# "Opera 76")[75](https://testingbot.com# "Opera 75")[74](https://testingbot.com# "Opera 74")[73](https://testingbot.com# "Opera 73")[72](https://testingbot.com# "Opera 72")[71](https://testingbot.com# "Opera 71")[70](https://testingbot.com# "Opera 70")[69](https://testingbot.com# "Opera 69")[68](https://testingbot.com# "Opera 68")[67](https://testingbot.com# "Opera 67")[66](https://testingbot.com# "Opera 66")[65](https://testingbot.com# "Opera 65")[64](https://testingbot.com# "Opera 64")[63](https://testingbot.com# "Opera 63")[62](https://testingbot.com# "Opera 62")[60](https://testingbot.com# "Opera 60")[59](https://testingbot.com# "Opera 59")[58](https://testingbot.com# "Opera 58")[57](https://testingbot.com# "Opera 57")[56](https://testingbot.com# "Opera 56")[55](https://testingbot.com# "Opera 55")[54](https://testingbot.com# "Opera 54")[53](https://testingbot.com# "Opera 53")[52](https://testingbot.com# "Opera 52")[51](https://testingbot.com# "Opera 51")[50](https://testingbot.com# "Opera 50")

### Safari
[13](https://testingbot.com# "Safari 13")

## macOS Mojave
(10.14)

### Chrome
[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[36](https://testingbot.com# "Chrome 36")

### Firefox
[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### Edge
[116](https://testingbot.com# "Edge 116")[115](https://testingbot.com# "Edge 115")[114](https://testingbot.com# "Edge 114")[113](https://testingbot.com# "Edge 113")[112](https://testingbot.com# "Edge 112")[111](https://testingbot.com# "Edge 111")[110](https://testingbot.com# "Edge 110")[109](https://testingbot.com# "Edge 109")[108](https://testingbot.com# "Edge 108")[107](https://testingbot.com# "Edge 107")[106](https://testingbot.com# "Edge 106")[105](https://testingbot.com# "Edge 105")[104](https://testingbot.com# "Edge 104")[103](https://testingbot.com# "Edge 103")[102](https://testingbot.com# "Edge 102")[101](https://testingbot.com# "Edge 101")[100](https://testingbot.com# "Edge 100")[99](https://testingbot.com# "Edge 99")[98](https://testingbot.com# "Edge 98")[97](https://testingbot.com# "Edge 97")[96](https://testingbot.com# "Edge 96")[95](https://testingbot.com# "Edge 95")[94](https://testingbot.com# "Edge 94")[93](https://testingbot.com# "Edge 93")[92](https://testingbot.com# "Edge 92")[91](https://testingbot.com# "Edge 91")[90](https://testingbot.com# "Edge 90")[89](https://testingbot.com# "Edge 89")[88](https://testingbot.com# "Edge 88")[87](https://testingbot.com# "Edge 87")[86](https://testingbot.com# "Edge 86")[85](https://testingbot.com# "Edge 85")[84](https://testingbot.com# "Edge 84")[83](https://testingbot.com# "Edge 83")[81](https://testingbot.com# "Edge 81")[80](https://testingbot.com# "Edge 80")[79](https://testingbot.com# "Edge 79")

### Opera
[102](https://testingbot.com# "Opera 102")[101](https://testingbot.com# "Opera 101")[100](https://testingbot.com# "Opera 100")[99](https://testingbot.com# "Opera 99")[98](https://testingbot.com# "Opera 98")[97](https://testingbot.com# "Opera 97")[96](https://testingbot.com# "Opera 96")[95](https://testingbot.com# "Opera 95")[94](https://testingbot.com# "Opera 94")[93](https://testingbot.com# "Opera 93")[92](https://testingbot.com# "Opera 92")[91](https://testingbot.com# "Opera 91")[90](https://testingbot.com# "Opera 90")[89](https://testingbot.com# "Opera 89")[88](https://testingbot.com# "Opera 88")[87](https://testingbot.com# "Opera 87")[86](https://testingbot.com# "Opera 86")[85](https://testingbot.com# "Opera 85")[84](https://testingbot.com# "Opera 84")[83](https://testingbot.com# "Opera 83")[82](https://testingbot.com# "Opera 82")[81](https://testingbot.com# "Opera 81")[80](https://testingbot.com# "Opera 80")[79](https://testingbot.com# "Opera 79")[78](https://testingbot.com# "Opera 78")[77](https://testingbot.com# "Opera 77")[76](https://testingbot.com# "Opera 76")[75](https://testingbot.com# "Opera 75")[74](https://testingbot.com# "Opera 74")[73](https://testingbot.com# "Opera 73")[72](https://testingbot.com# "Opera 72")[71](https://testingbot.com# "Opera 71")[70](https://testingbot.com# "Opera 70")[69](https://testingbot.com# "Opera 69")[68](https://testingbot.com# "Opera 68")[67](https://testingbot.com# "Opera 67")[66](https://testingbot.com# "Opera 66")[65](https://testingbot.com# "Opera 65")[64](https://testingbot.com# "Opera 64")[63](https://testingbot.com# "Opera 63")[62](https://testingbot.com# "Opera 62")[60](https://testingbot.com# "Opera 60")[59](https://testingbot.com# "Opera 59")[58](https://testingbot.com# "Opera 58")[57](https://testingbot.com# "Opera 57")[56](https://testingbot.com# "Opera 56")[55](https://testingbot.com# "Opera 55")[54](https://testingbot.com# "Opera 54")[53](https://testingbot.com# "Opera 53")[52](https://testingbot.com# "Opera 52")[51](https://testingbot.com# "Opera 51")[50](https://testingbot.com# "Opera 50")

### Safari
[12](https://testingbot.com# "Safari 12")

## macOS High Sierra
(10.13)

### Chrome
[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[36](https://testingbot.com# "Chrome 36")

### Firefox
[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")[90](https://testingbot.com# "FF 90")[89](https://testingbot.com# "FF 89")[88](https://testingbot.com# "FF 88")[87](https://testingbot.com# "FF 87")[86](https://testingbot.com# "FF 86")[85](https://testingbot.com# "FF 85")[84](https://testingbot.com# "FF 84")[83](https://testingbot.com# "FF 83")[82](https://testingbot.com# "FF 82")[81](https://testingbot.com# "FF 81")[80](https://testingbot.com# "FF 80")[79](https://testingbot.com# "FF 79")[78](https://testingbot.com# "FF 78")[77](https://testingbot.com# "FF 77")[76](https://testingbot.com# "FF 76")[75](https://testingbot.com# "FF 75")[74](https://testingbot.com# "FF 74")[73](https://testingbot.com# "FF 73")[72](https://testingbot.com# "FF 72")[71](https://testingbot.com# "FF 71")[70](https://testingbot.com# "FF 70")[69](https://testingbot.com# "FF 69")[68](https://testingbot.com# "FF 68")[67](https://testingbot.com# "FF 67")[66](https://testingbot.com# "FF 66")[65](https://testingbot.com# "FF 65")[64](https://testingbot.com# "FF 64")[63](https://testingbot.com# "FF 63")[62](https://testingbot.com# "FF 62")[61](https://testingbot.com# "FF 61")[60](https://testingbot.com# "FF 60")[59](https://testingbot.com# "FF 59")[58](https://testingbot.com# "FF 58")[57](https://testingbot.com# "FF 57")[56](https://testingbot.com# "FF 56")[55](https://testingbot.com# "FF 55")[54](https://testingbot.com# "FF 54")[53](https://testingbot.com# "FF 53")[52](https://testingbot.com# "FF 52")[51](https://testingbot.com# "FF 51")[50](https://testingbot.com# "FF 50")[49](https://testingbot.com# "FF 49")[48](https://testingbot.com# "FF 48")[47](https://testingbot.com# "FF 47")[46](https://testingbot.com# "FF 46")[45](https://testingbot.com# "FF 45")[44](https://testingbot.com# "FF 44")[43](https://testingbot.com# "FF 43")[42](https://testingbot.com# "FF 42")[41](https://testingbot.com# "FF 41")[40](https://testingbot.com# "FF 40")[39](https://testingbot.com# "FF 39")[38](https://testingbot.com# "FF 38")[37](https://testingbot.com# "FF 37")[36](https://testingbot.com# "FF 36")[35](https://testingbot.com# "FF 35")[34](https://testingbot.com# "FF 34")[33](https://testingbot.com# "FF 33")[32](https://testingbot.com# "FF 32")[31](https://testingbot.com# "FF 31")[30](https://testingbot.com# "FF 30")[29](https://testingbot.com# "FF 29")[28](https://testingbot.com# "FF 28")[27](https://testingbot.com# "FF 27")[26](https://testingbot.com# "FF 26")[25](https://testingbot.com# "FF 25")[24](https://testingbot.com# "FF 24")[23](https://testingbot.com# "FF 23")[22](https://testingbot.com# "FF 22")[21](https://testingbot.com# "FF 21")[20](https://testingbot.com# "FF 20")[19](https://testingbot.com# "FF 19")[18](https://testingbot.com# "FF 18")[17](https://testingbot.com# "FF 17")[16](https://testingbot.com# "FF 16")[15](https://testingbot.com# "FF 15")[14](https://testingbot.com# "FF 14")[13](https://testingbot.com# "FF 13")[12](https://testingbot.com# "FF 12")[11](https://testingbot.com# "FF 11")[10](https://testingbot.com# "FF 10")[9](https://testingbot.com# "FF 9")[8](https://testingbot.com# "FF 8")[7](https://testingbot.com# "FF 7")[6](https://testingbot.com# "FF 6")[5](https://testingbot.com# "FF 5")[4](https://testingbot.com# "FF 4")

### Opera
[102](https://testingbot.com# "Opera 102")[101](https://testingbot.com# "Opera 101")[100](https://testingbot.com# "Opera 100")[99](https://testingbot.com# "Opera 99")[98](https://testingbot.com# "Opera 98")[97](https://testingbot.com# "Opera 97")[96](https://testingbot.com# "Opera 96")[95](https://testingbot.com# "Opera 95")[94](https://testingbot.com# "Opera 94")[93](https://testingbot.com# "Opera 93")[92](https://testingbot.com# "Opera 92")[91](https://testingbot.com# "Opera 91")[90](https://testingbot.com# "Opera 90")[89](https://testingbot.com# "Opera 89")[88](https://testingbot.com# "Opera 88")[87](https://testingbot.com# "Opera 87")[86](https://testingbot.com# "Opera 86")[85](https://testingbot.com# "Opera 85")[84](https://testingbot.com# "Opera 84")[83](https://testingbot.com# "Opera 83")[82](https://testingbot.com# "Opera 82")[81](https://testingbot.com# "Opera 81")[80](https://testingbot.com# "Opera 80")[79](https://testingbot.com# "Opera 79")[78](https://testingbot.com# "Opera 78")[77](https://testingbot.com# "Opera 77")[76](https://testingbot.com# "Opera 76")[75](https://testingbot.com# "Opera 75")[74](https://testingbot.com# "Opera 74")[73](https://testingbot.com# "Opera 73")[72](https://testingbot.com# "Opera 72")[71](https://testingbot.com# "Opera 71")[70](https://testingbot.com# "Opera 70")[69](https://testingbot.com# "Opera 69")[68](https://testingbot.com# "Opera 68")[67](https://testingbot.com# "Opera 67")[66](https://testingbot.com# "Opera 66")[65](https://testingbot.com# "Opera 65")[64](https://testingbot.com# "Opera 64")[63](https://testingbot.com# "Opera 63")[62](https://testingbot.com# "Opera 62")[60](https://testingbot.com# "Opera 60")[59](https://testingbot.com# "Opera 59")[58](https://testingbot.com# "Opera 58")[57](https://testingbot.com# "Opera 57")[56](https://testingbot.com# "Opera 56")[55](https://testingbot.com# "Opera 55")[54](https://testingbot.com# "Opera 54")[53](https://testingbot.com# "Opera 53")[52](https://testingbot.com# "Opera 52")[51](https://testingbot.com# "Opera 51")[50](https://testingbot.com# "Opera 50")

### Safari
[11](https://testingbot.com# "Safari 11")

## Linux
(Ubuntu 22.04)

### Chrome
[beta](https://testingbot.com# "Chrome")[dev](https://testingbot.com# "Chrome")[147](https://testingbot.com# "Chrome 147")[146](https://testingbot.com# "Chrome 146")[145](https://testingbot.com# "Chrome 145")[144](https://testingbot.com# "Chrome 144")[143](https://testingbot.com# "Chrome 143")[142](https://testingbot.com# "Chrome 142")[141](https://testingbot.com# "Chrome 141")[140](https://testingbot.com# "Chrome 140")[139](https://testingbot.com# "Chrome 139")[138](https://testingbot.com# "Chrome 138")[137](https://testingbot.com# "Chrome 137")[136](https://testingbot.com# "Chrome 136")[135](https://testingbot.com# "Chrome 135")[134](https://testingbot.com# "Chrome 134")[133](https://testingbot.com# "Chrome 133")[132](https://testingbot.com# "Chrome 132")[131](https://testingbot.com# "Chrome 131")[130](https://testingbot.com# "Chrome 130")[129](https://testingbot.com# "Chrome 129")[128](https://testingbot.com# "Chrome 128")[127](https://testingbot.com# "Chrome 127")[126](https://testingbot.com# "Chrome 126")[125](https://testingbot.com# "Chrome 125")[124](https://testingbot.com# "Chrome 124")[123](https://testingbot.com# "Chrome 123")[122](https://testingbot.com# "Chrome 122")[121](https://testingbot.com# "Chrome 121")[120](https://testingbot.com# "Chrome 120")[119](https://testingbot.com# "Chrome 119")[118](https://testingbot.com# "Chrome 118")[117](https://testingbot.com# "Chrome 117")[116](https://testingbot.com# "Chrome 116")[115](https://testingbot.com# "Chrome 115")[114](https://testingbot.com# "Chrome 114")[113](https://testingbot.com# "Chrome 113")[112](https://testingbot.com# "Chrome 112")[111](https://testingbot.com# "Chrome 111")[110](https://testingbot.com# "Chrome 110")[109](https://testingbot.com# "Chrome 109")[108](https://testingbot.com# "Chrome 108")[107](https://testingbot.com# "Chrome 107")[106](https://testingbot.com# "Chrome 106")[105](https://testingbot.com# "Chrome 105")[104](https://testingbot.com# "Chrome 104")[103](https://testingbot.com# "Chrome 103")[102](https://testingbot.com# "Chrome 102")[101](https://testingbot.com# "Chrome 101")[100](https://testingbot.com# "Chrome 100")[99](https://testingbot.com# "Chrome 99")[98](https://testingbot.com# "Chrome 98")[97](https://testingbot.com# "Chrome 97")[96](https://testingbot.com# "Chrome 96")[95](https://testingbot.com# "Chrome 95")[94](https://testingbot.com# "Chrome 94")[93](https://testingbot.com# "Chrome 93")[92](https://testingbot.com# "Chrome 92")[91](https://testingbot.com# "Chrome 91")[90](https://testingbot.com# "Chrome 90")[89](https://testingbot.com# "Chrome 89")[88](https://testingbot.com# "Chrome 88")[87](https://testingbot.com# "Chrome 87")[86](https://testingbot.com# "Chrome 86")[85](https://testingbot.com# "Chrome 85")[84](https://testingbot.com# "Chrome 84")[83](https://testingbot.com# "Chrome 83")[81](https://testingbot.com# "Chrome 81")[80](https://testingbot.com# "Chrome 80")[79](https://testingbot.com# "Chrome 79")[78](https://testingbot.com# "Chrome 78")[77](https://testingbot.com# "Chrome 77")[76](https://testingbot.com# "Chrome 76")[75](https://testingbot.com# "Chrome 75")[74](https://testingbot.com# "Chrome 74")[73](https://testingbot.com# "Chrome 73")[72](https://testingbot.com# "Chrome 72")[71](https://testingbot.com# "Chrome 71")[70](https://testingbot.com# "Chrome 70")[69](https://testingbot.com# "Chrome 69")[68](https://testingbot.com# "Chrome 68")[67](https://testingbot.com# "Chrome 67")[66](https://testingbot.com# "Chrome 66")[65](https://testingbot.com# "Chrome 65")[64](https://testingbot.com# "Chrome 64")[63](https://testingbot.com# "Chrome 63")[62](https://testingbot.com# "Chrome 62")[61](https://testingbot.com# "Chrome 61")[60](https://testingbot.com# "Chrome 60")[59](https://testingbot.com# "Chrome 59")[58](https://testingbot.com# "Chrome 58")[57](https://testingbot.com# "Chrome 57")[56](https://testingbot.com# "Chrome 56")[55](https://testingbot.com# "Chrome 55")[54](https://testingbot.com# "Chrome 54")[53](https://testingbot.com# "Chrome 53")[52](https://testingbot.com# "Chrome 52")[51](https://testingbot.com# "Chrome 51")[50](https://testingbot.com# "Chrome 50")[49](https://testingbot.com# "Chrome 49")[48](https://testingbot.com# "Chrome 48")[47](https://testingbot.com# "Chrome 47")[46](https://testingbot.com# "Chrome 46")[45](https://testingbot.com# "Chrome 45")[44](https://testingbot.com# "Chrome 44")[43](https://testingbot.com# "Chrome 43")[42](https://testingbot.com# "Chrome 42")[41](https://testingbot.com# "Chrome 41")[40](https://testingbot.com# "Chrome 40")[39](https://testingbot.com# "Chrome 39")[38](https://testingbot.com# "Chrome 38")[37](https://testingbot.com# "Chrome 37")

### Edge
[147](https://testingbot.com# "edge147")[146](https://testingbot.com# "edge146")[145](https://testingbot.com# "edge145")[144](https://testingbot.com# "edge144")[143](https://testingbot.com# "edge143")

### Firefox
[150](https://testingbot.com# "FF 150")[149](https://testingbot.com# "FF 149")[148](https://testingbot.com# "FF 148")[147](https://testingbot.com# "FF 147")[146](https://testingbot.com# "FF 146")[145](https://testingbot.com# "FF 145")[144](https://testingbot.com# "FF 144")[143](https://testingbot.com# "FF 143")[142](https://testingbot.com# "FF 142")[141](https://testingbot.com# "FF 141")[140](https://testingbot.com# "FF 140")[139](https://testingbot.com# "FF 139")[138](https://testingbot.com# "FF 138")[137](https://testingbot.com# "FF 137")[136](https://testingbot.com# "FF 136")[135](https://testingbot.com# "FF 135")[134](https://testingbot.com# "FF 134")[133](https://testingbot.com# "FF 133")[132](https://testingbot.com# "FF 132")[131](https://testingbot.com# "FF 131")[130](https://testingbot.com# "FF 130")[129](https://testingbot.com# "FF 129")[128](https://testingbot.com# "FF 128")[127](https://testingbot.com# "FF 127")[126](https://testingbot.com# "FF 126")[125](https://testingbot.com# "FF 125")[124](https://testingbot.com# "FF 124")[123](https://testingbot.com# "FF 123")[122](https://testingbot.com# "FF 122")[121](https://testingbot.com# "FF 121")[120](https://testingbot.com# "FF 120")[119](https://testingbot.com# "FF 119")[118](https://testingbot.com# "FF 118")[117](https://testingbot.com# "FF 117")[116](https://testingbot.com# "FF 116")[115](https://testingbot.com# "FF 115")[114](https://testingbot.com# "FF 114")[113](https://testingbot.com# "FF 113")[112](https://testingbot.com# "FF 112")[111](https://testingbot.com# "FF 111")[110](https://testingbot.com# "FF 110")[109](https://testingbot.com# "FF 109")[108](https://testingbot.com# "FF 108")[107](https://testingbot.com# "FF 107")[106](https://testingbot.com# "FF 106")[105](https://testingbot.com# "FF 105")[104](https://testingbot.com# "FF 104")[103](https://testingbot.com# "FF 103")[102](https://testingbot.com# "FF 102")[101](https://testingbot.com# "FF 101")[100](https://testingbot.com# "FF 100")[99](https://testingbot.com# "FF 99")[98](https://testingbot.com# "FF 98")[97](https://testingbot.com# "FF 97")[96](https://testingbot.com# "FF 96")[95](https://testingbot.com# "FF 95")[94](https://testingbot.com# "FF 94")[93](https://testingbot.com# "FF 93")[92](https://testingbot.com# "FF 92")[91](https://testingbot.com# "FF 91")

### Opera
[130](https://testingbot.com# "Opera 130")[129](https://testingbot.com# "Opera 129")[128](https://testingbot.com# "Opera 128")[127](https://testingbot.com# "Opera 127")[126](https://testingbot.com# "Opera 126")[125](https://testingbot.com# "Opera 125")[124](https://testingbot.com# "Opera 124")[123](https://testingbot.com# "Opera 123")[122](https://testingbot.com# "Opera 122")[121](https://testingbot.com# "Opera 121")[120](https://testingbot.com# "Opera 120")[119](https://testingbot.com# "Opera 119")[118](https://testingbot.com# "Opera 118")[117](https://testingbot.com# "Opera 117")[116](https://testingbot.com# "Opera 116")[115](https://testingbot.com# "Opera 115")[114](https://testingbot.com# "Opera 114")[113](https://testingbot.com# "Opera 113")[112](https://testingbot.com# "Opera 112")[111](https://testingbot.com# "Opera 111")[110](https://testingbot.com# "Opera 110")[109](https://testingbot.com# "Opera 109")[108](https://testingbot.com# "Opera 108")[107](https://testingbot.com# "Opera 107")[106](https://testingbot.com# "Opera 106")[105](https://testingbot.com# "Opera 105")[104](https://testingbot.com# "Opera 104")[103](https://testingbot.com# "Opera 103")[102](https://testingbot.com# "Opera 102")[101](https://testingbot.com# "Opera 101")[100](https://testingbot.com# "Opera 100")[99](https://testingbot.com# "Opera 99")[98](https://testingbot.com# "Opera 98")[97](https://testingbot.com# "Opera 97")[96](https://testingbot.com# "Opera 96")[95](https://testingbot.com# "Opera 95")[94](https://testingbot.com# "Opera 94")[93](https://testingbot.com# "Opera 93")[92](https://testingbot.com# "Opera 92")[91](https://testingbot.com# "Opera 91")[90](https://testingbot.com# "Opera 90")[89](https://testingbot.com# "Opera 89")[88](https://testingbot.com# "Opera 88")[87](https://testingbot.com# "Opera 87")[86](https://testingbot.com# "Opera 86")[85](https://testingbot.com# "Opera 85")[84](https://testingbot.com# "Opera 84")[83](https://testingbot.com# "Opera 83")[82](https://testingbot.com# "Opera 82")[81](https://testingbot.com# "Opera 81")[80](https://testingbot.com# "Opera 80")[79](https://testingbot.com# "Opera 79")

## iOS Simulators
### Phones
#### 26.3
[iPhone 17 Pro Max (26.3)](https://testingbot.com# "iPhone 17 Pro Max - 26.3")[iPhone 17 Pro (26.3)](https://testingbot.com# "iPhone 17 Pro - 26.3")[iPhone 17 (26.3)](https://testingbot.com# "iPhone 17 - 26.3")[iPhone 16e (26.3)](https://testingbot.com# "iPhone 16e - 26.3")[iPhone Air (26.3)](https://testingbot.com# "iPhone Air - 26.3")
#### 26.0
[iPhone 17 Pro Max (26.0)](https://testingbot.com# "iPhone 17 Pro Max - 26.0")[iPhone 17 Pro (26.0)](https://testingbot.com# "iPhone 17 Pro - 26.0")[iPhone 17 (26.0)](https://testingbot.com# "iPhone 17 - 26.0")[iPhone 16e (26.0)](https://testingbot.com# "iPhone 16e - 26.0")[iPhone Air (26.0)](https://testingbot.com# "iPhone Air - 26.0")
#### 18.6
[iPhone 16 Pro Max (18.6)](https://testingbot.com# "iPhone 16 Pro Max - 18.6")[iPhone 16 Pro (18.6)](https://testingbot.com# "iPhone 16 Pro - 18.6")[iPhone 16 Plus (18.6)](https://testingbot.com# "iPhone 16 Plus - 18.6")[iPhone 16 (18.6)](https://testingbot.com# "iPhone 16 - 18.6")[iPhone 16e (18.6)](https://testingbot.com# "iPhone 16e - 18.6")
#### 18.3
[iPhone 16 (18.3)](https://testingbot.com# "iPhone 16 - 18.3")[iPhone 16e (18.3)](https://testingbot.com# "iPhone 16e - 18.3")[iPhone 16 Plus (18.3)](https://testingbot.com# "iPhone 16 Plus - 18.3")[iPhone 16 Pro (18.3)](https://testingbot.com# "iPhone 16 Pro - 18.3")[iPhone 16 Pro Max (18.3)](https://testingbot.com# "iPhone 16 Pro Max - 18.3")
#### 18.0
[iPhone 16 (18.0)](https://testingbot.com# "iPhone 16 - 18.0")[iPhone 16 Plus (18.0)](https://testingbot.com# "iPhone 16 Plus - 18.0")[iPhone 16 Pro (18.0)](https://testingbot.com# "iPhone 16 Pro - 18.0")[iPhone 16 Pro Max (18.0)](https://testingbot.com# "iPhone 16 Pro Max - 18.0")[iPhone 15 (18.0)](https://testingbot.com# "iPhone 15 - 18.0")[iPhone SE (3rd generation) (18.0)](https://testingbot.com# "iPhone SE (3rd generation) - 18.0")
#### 17.5
[iPhone 15 (17.5)](https://testingbot.com# "iPhone 15 - 17.5")[iPhone 15 Plus (17.5)](https://testingbot.com# "iPhone 15 Plus - 17.5")[iPhone 15 Pro (17.5)](https://testingbot.com# "iPhone 15 Pro - 17.5")[iPhone 15 Pro Max (17.5)](https://testingbot.com# "iPhone 15 Pro Max - 17.5")[iPhone SE (3rd generation) (17.5)](https://testingbot.com# "iPhone SE (3rd generation) - 17.5")
#### 17.4
[iPhone 15 (17.4)](https://testingbot.com# "iPhone 15 - 17.4")[iPhone 15 Plus (17.4)](https://testingbot.com# "iPhone 15 Plus - 17.4")[iPhone 15 Pro (17.4)](https://testingbot.com# "iPhone 15 Pro - 17.4")[iPhone 15 Pro Max (17.4)](https://testingbot.com# "iPhone 15 Pro Max - 17.4")[iPhone SE (3rd generation) (17.4)](https://testingbot.com# "iPhone SE (3rd generation) - 17.4")
#### 17.2
[iPhone 15 (17.2)](https://testingbot.com# "iPhone 15 - 17.2")[iPhone 15 Plus (17.2)](https://testingbot.com# "iPhone 15 Plus - 17.2")[iPhone 15 Pro (17.2)](https://testingbot.com# "iPhone 15 Pro - 17.2")[iPhone 15 Pro Max (17.2)](https://testingbot.com# "iPhone 15 Pro Max - 17.2")[iPhone SE (3rd generation) (17.2)](https://testingbot.com# "iPhone SE (3rd generation) - 17.2")
#### 17.0
[iPhone 15 (17.0)](https://testingbot.com# "iPhone 15 - 17.0")[iPhone 15 Plus (17.0)](https://testingbot.com# "iPhone 15 Plus - 17.0")[iPhone 15 Pro (17.0)](https://testingbot.com# "iPhone 15 Pro - 17.0")[iPhone 15 Pro Max (17.0)](https://testingbot.com# "iPhone 15 Pro Max - 17.0")[iPhone SE (3rd generation) (17.0)](https://testingbot.com# "iPhone SE (3rd generation) - 17.0")
#### 16.2
[iPhone 14 (16.2)](https://testingbot.com# "iPhone 14 - 16.2")[iPhone 14 Plus (16.2)](https://testingbot.com# "iPhone 14 Plus - 16.2")[iPhone 14 Pro (16.2)](https://testingbot.com# "iPhone 14 Pro - 16.2")[iPhone 14 Pro Max (16.2)](https://testingbot.com# "iPhone 14 Pro Max - 16.2")
#### 16.0
[iPhone 14 (16.0)](https://testingbot.com# "iPhone 14 - 16.0")[iPhone 14 Pro (16.0)](https://testingbot.com# "iPhone 14 Pro - 16.0")[iPhone 14 Pro Max (16.0)](https://testingbot.com# "iPhone 14 Pro Max - 16.0")[iPhone 14 Plus (16.0)](https://testingbot.com# "iPhone 14 Plus - 16.0")[iPhone SE (3rd generation) (16.0)](https://testingbot.com# "iPhone SE (3rd generation) - 16.0")
#### Tablets
#### 26.3
[iPad mini (A17 Pro) (26.3)](https://testingbot.com# "iPad mini (A17 Pro) - 26.3")[iPad (A16) (26.3)](https://testingbot.com# "iPad (A16) - 26.3")[iPad Air 13-inch (M3) (26.3)](https://testingbot.com# "iPad Air 13-inch (M3) - 26.3")[iPad Pro (13-inch) (M5) (26.3)](https://testingbot.com# "iPad Pro (13-inch) (M5) - 26.3")[iPad Air 11-inch (M3) (26.3)](https://testingbot.com# "iPad Air 11-inch (M3) - 26.3")[iPad Pro (11-inch) (M5) (26.3)](https://testingbot.com# "iPad Pro (11-inch) (M5) - 26.3")
#### 26.0
[iPad (A16) (26.0)](https://testingbot.com# "iPad (A16) - 26.0")[iPad Pro (13-inch) (M5) (26.0)](https://testingbot.com# "iPad Pro (13-inch) (M5) - 26.0")[iPad Air 13-inch (M3) (26.0)](https://testingbot.com# "iPad Air 13-inch (M3) - 26.0")[iPad Pro (11-inch) (M5) (26.0)](https://testingbot.com# "iPad Pro (11-inch) (M5) - 26.0")[iPad Air 11-inch (M3) (26.0)](https://testingbot.com# "iPad Air 11-inch (M3) - 26.0")
#### 18.6
[iPad Mini (A17 Pro) (18.6)](https://testingbot.com# "iPad Mini (A17 Pro) - 18.6")[iPad (A16) (18.6)](https://testingbot.com# "iPad (A16) - 18.6")[iPad Pro (13-inch) (M4) (18.6)](https://testingbot.com# "iPad Pro (13-inch) (M4) - 18.6")[iPad Air 13-inch (M3) (18.6)](https://testingbot.com# "iPad Air 13-inch (M3) - 18.6")[iPad Pro (11-inch) (M4) (18.6)](https://testingbot.com# "iPad Pro (11-inch) (M4) - 18.6")[iPad Air 11-inch (M3) (18.6)](https://testingbot.com# "iPad Air 11-inch (M3) - 18.6")
#### 18.3
[iPad (A16) (18.3)](https://testingbot.com# "iPad (A16) - 18.3")[iPad Pro (13-inch) (M4) (18.3)](https://testingbot.com# "iPad Pro (13-inch) (M4) - 18.3")[iPad Air 13-inch (M3) (18.3)](https://testingbot.com# "iPad Air 13-inch (M3) - 18.3")[iPad Pro (11-inch) (M4) (18.3)](https://testingbot.com# "iPad Pro (11-inch) (M4) - 18.3")[iPad Air 11-inch (M3) (18.3)](https://testingbot.com# "iPad Air 11-inch (M3) - 18.3")
#### 18.0
[iPad Pro (13-inch) (M4) (18.0)](https://testingbot.com# "iPad Pro (13-inch) (M4) - 18.0")[iPad Air 13-inch (M2) (18.0)](https://testingbot.com# "iPad Air 13-inch (M2) - 18.0")[iPad Air 11-inch (M2) (18.0)](https://testingbot.com# "iPad Air 11-inch (M2) - 18.0")[iPad Pro (11-inch) (M4) (18.0)](https://testingbot.com# "iPad Pro (11-inch) (M4) - 18.0")
#### 17.5
[iPad Pro (13-inch) (M4) (17.5)](https://testingbot.com# "iPad Pro (13-inch) (M4) - 17.5")[iPad Air 13-inch (M2) (17.5)](https://testingbot.com# "iPad Air 13-inch (M2) - 17.5")[iPad Pro (11-inch) (M4) (17.5)](https://testingbot.com# "iPad Pro (11-inch) (M4) - 17.5")[iPad Air 11-inch (M2) (17.5)](https://testingbot.com# "iPad Air 11-inch (M2) - 17.5")
#### 17.4
[iPad Pro (13-inch) (M4) (17.4)](https://testingbot.com# "iPad Pro (13-inch) (M4) - 17.4")[iPad Air 13-inch (M2) (17.4)](https://testingbot.com# "iPad Air 13-inch (M2) - 17.4")[iPad Pro (11-inch) (M4) (17.4)](https://testingbot.com# "iPad Pro (11-inch) (M4) - 17.4")[iPad Air 11-inch (M2) (17.4)](https://testingbot.com# "iPad Air 11-inch (M2) - 17.4")
#### 17.2
[iPad Pro (12.9-inch) (6th generation) (17.2)](https://testingbot.com# "iPad Pro (12.9-inch) (6th generation) - 17.2")[iPad Pro (11-inch) (4th generation) (17.2)](https://testingbot.com# "iPad Pro (11-inch) (4th generation) - 17.2")[iPad (10th generation) (17.2)](https://testingbot.com# "iPad (10th generation) - 17.2")[iPad mini (6th generation) (17.2)](https://testingbot.com# "iPad mini (6th generation) - 17.2")[iPad Air (5th generation) (17.2)](https://testingbot.com# "iPad Air (5th generation) - 17.2")
#### 17.0
[iPad Pro (12.9-inch) (6th generation) (17.0)](https://testingbot.com# "iPad Pro (12.9-inch) (6th generation) - 17.0")[iPad Pro (11-inch) (4th generation) (17.0)](https://testingbot.com# "iPad Pro (11-inch) (4th generation) - 17.0")[iPad (10th generation) (17.0)](https://testingbot.com# "iPad (10th generation) - 17.0")[iPad mini (6th generation) (17.0)](https://testingbot.com# "iPad mini (6th generation) - 17.0")[iPad Air (5th generation) (17.0)](https://testingbot.com# "iPad Air (5th generation) - 17.0")
#### 16.2
[iPad Pro (12.9-inch) (6th generation) (16.2)](https://testingbot.com# "iPad Pro (12.9-inch) (6th generation) - 16.2")[iPad Pro (11-inch) (4th generation) (16.2)](https://testingbot.com# "iPad Pro (11-inch) (4th generation) - 16.2")[iPad (10th generation) (16.2)](https://testingbot.com# "iPad (10th generation) - 16.2")[iPad Air (5th generation) (16.2)](https://testingbot.com# "iPad Air (5th generation) - 16.2")
#### 16.0
[iPad Pro (12.9-inch) (5th generation) (16.0)](https://testingbot.com# "iPad Pro (12.9-inch) (5th generation) - 16.0")[iPad Pro (11-inch) (3rd generation) (16.0)](https://testingbot.com# "iPad Pro (11-inch) (3rd generation) - 16.0")[iPad (9th generation) (16.0)](https://testingbot.com# "iPad (9th generation) - 16.0")[iPad Air (5th generation) (16.0)](https://testingbot.com# "iPad Air (5th generation) - 16.0")

## Android Emulators
### Phones
#### 16.0
[Pixel 9 (16.0)](https://testingbot.com# "Pixel 9 - 16.0")
#### 15.0
[Pixel 9 (15.0)](https://testingbot.com# "Pixel 9 - 15.0")
#### 14.0
[Pixel 8 (14.0)](https://testingbot.com# "Pixel 8 - 14.0")
#### 13.0
[Pixel 6a (13.0)](https://testingbot.com# "Pixel 6a - 13.0")
#### 12.0
[Pixel 6 (12.0)](https://testingbot.com# "Pixel 6 - 12.0")
#### 11.0
[Galaxy S21 (11.0)](https://testingbot.com# "Galaxy S21 - 11.0")
#### 10.0
[Pixel 3 (10.0)](https://testingbot.com# "Pixel 3 - 10.0")[Galaxy S10 (10.0)](https://testingbot.com# "Galaxy S10 - 10.0")
#### 9.0
[Galaxy S9 (9.0)](https://testingbot.com# "Galaxy S9 - 9.0")
#### 8.0
[Pixel 2 XL (8.0)](https://testingbot.com# "Pixel 2 XL - 8.0")
#### 7.1
[Pixel 2 (7.1)](https://testingbot.com# "Pixel 2 - 7.1")[Nexus 7 (7.1)](https://testingbot.com# "Nexus 7 - 7.1")
#### 6.0
[Nexus S (6.0)](https://testingbot.com# "Nexus S - 6.0")[Nexus 6 (6.0)](https://testingbot.com# "Nexus 6 - 6.0")[Galaxy S6 (6.0)](https://testingbot.com# "Galaxy S6 - 6.0")[Nexus 4 (6.0)](https://testingbot.com# "Nexus 4 - 6.0")
#### 5.0
[Galaxy S5 (5.0)](https://testingbot.com# "Galaxy S5 - 5.0")[Nexus 1 (5.0)](https://testingbot.com# "Nexus 1 - 5.0")
#### 4.4
[Galaxy S4 (4.4)](https://testingbot.com# "Galaxy S4 - 4.4")[Galaxy Nexus (4.4)](https://testingbot.com# "Galaxy Nexus - 4.4")
### Tablets
#### 15.0
[Galaxy Tab S9 (15.0)](https://testingbot.com# "Galaxy Tab S9 - 15.0")
#### 12.0
[Galaxy Tab S7 (12.0)](https://testingbot.com# "Galaxy Tab S7 - 12.0")
#### 11.0
[Galaxy Tab S6 (11.0)](https://testingbot.com# "Galaxy Tab S6 - 11.0")
#### 10.0
[Galaxy Tab S5 (10.0)](https://testingbot.com# "Galaxy Tab S5 - 10.0")
#### 9.0
[Galaxy Tab S4 (9.0)](https://testingbot.com# "Galaxy Tab S4 - 9.0")
#### 8.0
[Pixel C (8.0)](https://testingbot.com# "Pixel C - 8.0")
### Chromeos
#### 14.0
[ChromeOS Medium (14.0)](https://testingbot.com# "ChromeOS Medium - 14.0")[ChromeOS Large (14.0)](https://testingbot.com# "ChromeOS Large - 14.0")[ChromeOS Small (14.0)](https://testingbot.com# "ChromeOS Small - 14.0")
---
URL: https://testingbot.com/support/web-automate/selenium
Documentation
# Selenium Testing in the Cloud
TestingBot is a Selenium testing cloud that runs your WebDriver tests on 6100+ real desktop browsers and physical iOS and Android devices without managing local infrastructure. Point your existing Selenium scripts at our remote grid endpoint and run tests in parallel across Chrome, Firefox, Safari, Edge and Internet Explorer. Test across Windows, macOS, Linux, iOS and Android.
## Quick start: run your first Selenium test
Four steps to point an existing Selenium WebDriver test at the TestingBot cloud:
1. **Sign up and grab your API key.** [Create a free account](https://testingbot.com/users/sign_up). Your API key and secret are in the dashboard under _Account → Keys & Authentication_.
2. **Point Selenium at the TestingBot grid.** Change your WebDriver endpoint to `https://hub.testingbot.com/wd/hub`. Pass your key and secret via the `tb:options` capability.
3. **Set desired capabilities.** Pick a browser, version and platform, for example `browserName: "chrome"`, `browserVersion: "latest"`, `platformName: "WIN11"`. Add an optional `name` and `build` for grouping in the dashboard.
4. **Run your test and view results.** TestingBot streams a live video, screenshots, browser logs and Selenium command logs into the dashboard while the test runs and after it finishes.
## Pick your language or framework
Each card below links to a complete, runnable example for that language or framework, including [desired capabilities](https://testingbot.com/support/web-automate/selenium/test-options), common patterns and CI examples.
### [NodeJS](https://testingbot.com/support/web-automate/selenium/nodejs)
Get started with NodeJS testing frameworks and tools.
- [NodeJS](https://testingbot.com/support/web-automate/selenium/nodejs)
- [WebdriverIO](https://testingbot.com/support/web-automate/selenium/nodejs/webdriverio)
- [CodeceptJS](https://testingbot.com/support/web-automate/selenium/nodejs/codeceptjs)
- [Nightwatch](https://testingbot.com/support/web-automate/selenium/nodejs/nightwatch)
- [Protractor](https://testingbot.com/support/web-automate/selenium/nodejs/protractor)
- [Soda](https://testingbot.com/support/web-automate/selenium/nodejs/soda)
- [TestCafe](https://testingbot.com/support/web-automate/selenium/nodejs/testcafe)
- [Hermione](https://testingbot.com/support/web-automate/selenium/nodejs/hermione)
- [Jest](https://testingbot.com/support/web-automate/selenium/nodejs/jest)
### [Java](https://testingbot.com/support/web-automate/selenium/java)
Get started with Java testing frameworks and tools.
- [Java](https://testingbot.com/support/web-automate/selenium/java)
- [JUnit](https://testingbot.com/support/web-automate/selenium/java/junit)
- [Parallel JUnit](https://testingbot.com/support/web-automate/selenium/java/parallel-junit)
- [Selenide](https://testingbot.com/support/web-automate/selenium/java/selenide)
- [TestNG](https://testingbot.com/support/web-automate/selenium/java/testng)
- [TestNG + Cucumber](https://testingbot.com/support/web-automate/selenium/java/testng-cucumber)
### [Python](https://testingbot.com/support/web-automate/selenium/python)
Get started with Python testing frameworks and tools.
- [Python](https://testingbot.com/support/web-automate/selenium/python)
- [PyTest](https://testingbot.com/support/web-automate/selenium/python/pytest)
- [PyUnit](https://testingbot.com/support/web-automate/selenium/python/pyunit)
- [Behave](https://testingbot.com/support/web-automate/selenium/python/behave)
- [Lettuce](https://testingbot.com/support/web-automate/selenium/python/lettuce)
- [Helium](https://testingbot.com/support/web-automate/selenium/python/helium)
- [SeleniumBase](https://testingbot.com/support/web-automate/selenium/python/seleniumbase)
### [.NET](https://testingbot.com/support/web-automate/selenium/csharp)
Get started with .NET testing frameworks and tools.
- [C#](https://testingbot.com/support/web-automate/selenium/csharp)
- [NUnit](https://testingbot.com/support/web-automate/selenium/csharp/nunit)
- [Reqnroll](https://testingbot.com/support/web-automate/selenium/csharp/reqnroll)
- [xUnit](https://testingbot.com/support/web-automate/selenium/csharp/xunit)
- [PNunit](https://testingbot.com/support/web-automate/selenium/csharp/pnunit)
- [SpecFlow](https://testingbot.com/support/web-automate/selenium/csharp/specflow)
- [MSTest](https://testingbot.com/support/web-automate/selenium/csharp/mstest)
- [MbUnit](https://testingbot.com/support/web-automate/selenium/csharp/mbunit)
### [PHP](https://testingbot.com/support/web-automate/selenium/php)
Get started with PHP testing frameworks and tools.
- [PHP](https://testingbot.com/support/web-automate/selenium/php)
- [Behat](https://testingbot.com/support/web-automate/selenium/php/behat-mink)
- [Codeception](https://testingbot.com/support/web-automate/selenium/php/codeception)
- [Laravel-Dusk](https://testingbot.com/support/web-automate/selenium/php/laravel-dusk)
- [PHPUnit](https://testingbot.com/support/web-automate/selenium/php/phpunit)
- [SimpleTest](https://testingbot.com/support/web-automate/selenium/php/simpletest)
### [Ruby](https://testingbot.com/support/web-automate/selenium/ruby)
Get started with Ruby testing frameworks and tools.
- [Ruby](https://testingbot.com/support/web-automate/selenium/ruby)
- [Capybara](https://testingbot.com/support/web-automate/selenium/ruby/capybara)
- [Cucumber](https://testingbot.com/support/web-automate/selenium/ruby/cucumber)
- [Minitest](https://testingbot.com/support/web-automate/selenium/ruby/minitest)
- [RSpec](https://testingbot.com/support/web-automate/selenium/ruby/rspec)
- [Test::Unit](https://testingbot.com/support/web-automate/selenium/ruby/testunit)
- [Watir](https://testingbot.com/support/web-automate/selenium/ruby/watir)
### [JavaScript](https://testingbot.com/support/web-automate/selenium/javascript)
Get started with JavaScript testing frameworks and tools.
- [JavaScript](https://testingbot.com/support/web-automate/selenium/javascript)
- [Intern](https://testingbot.com/support/web-automate/selenium/javascript/intern)
- [Karma](https://testingbot.com/support/web-automate/selenium/javascript/karma)
### [Appium](https://testingbot.com/support/web-automate/mobile)
Get started with mobile web testing using Appium.
- [Appium Examples](https://testingbot.com/support/web-automate/mobile)

### [Robot Framework](https://testingbot.com/support/web-automate/selenium/robotframework)
Get started with Robot Framework test automation.
- [RobotFramework Examples](https://testingbot.com/support/web-automate/selenium/robotframework)
### Other
Get started with other testing tools and IDEs.
- [Selenium IDE](https://testingbot.com/support/web-automate/selenium/ide)
- [Katalon Studio](https://testingbot.com/support/web-automate/selenium/katalon-studio)
- [Oxygen](https://testingbot.com/support/web-automate/selenium/oxygen)
## Selenium vs Playwright vs Cypress for cloud testing
Selenium is the open-source standard for browser automation across the widest range of browsers and languages. Playwright and Cypress are newer frameworks with different trade-offs. All three run on TestingBot, pick the one that fits your stack:
| | Selenium | Playwright | Cypress |
| --- | --- | --- | --- |
| Browsers | Chrome, Firefox, Safari, Edge, Internet Explorer | Chromium, Firefox, WebKit | Chrome, Firefox, Edge, Electron |
| Real mobile devices | Yes (Chrome on Android, Safari on iOS) | Emulated viewport only | Emulated viewport only |
| Languages | Java, Python, C#, Ruby, JavaScript, PHP, Kotlin | JavaScript, Python, Java, .NET | JavaScript, TypeScript |
| Parallel execution | Native via the grid; concurrency by plan | Native worker-based parallelism | Parallel via cloud or Cypress Cloud |
| Protocol | W3C WebDriver (standard) | Direct CDP / WebKit protocols | Runs inside the browser as a Node process |
| Best for | Broad browser coverage, multi-language teams, real-device testing | Modern web apps, fast Chromium-first feedback | JS / TS teams wanting an integrated runner |
See dedicated guides for [Playwright on TestingBot](https://testingbot.com/support/web-automate/playwright) and [Cypress on TestingBot](https://testingbot.com/support/web-automate/cypress), or compare in depth on [Playwright vs Selenium](https://testingbot.com/playwright-vs-selenium).
## Frequently asked questions
What is a Selenium testing cloud?
A Selenium testing cloud is a hosted grid of remote browsers and devices that your Selenium WebDriver scripts connect to instead of running locally. TestingBot maintains 6100+ browser, operating system and version combinations including real iOS and Android devices, so you do not have to install drivers, browsers, or maintain a Selenium Grid yourself.
How do I run Selenium tests in the cloud with TestingBot?
Sign up for a free account, copy your API key and secret from the dashboard, and change your Selenium WebDriver endpoint to `https://hub.testingbot.com/wd/hub`. Add desired capabilities (browserName, browserVersion, platformName) and run your existing tests. No code changes are required beyond the URL and credentials.
Which browsers and versions does TestingBot support?
TestingBot supports Chrome, Firefox, Safari, Microsoft Edge and Internet Explorer on Windows, macOS and Linux, plus Chrome and Safari on real iOS and Android phones and tablets. See the [full browser list](https://testingbot.com/support/web-automate/browsers) for current versions.
Can I run Selenium tests in parallel?
Yes. TestingBot supports parallel test execution across multiple browsers and devices simultaneously. The number of concurrent tests is determined by your plan. Tests run on isolated, freshly-provisioned VMs so they never interfere with each other.
Do I need to change my Selenium tests to run on TestingBot?
No. Any test written against the standard Selenium WebDriver protocol works as-is on TestingBot. The only required changes are the remote URL (https://hub.testingbot.com/wd/hub) and your API key and secret. Your assertions, page objects and frameworks (JUnit, pytest, RSpec, Mocha, Cucumber and others) stay the same.
How is TestingBot different from running my own Selenium Grid?
With TestingBot you skip the infrastructure: no nodes to provision, no browsers or drivers to install, no version mismatches when a browser auto-updates, no flaky cleanup between tests. Every test runs on a fresh VM that is destroyed afterwards. Screenshots, video recording, network logs and a dashboard are included out of the box.
Does TestingBot support real mobile devices for Selenium testing?
Yes. In addition to desktop browsers, TestingBot runs Selenium tests against Chrome on real Android devices and Safari on real iOS devices housed in our EU data centre. See [real device testing](https://testingbot.com/real-device-testing) for details.
How much does Selenium cloud testing cost?
TestingBot offers a free trial with included minutes, plus paid plans starting from $30 per month. Pricing is based on test concurrency and total minutes. See [pricing](https://testingbot.com/pricing). Approved open-source projects can apply for a free plan.
## Next steps
- [ **Java tutorial**
Selenium WebDriver examples in Java](https://testingbot.com/support/web-automate/selenium/java)
- [ **Python tutorial**
Selenium WebDriver examples in Python](https://testingbot.com/support/web-automate/selenium/python)
- [ **Node.js tutorial**
Selenium WebDriver examples in Node.js](https://testingbot.com/support/web-automate/selenium/nodejs)
- [ **Capabilities reference**
All tb:options capabilities](https://testingbot.com/support/web-automate/selenium/test-options)
- [ **Browser list**
All 6100+ supported browser combinations](https://testingbot.com/support/web-automate/browsers)
- [ **Real device testing**
Selenium on real iOS and Android](https://testingbot.com/real-device-testing)
---
URL: https://testingbot.com/support/web-automate/selenium/annotating-tests
# Updating Tests with Selenium
You can use the TestingBot API to send various test information back to TestingBot: test-name, passed/failed state, build identifier and more.
We've added custom [JavaScript Executor](https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/JavascriptExecutor.html) commands which allow you to send back test meta-data to TestingBot, while your test is running.
Below are some examples on how to do this in various programming languages:
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[PHP](https://testingbot.com#)[C#](https://testingbot.com#)
((JavascriptExecutor)driver).executeScript("tb:test-name=My test");
((JavascriptExecutor)driver).executeScript("tb:test-result=passed");
driver.execute_script('tb:test-name=My test')
driver.execute_script('tb:test-result=passed')
await driver.executeScript('tb:test-name=My test');
await driver.executeScript('tb:test-result=passed');
driver.execute_script('tb:test-name=My test')
driver.execute_script('tb:test-result=passed')
$web_driver->executeScript('tb:test-name=My test');
$web_driver->executeScript('tb:test-result=passed');
((IJavaScriptExecutor)driver).ExecuteScript("tb:test-name=My test");
((IJavaScriptExecutor)driver).ExecuteScript("tb:test-result=passed");
## Set a test name
tb:test-name=My Test Name
Sets the test-name for the currently running test. This name will be visible on TestingBot.
## Break test
tb:break
This will add a breakpoint to the test and pause it, so that you can investigate it manually.
When you go to the test result page on TestingBot, a live session will appear so that you can debug with mouse and keyboard.
## Test Result
tb:test-result=true
Possible values: passed, true, failed, false
This will set the test as passed (`"passed"` or `true`) or failed (`"failed"` or `false`) on TestingBot.
## Test Build
tb:test-build=my build
Sets the test's build name.
## Test Tags
tb:test-tags=tag1,tag2
Comma-separated list of tags.
Sets the test's tags.
## Test Context
tb:test-context=my context
Logs the given context and prints it on the TestingBot test result page, in the Selenium command list.
## Test Info
tb:test-info={'build':'my first build', 'name':'my new test', 'public':true}
JSON-formatted dictionary with optional keys: `'build'`, `'name'`, `'public'`, `'status_message'`, `'extra'`
Pass in a JSON dictionary with various fields to update the test's meta-data on TestingBot.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/web-automate/selenium/capabilities
# Selenium Capabilities Generator
Generate Selenium capabilities for your automated tests. Choose your browser, OS and configuration options to get the exact code you need for Java, Python, NodeJS, C#, Ruby or PHP.
[Selenium ](https://testingbot.com/support/web-automate/selenium/capabilities)[Appium ](https://testingbot.com/support/app-automate/appium/capabilities)[Puppeteer ](https://testingbot.com/support/web-automate/puppeteer/capabilities)[Playwright ](https://testingbot.com/support/web-automate/playwright/capabilities)
Browser Environment
 Windows 11 ›  Chrome 139
Loading environments...
Please wait while we load the available browsers and platforms.
Resolution800x6001024x7681280x8001280x9601280x10241440x9001680x10501600x12001920x12001920x10802560x1440
Selenium Version4.41.04.40.04.39.04.38.04.37.04.36.04.35.04.34.04.33.04.32.04.31.04.30.04.29.04.28.14.28.04.27.04.26.04.25.04.24.04.23.14.23.04.22.04.21.04.20.04.19.04.18.14.18.04.17.04.16.14.16.04.15.04.14.14.14.04.13.04.12.14.12.04.11.04.10.04.9.04.8.34.8.24.8.14.8.04.7.04.6.04.5.34.5.24.5.14.5.04.4.04.3.04.2.24.2.14.2.04.1.34.1.24.1.14.1.04.0.04.0.0-beta-44.0.0-beta-34.0.0-beta-24.0.0-beta-14.0.0-alpha-74.0.0-alpha-63.141.593.141.53.141.03.14.03.13.03.12.03.11.03.10.03.9.03.8.13.8.03.7.13.7.03.6.03.5.33.5.23.5.13.5.03.4.03.3.13.3.03.2.03.1.03.0.12.53.12.53.02.52.02.51.02.50.02.49.02.48.22.48.12.48.0
## Project Capabilities
Test NameAllows you to customize the name of the test, as it will appear in the dashboard.
Build NameGroup tests under the same build identifier for easy retrieval.
## Debugging
Record Video
YesNo
Take Screenshot After Each Step
YesNo
Collect Logs
YesNo
## Customization
Headless ModeRun the browser without rendering UI for faster test execution.
YesNo
Geolocation TestingRun the test from a different geographical location.NoneRandom CountryAustraliaBelgiumBrazilCanadaChileFranceGermanyIndiaItalyJapanNorwaySingaporeSouth AfricaSwedenSwitzerlandUnited Arab EmiratesUnited KingdomUnited States
Timezone ([list of timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones))
Prerun Script ([documentation](https://testingbot.com/support/web-automate/selenium/test-options#prerun))
Upload URLs
Looking for more options? See [all Selenium capabilities](https://testingbot.com/support/web-automate/selenium/test-options).
W3C Protocol
W3C is the newest WebDriver protocol, TestingBot recommends using W3C.
JSONWP
JSONWP is the legacy protocol which is no longer actively maintained.
JavaPythonNodeJSC#RubyPHP
MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability(CapabilityType.BROWSER_NAME, "chrome");
capabilities.setCapability(CapabilityType.BROWSER_VERSION, "latest");
capabilities.setCapability(CapabilityType.PLATFORM_NAME, "Windows 11");
---
URL: https://testingbot.com/support/web-automate/selenium/performance
# Performance Testing Documentation
With this call, you can fetch various performance metrics from the browser, during your test.
With this functionality, you can create tests where you monitor specific pageload time, or any other metric.
If the metric is below a specific threshold that you define, you can make the test fail.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[PHP](https://testingbot.com#)[C#](https://testingbot.com#)
((JavascriptExecutor) driver).executeScript("tb:performance");
driver.execute_script("tb:performance")
await driver.executeScript('tb:performance');
driver.execute_script("tb:performance")
$driver->executeScript("tb:performance");
((IJavaScriptExecutor)driver).ExecuteScript("tb:performance");
## Response Metrics
| `pageSize (bytes)` | The size (in bytes) of the page (HTML source). |
| `nrRequests` | How many requests the page made (AJAX/images/...) |
| `load (ms)` | The pageload time of the webpage.
The time it takes for a page to download and display the entire web page.
|
| `domContentLoaded (ms)` | The domContentLoaded duration of the page.
The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.
[DOMContentLoaded Info](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) |
| `firstMeaningfulPaint (ms)` |
Indicates how fast your page displays its primary content.
[firstMeaningfulPaint Info](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint) |
| `firstPaint (ms)` | First Paint is triggered when a render (any render) is detected in the browser. |
| `firstContentfulPaint (ms)` |
First Contentful Paint measures the time from navigation to the time when the browser renders the first bit of content from the DOM.
[firstContentfulPaint Info](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint) |
### Example Response:
{
"pageSize": 396800,
"nrRequests": 20,
"load": 1378,
"domContentLoaded": 585,
"firstMeaningfulPaint": 0,
"firstPaint": 348,
"firstContentfulPaint": 348
}
## Sample Use Case
This example demonstrates a test that visits a page, checks the performance and fails the test if the performance is not sufficient.
require "selenium-webdriver"
options = Selenium::WebDriver::Chrome::Options.new
options.add_option("platformName", "WIN11")
options.add_option("browserVersion", "latest")
options.add_option("tb:options", {
key: "API_KEY",
secret: "API_SECRET",
name: "Performance Test"
})
driver = Selenium::WebDriver.for :remote,
url: "https://hub.testingbot.com/wd/hub",
options: options
driver.navigate.to "https://testingbot.com/"
performance = driver.execute_script("tb:performance")
if performance["load"] > 900
puts "Performance test failed: load time #{performance["load"]}ms exceeds 900ms threshold"
end
driver.quit
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/web-automate/selenium/uploads
# Uploading with Selenium
With Selenium WebDriver it's possible to test file uploads on remote browsers and devices. There are several ways to handle file uploads depending on your use case:
- [Upload a file before your test starts](https://testingbot.com#before), pre-load a file onto the test VM via a URL
- [Upload a file during your test](https://testingbot.com#during), send a local file to the remote VM with `LocalFileDetector`
- [Upload with WebdriverIO](https://testingbot.com#webdriverio), file upload examples for the WebdriverIO framework
- [TestingBot Storage](https://testingbot.com#storage), use TestingBot's internal storage for faster file transfers
- [Tips & Best Practices](https://testingbot.com#tips), common pitfalls and recommendations
## Which method should I use?
- **Use [Upload Before Test](https://testingbot.com#before)** if you have a fixed test file at a public URL and want it ready on the VM before your test runs.
- **Use [Upload During Test](https://testingbot.com#during)** if your test needs to interact with a file upload UI and send a file from your local machine to the remote browser.
- **Use [TestingBot Storage](https://testingbot.com#storage)** if you have large files, private files, or need fast and consistent transfers across multiple test runs.
## 1. Upload a file before your test starts
With TestingBot you can specify a URL in your capabilities which links to the file you want to be present on our test VMs. TestingBot will download the file and save it on the test VM before your test begins, so it's ready to use immediately.
Use the following `tb:options` capabilities:
`upload`The URL of the file to download onto the test VM. This can be a public URL or a `tb://` URL from [TestingBot Storage](https://testingbot.com#storage).`uploadFilepath`The absolute file path on the test VM where TestingBot should save the downloaded file (e.g. `C:\test\logo.png` on Windows).[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[PHP](https://testingbot.com#)[C#](https://testingbot.com#)
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class JavaSample {
public static void main(String[] args) throws Exception {
ChromeOptions options = new ChromeOptions();
options.setPlatformName("WIN11");
options.setBrowserVersion("latest");
Map tbOptions = new HashMap<>();
tbOptions.put("key", "API_KEY");
tbOptions.put("secret", "API_SECRET");
tbOptions.put("name", "Test File Upload");
tbOptions.put("upload", "https://testingbot.com/assets/logo-head.png");
tbOptions.put("uploadFilepath", "C:\\test\\logo.png");
options.setCapability("tb:options", tbOptions);
RemoteWebDriver driver = new RemoteWebDriver(new URL("https://hub.testingbot.com/wd/hub"), options);
// No LocalFileDetector needed, file is already on the VM
driver.get("http://the-internet.herokuapp.com/upload");
driver.findElement(By.id("file-upload")).sendKeys("C:\\test\\logo.png");
driver.findElement(By.id("file-submit")).click();
driver.quit();
}
}
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
options = Options()
options.set_capability('platformName', 'WIN11')
options.set_capability('browserVersion', 'latest')
options.set_capability('tb:options', {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Test File Upload',
'upload': 'https://testingbot.com/assets/logo-head.png',
'uploadFilepath': 'C:\\test\\logo.png'
})
driver = webdriver.Remote(
command_executor='https://hub.testingbot.com/wd/hub',
options=options)
driver.get('http://the-internet.herokuapp.com/upload')
driver.find_element(By.ID, 'file-upload').send_keys('C:\\test\\logo.png')
driver.find_element(By.ID, 'file-submit').click()
driver.quit()
const { Builder, By } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
async function runTest() {
const options = new chrome.Options();
options.set('platformName', 'WIN11');
options.set('browserVersion', 'latest');
options.set('tb:options', {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Test File Upload',
'upload': 'https://testingbot.com/assets/logo-head.png',
'uploadFilepath': 'C:\\test\\logo.png'
});
const driver = await new Builder()
.usingServer('https://hub.testingbot.com/wd/hub')
.setChromeOptions(options)
.build();
// No FileDetector needed, file is already on the VM
try {
await driver.get('http://the-internet.herokuapp.com/upload');
await driver.findElement(By.id('file-upload')).sendKeys('C:\\test\\logo.png');
await driver.findElement(By.id('file-submit')).click();
console.log(await driver.getTitle());
} finally {
await driver.quit();
}
}
runTest();
require 'rubygems'
require 'test/unit'
require 'selenium-webdriver'
class UploadTest < Test::Unit::TestCase
def setup
options = Selenium::WebDriver::Chrome::Options.new
options.add_option('platformName', 'WIN11')
options.add_option('browserVersion', 'latest')
options.add_option('tb:options', {
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'Test File Upload',
'upload' => 'https://testingbot.com/assets/logo-head.png',
'uploadFilepath' => 'C:\\test\\logo.png'
})
@driver = Selenium::WebDriver.for(
:remote,
url: 'https://hub.testingbot.com/wd/hub',
options: options
)
end
def test_upload
@driver.navigate.to 'http://the-internet.herokuapp.com/upload'
element = @driver.find_element(:id, 'file-upload')
element.send_keys 'C:\\test\\logo.png'
@driver.find_element(:id, 'file-submit').click
assert_equal 'logo.png', @driver.find_element(:id, 'uploaded-files').text
end
def teardown
@driver.quit
end
end
setCapability('platformName', 'WIN11');
$capabilities->setCapability('browserVersion', 'latest');
$capabilities->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'Test File Upload',
'upload' => 'https://testingbot.com/assets/logo-head.png',
'uploadFilepath' => 'C:\\test\\logo.png'
]);
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
$driver = RemoteWebDriver::create(
'https://hub.testingbot.com/wd/hub',
$capabilities
);
$driver->get('http://the-internet.herokuapp.com/upload');
// No LocalFileDetector needed, file is already on the VM
$file_input = $driver->findElement(WebDriverBy::id('file-upload'));
$file_input->sendKeys('C:\\test\\logo.png');
$driver->findElement(WebDriverBy::id('file-submit'))->click();
$driver->quit();
?>
using System;
using System.Collections.Generic;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
namespace SeleniumTest
{
class Program
{
static void Main(string[] args)
{
ChromeOptions options = new ChromeOptions();
options.PlatformName = "WIN11";
options.BrowserVersion = "latest";
var tbOptions = new Dictionary
{
["key"] = "API_KEY",
["secret"] = "API_SECRET",
["name"] = "Test File Upload",
["upload"] = "https://testingbot.com/assets/logo-head.png",
["uploadFilepath"] = "C:\\test\\logo.png"
};
options.AddAdditionalOption("tb:options", tbOptions);
// No LocalFileDetector needed, file is already on the VM
IWebDriver driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"), options);
driver.Navigate().GoToUrl("http://the-internet.herokuapp.com/upload");
IWebElement upload = driver.FindElement(By.Id("file-upload"));
upload.SendKeys("C:\\test\\logo.png");
driver.FindElement(By.Id("file-submit")).Click();
driver.Quit();
}
}
}
## 2. Upload a file during your test
You can use `LocalFileDetector` to upload a file from your local computer to the remote VM/device during your test. The file is automatically transferred over the WebDriver protocol before being sent to the file input element.
**Important:** You must set `LocalFileDetector` on the driver **before** calling `sendKeys` on the file input. Without it, Selenium will try to find the file path on the remote machine instead of your local machine.
In the example below, we upload an image residing on your own computer (`/Users/test/logo.png`) via the remote VM/device in the TestingBot cloud to a website.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[PHP](https://testingbot.com#)[C#](https://testingbot.com#)
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.LocalFileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class JavaSample {
public static void main(String[] args) throws Exception {
ChromeOptions options = new ChromeOptions();
options.setPlatformName("WIN11");
options.setBrowserVersion("latest");
Map tbOptions = new HashMap<>();
tbOptions.put("key", "API_KEY");
tbOptions.put("secret", "API_SECRET");
tbOptions.put("name", "Test File Upload");
options.setCapability("tb:options", tbOptions);
RemoteWebDriver driver = new RemoteWebDriver(new URL("https://hub.testingbot.com/wd/hub"), options);
// Must set LocalFileDetector before uploading
driver.setFileDetector(new LocalFileDetector());
driver.get("http://the-internet.herokuapp.com/upload");
driver.findElement(By.id("file-upload")).sendKeys("/Users/test/logo.png");
driver.findElement(By.id("file-submit")).click();
driver.quit();
}
}
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
options = Options()
options.set_capability('platformName', 'WIN11')
options.set_capability('browserVersion', 'latest')
options.set_capability('tb:options', {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Test File Upload'
})
driver = webdriver.Remote(
command_executor='https://hub.testingbot.com/wd/hub',
options=options)
# Python's Remote WebDriver uses LocalFileDetector by default
driver.get('http://the-internet.herokuapp.com/upload')
driver.find_element(By.ID, 'file-upload').send_keys('/Users/test/logo.png')
driver.find_element(By.ID, 'file-submit').click()
driver.quit()
const { Builder, By } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const remote = require('selenium-webdriver/remote');
async function runTest() {
const options = new chrome.Options();
options.set('platformName', 'WIN11');
options.set('browserVersion', 'latest');
options.set('tb:options', {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Test File Upload'
});
const driver = await new Builder()
.usingServer('https://hub.testingbot.com/wd/hub')
.setChromeOptions(options)
.build();
// Must set FileDetector for remote file uploads
driver.setFileDetector(new remote.FileDetector());
try {
await driver.get('http://the-internet.herokuapp.com/upload');
await driver.findElement(By.id('file-upload')).sendKeys('/Users/test/logo.png');
await driver.findElement(By.id('file-submit')).click();
console.log(await driver.getTitle());
} finally {
await driver.quit();
}
}
runTest();
require 'rubygems'
require 'test/unit'
require 'selenium-webdriver'
class UploadTest < Test::Unit::TestCase
def setup
options = Selenium::WebDriver::Chrome::Options.new
options.add_option('platformName', 'WIN11')
options.add_option('browserVersion', 'latest')
options.add_option('tb:options', {
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'Test File Upload'
})
@driver = Selenium::WebDriver.for(
:remote,
url: 'https://hub.testingbot.com/wd/hub',
options: options
)
# Ruby's Remote WebDriver uses LocalFileDetector by default
end
def test_upload
@driver.navigate.to 'http://the-internet.herokuapp.com/upload'
element = @driver.find_element(:id, 'file-upload')
element.send_keys '/Users/test/logo.png'
@driver.find_element(:id, 'file-submit').click
assert_equal 'logo.png', @driver.find_element(:id, 'uploaded-files').text
end
def teardown
@driver.quit
end
end
setCapability('platformName', 'WIN11');
$capabilities->setCapability('browserVersion', 'latest');
$capabilities->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'Test File Upload'
]);
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
$driver = RemoteWebDriver::create(
'https://hub.testingbot.com/wd/hub',
$capabilities
);
// Must set LocalFileDetector on the driver before uploading
$driver->setFileDetector(new LocalFileDetector());
$driver->get('http://the-internet.herokuapp.com/upload');
$file_input = $driver->findElement(WebDriverBy::id('file-upload'));
$file_input->sendKeys('/Users/test/logo.png');
$driver->findElement(WebDriverBy::id('file-submit'))->click();
$driver->quit();
?>
using System;
using System.Collections.Generic;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
namespace SeleniumTest
{
class Program
{
static void Main(string[] args)
{
ChromeOptions options = new ChromeOptions();
options.PlatformName = "WIN11";
options.BrowserVersion = "latest";
var tbOptions = new Dictionary
{
["key"] = "API_KEY",
["secret"] = "API_SECRET",
["name"] = "Test File Upload"
};
options.AddAdditionalOption("tb:options", tbOptions);
IWebDriver driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"), options);
// Must set LocalFileDetector before uploading
var allowsDetection = driver as IAllowsFileDetection;
allowsDetection.FileDetector = new LocalFileDetector();
driver.Navigate().GoToUrl("http://the-internet.herokuapp.com/upload");
IWebElement upload = driver.FindElement(By.Id("file-upload"));
upload.SendKeys("/Users/test/logo.png");
driver.FindElement(By.Id("file-submit")).Click();
driver.Quit();
}
}
}
## 3. Upload with WebdriverIO
WebdriverIO handles file uploads differently from raw Selenium. It provides a built-in `uploadFile` command on the browser object that transfers a local file to the remote machine and returns the remote path.
### Upload a file before your test starts
You can use the `upload` and `uploadFilepath` capabilities with WebdriverIO, just like with Selenium.
// wdio.conf.js
exports.config = {
user: 'API_KEY',
key: 'API_SECRET',
capabilities: [{
browserName: 'chrome',
browserVersion: 'latest',
platformName: 'WIN11',
'tb:options': {
name: 'WebdriverIO File Upload',
upload: 'https://testingbot.com/assets/logo-head.png',
uploadFilepath: 'C:\\test\\logo.png'
}
}],
hostname: 'hub.testingbot.com',
port: 443,
path: '/wd/hub',
protocol: 'https'
};
Then in your test, use the pre-loaded file path:
describe('File Upload', () => {
it('should upload a pre-loaded file', async () => {
await browser.url('http://the-internet.herokuapp.com/upload');
const fileInput = await $('#file-upload');
await fileInput.setValue('C:\\test\\logo.png');
await $('#file-submit').click();
const uploadedFile = await $('#uploaded-files');
await expect(uploadedFile).toHaveText('logo.png');
});
});
### Upload a local file during your test
WebdriverIO's `browser.uploadFile()` method transfers a file from your local machine to the remote VM and returns the remote file path. Use this path with `setValue()` on the file input.
describe('File Upload', () => {
it('should upload a local file during the test', async () => {
// Transfer file to remote machine, returns remote path
const remotePath = await browser.uploadFile('/Users/test/logo.png');
await browser.url('http://the-internet.herokuapp.com/upload');
const fileInput = await $('#file-upload');
await fileInput.setValue(remotePath);
await $('#file-submit').click();
const uploadedFile = await $('#uploaded-files');
await expect(uploadedFile).toHaveText('logo.png');
});
});
The `browser.uploadFile()` command is only available when running tests on a remote Selenium grid (like TestingBot). It will not work when running tests locally.
## 4. TestingBot Storage
With [TestingBot Storage](https://testingbot.com/support/api#upload), you can upload your files to our servers ahead of time. The advantage is that our test VMs can immediately download your file from our own network, which is much faster than downloading from the public internet.
TestingBot Storage is recommended for large files or when you need consistent upload speeds across test runs. Files are stored securely and available for 60 days.
### Step 1: Upload your file to TestingBot Storage
First, upload your file to TestingBot Storage using the [REST API](https://testingbot.com/support/api#upload). The API returns a `tb://` URL that you'll use in your test capabilities.
#### Upload with curl
curl -X POST "https://api.testingbot.com/v1/storage" \
-u API_KEY:API_SECRET \
-F "file=@/path/to/your/file.png"
The API responds with a JSON object containing the `tb://` URL:
{"app_url": "tb://398eijf83i"}
You can also upload a file from a remote URL instead of a local file:
curl -X POST "https://api.testingbot.com/v1/storage" \
-u API_KEY:API_SECRET \
-d "url=https://example.com/myfile.png"
#### Upload with an API client
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[PHP](https://testingbot.com#)
import com.testingbot.TestingbotREST;
import com.testingbot.models.TestingbotStorageUploadResponse;
import java.io.File;
TestingbotREST restApi = new TestingbotREST(
"API_KEY",
"API_SECRET");
// Upload a local file
TestingbotStorageUploadResponse response = restApi.uploadToStorage(
new File("/path/to/your/file.png"));
// response.getAppUrl() returns "tb://398eijf83i"
String tbUrl = response.getAppUrl();
import testingbotclient
tb = testingbotclient.TestingBotClient(
'API_KEY',
'API_SECRET')
# Upload a local file
response = tb.storage.upload_local_file('/path/to/your/file.png')
# response['app_url'] returns "tb://398eijf83i"
tb_url = response['app_url']
const TestingBot = require('testingbot-api');
const api = new TestingBot({
api_key: 'API_KEY',
api_secret: 'API_SECRET'
});
// Upload a local file
api.uploadFile('/path/to/your/file.png', function(error, response) {
// response.app_url returns "tb://398eijf83i"
console.log(response.app_url);
});
require 'testingbot'
api = TestingBot::Api.new(
'API_KEY',
'API_SECRET')
# Upload a local file
response = api.upload_local_file('/path/to/your/file.png')
# response['app_url'] returns "tb://398eijf83i"
tb_url = response['app_url']
$api = new TestingBot\TestingBotAPI(
'API_KEY',
'API_SECRET');
// Upload a local file
$response = $api->uploadLocalFileToStorage('/path/to/your/file.png');
// $response['app_url'] returns "tb://398eijf83i"
$tbUrl = $response['app_url'];
### Step 2: Use the `tb://` URL in your test
Once you have the `tb://` URL, use it as the `upload` value in your `tb:options` capabilities. TestingBot will fetch the file from internal storage and place it at the `uploadFilepath` location on the test VM.
[Java](https://testingbot.com#)[Python](https://testingbot.com#)[NodeJS](https://testingbot.com#)[Ruby](https://testingbot.com#)[PHP](https://testingbot.com#)[C#](https://testingbot.com#)[WebdriverIO](https://testingbot.com#)
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class StorageUploadTest {
public static void main(String[] args) throws Exception {
ChromeOptions options = new ChromeOptions();
options.setPlatformName("WIN11");
options.setBrowserVersion("latest");
Map tbOptions = new HashMap<>();
tbOptions.put("key", "API_KEY");
tbOptions.put("secret", "API_SECRET");
tbOptions.put("name", "Test File Upload via Storage");
// Use the tb:// URL from Step 1
tbOptions.put("upload", "tb://398eijf83i");
tbOptions.put("uploadFilepath", "C:\\test\\file.png");
options.setCapability("tb:options", tbOptions);
RemoteWebDriver driver = new RemoteWebDriver(
new URL("https://hub.testingbot.com/wd/hub"), options);
driver.get("http://the-internet.herokuapp.com/upload");
driver.findElement(By.id("file-upload")).sendKeys("C:\\test\\file.png");
driver.findElement(By.id("file-submit")).click();
driver.quit();
}
}
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
options = Options()
options.set_capability('platformName', 'WIN11')
options.set_capability('browserVersion', 'latest')
options.set_capability('tb:options', {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Test File Upload via Storage',
# Use the tb:// URL from Step 1
'upload': 'tb://398eijf83i',
'uploadFilepath': 'C:\\test\\file.png'
})
driver = webdriver.Remote(
command_executor='https://hub.testingbot.com/wd/hub',
options=options)
driver.get('http://the-internet.herokuapp.com/upload')
driver.find_element(By.ID, 'file-upload').send_keys('C:\\test\\file.png')
driver.find_element(By.ID, 'file-submit').click()
driver.quit()
const { Builder, By } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
async function runTest() {
const options = new chrome.Options();
options.set('platformName', 'WIN11');
options.set('browserVersion', 'latest');
options.set('tb:options', {
'key': 'API_KEY',
'secret': 'API_SECRET',
'name': 'Test File Upload via Storage',
// Use the tb:// URL from Step 1
'upload': 'tb://398eijf83i',
'uploadFilepath': 'C:\\test\\file.png'
});
const driver = await new Builder()
.usingServer('https://hub.testingbot.com/wd/hub')
.setChromeOptions(options)
.build();
try {
await driver.get('http://the-internet.herokuapp.com/upload');
await driver.findElement(By.id('file-upload')).sendKeys('C:\\test\\file.png');
await driver.findElement(By.id('file-submit')).click();
} finally {
await driver.quit();
}
}
runTest();
require 'rubygems'
require 'test/unit'
require 'selenium-webdriver'
class UploadTest < Test::Unit::TestCase
def setup
options = Selenium::WebDriver::Chrome::Options.new
options.add_option('platformName', 'WIN11')
options.add_option('browserVersion', 'latest')
options.add_option('tb:options', {
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'Test File Upload via Storage',
# Use the tb:// URL from Step 1
'upload' => 'tb://398eijf83i',
'uploadFilepath' => 'C:\\test\\file.png'
})
@driver = Selenium::WebDriver.for(
:remote,
url: 'https://hub.testingbot.com/wd/hub',
options: options
)
end
def test_upload
@driver.navigate.to 'http://the-internet.herokuapp.com/upload'
element = @driver.find_element(:id, 'file-upload')
element.send_keys 'C:\\test\\file.png'
@driver.find_element(:id, 'file-submit').click
assert_equal 'file.png', @driver.find_element(:id, 'uploaded-files').text
end
def teardown
@driver.quit
end
end
setCapability('platformName', 'WIN11');
$capabilities->setCapability('browserVersion', 'latest');
// Use the tb:// URL from Step 1
$capabilities->setCapability('tb:options', [
'key' => 'API_KEY',
'secret' => 'API_SECRET',
'name' => 'Test File Upload via Storage',
'upload' => 'tb://398eijf83i',
'uploadFilepath' => 'C:\\test\\file.png'
]);
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
$driver = RemoteWebDriver::create(
'https://hub.testingbot.com/wd/hub',
$capabilities
);
$driver->get('http://the-internet.herokuapp.com/upload');
$driver->findElement(WebDriverBy::id('file-upload'))->sendKeys('C:\\test\\file.png');
$driver->findElement(WebDriverBy::id('file-submit'))->click();
$driver->quit();
?>
using System;
using System.Collections.Generic;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
namespace SeleniumTest
{
class Program
{
static void Main(string[] args)
{
ChromeOptions options = new ChromeOptions();
options.PlatformName = "WIN11";
options.BrowserVersion = "latest";
var tbOptions = new Dictionary
{
["key"] = "API_KEY",
["secret"] = "API_SECRET",
["name"] = "Test File Upload via Storage",
// Use the tb:// URL from Step 1
["upload"] = "tb://398eijf83i",
["uploadFilepath"] = "C:\\test\\file.png"
};
options.AddAdditionalOption("tb:options", tbOptions);
IWebDriver driver = new RemoteWebDriver(
new Uri("https://hub.testingbot.com/wd/hub"), options);
driver.Navigate().GoToUrl("http://the-internet.herokuapp.com/upload");
driver.FindElement(By.Id("file-upload")).SendKeys("C:\\test\\file.png");
driver.FindElement(By.Id("file-submit")).Click();
driver.Quit();
}
}
}
// wdio.conf.js
exports.config = {
user: 'API_KEY',
key: 'API_SECRET',
capabilities: [{
browserName: 'chrome',
browserVersion: 'latest',
platformName: 'WIN11',
'tb:options': {
name: 'Test File Upload via Storage',
// Use the tb:// URL from Step 1
upload: 'tb://398eijf83i',
uploadFilepath: 'C:\\test\\file.png'
}
}],
hostname: 'hub.testingbot.com',
port: 443,
path: '/wd/hub',
protocol: 'https'
};
## Tips & Best Practices
### Always set LocalFileDetector for remote uploads
When running tests on a remote grid, `sendKeys` on a file input will look for the file on the **remote** machine by default. Setting `LocalFileDetector` tells Selenium to transfer the file from your local machine first.
Forgetting to set `LocalFileDetector` is the most common cause of file upload failures on remote grids. The error message may not clearly indicate this, you'll typically see a "file not found" error on the remote machine.
### Handle hidden file inputs
Many modern web applications hide the native `` element and use a styled button or drag-and-drop zone instead. You can still use `sendKeys` on hidden file inputs, Selenium allows interaction with hidden file input elements.
// The file input may be hidden, but sendKeys still works
WebElement fileInput = driver.findElement(By.cssSelector("input[type='file']"));
fileInput.sendKeys("/path/to/file.png");
### Wait for the upload to complete
After triggering a file upload, always wait for a confirmation element or success message before proceeding. File transfers take time, especially for larger files.
// Wait for a success indicator after uploading
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
wait.until(ExpectedConditions.visibilityOfElementLocated(
By.id("uploaded-files")));
### Use absolute file paths
Always use absolute file paths (e.g. `/Users/test/logo.png` or `C:\test\logo.png`) instead of relative paths. Relative paths may resolve differently on the remote machine and your local machine.
### Upload multiple files
If the file input accepts multiple files (has the `multiple` attribute), you can upload several files by sending all paths separated by a newline character (`\n`).
# Upload multiple files at once
file_input = driver.find_element(By.CSS_SELECTOR, 'input[type="file"]')
file_input.send_keys('/Users/test/file1.png\n/Users/test/file2.png')
## Frequently Asked Questions
### Why does my file upload fail with "file not found" on the remote machine?
This happens when `LocalFileDetector` is not set. Without it, Selenium sends the local file path as a string to the remote machine, which then tries to find the file at that path locally. Set `LocalFileDetector` on the driver before calling `sendKeys`. Python and Ruby remote drivers use `LocalFileDetector` by default.
### Can I upload a file to a drag-and-drop upload zone?
Drag-and-drop upload zones typically have a hidden `` element underneath. Locate this hidden input using `By.cssSelector("input[type='file']")` and call `sendKeys` on it directly. This bypasses the drag-and-drop interface and triggers the same upload behavior.
### What is the difference between uploading before and during a test?
Uploading **before** the test (via the `upload` capability) downloads the file from a URL and places it on the VM before your test starts. This is useful for test fixtures and large files. Uploading **during** the test (via `LocalFileDetector`) transfers a file from your local machine through the WebDriver protocol as part of the test flow. Use this when you need to test the actual upload UI interaction.
### How do I upload files with WebdriverIO on TestingBot?
WebdriverIO provides a built-in `browser.uploadFile()` command that transfers a local file to the remote machine and returns the remote path. Use this path with `setValue()` on the file input element. Alternatively, use the `upload` and `uploadFilepath` capabilities in your `wdio.conf.js` to pre-load files before the test. See the [WebdriverIO section](https://testingbot.com#webdriverio) for full examples.
### Looking for more help?
Have questions or need more information? Reach out via email or Slack.
[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
---
URL: https://testingbot.com/support/web-automate/selenium/test-options
# Selenium Test Options
This page offers an overview of all the various options you can specify when starting an automated Selenium test.
The **Selenium-Specific Settings** are required to run automated Selenium tests: `browserName`, `browserVersion` and `platformName`.
The other options are **TestingBot Options** , these allow you to customize your test in terms of specific driver versions, privacy options, platform options and more.
The [Selenium Capabilities generator](https://testingbot.com/support/web-automate/selenium/capabilities) allows you to easily generate the necessary capabilities for your tests.
## Required Selenium Settings
### Browser Name
The name of the browser to run your automated test on. Please see our [list of browsers](https://testingbot.com/support/web-automate/browsers) that we support.
The `browserName` is a required field that needs to be passed to us via Selenium's [Desired Capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["browserName"] = "chrome"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("browserName", "chrome");
$caps = array(
"browserName" => "chrome"
);
capabilities = {
"browserName" : "chrome"
}
const capabilities = {
"browserName" : "chrome"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("browserName", "chrome");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
browserName: "chrome"
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'browserName': "chrome",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
### Browser Version
The version of the browser to run your automated test on. Please see our [list of browsers + versions](https://testingbot.com/support/web-automate/browsers) that we support.
The `version` is a required field that needs to be passed to us via Selenium's [Desired Capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["version"] = "81"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("version", "81");
$caps = array(
"version" => "81"
);
capabilities = {
"version" : "81"
}
const capabilities = {
"version" : "81"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("version", "81");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
browserName: "chrome",
browserVersion: "latest"
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("browserVersion", "latest");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setVersion(81);
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'browserName': "chrome",
'browserVersion': 'latest',
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"browserVersion": "latest",
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Other Options:
- `"version": "*"` : If you use `*` as version, TestingBot will pick a random version.
- `"version": "latest"` : TestingBot will automatically take the latest version. You can also use `latest-1`, `latest-2`, ... to test on the next most recent versions. For example, if the current latest Firefox version is 81 and you use `latest-2`, then the test will run on Firefox 79.
- `"version": "<=16"` : TestingBot will pick a version smaller than or equal to the version you specify with `<=`.
`"version": "16>="` : TestingBot will pick a version higher than or equal to the version you specify with `>=`.
### Platform
Indicates on which operating system the test should run. Please see a [list of platforms](https://testingbot.com/support/web-automate/browsers) that we currently support.
The `platform` is a required field that needs to be passed to us via Selenium's [Desired Capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["platform"] = "WIN11"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platform", "WIN11");
$caps = array(
"platform" => "WIN11"
);
capabilities = {
"platform" : "WIN11"
}
const capabilities = {
"platform" : "WIN11"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("platform", "WIN11");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platform_name: "WIN11"
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
### Device Name
When running a mobile automated test, you'll need to specify on which [mobile device](https://testingbot.com/support/app-automate/devices) (or [simulator/emulator](https://testingbot.com/support/web-automate/browsers)) you want to test.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
deviceName: "Pixel 9"
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("appium:deviceName", "Pixel 9");
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'deviceName': "Pixel 9",
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"appium:deviceName": 'Pixel 9',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name,
["appium:deviceName"] = "Pixel 9"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
We offer regex/wildcard parameters which you can use to allocate a device:
| Regex Input | Result |
| --- | --- |
| `"iPhone.*"` | This will allocate any available iPhone device (phone) |
| `".*Galaxy.*"` | This will allocate any of the available Galaxy devices (phone or tablet) |
| `"*"` | This will allocate a random available device, either iOS or Android device |
| `"iPhone [8-11]"` | This will allocate either an iPhone 8 or 11 |
| `"iPhone 6.*"` | This will allocate either an iPhone 6 or 6S |
Some Examples:
// find any iPhone, except 6 or 6s
capabilities.setCapability("appium:deviceName", "^(iPhone.*)(?!6|6S)$");
// find any device which name starts with Galaxy
capabilities.setCapability("appium:deviceName", "Galaxy.*");
### Device Version
You can specify the version of the device you want to target. In case of physical devices, you might want to use regex/wildcard patterns to target a broad range of devices. This way your chance of hitting an occupied device during your automated test decreases significantly.
Instead of only targeting a specific iOS or Android device, you can target a wider range of devices.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
version: "(16|17).*"
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("appium:platformName", "Android");
caps.setCapability("appium:deviceName", "*");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("appium:version", "(13|14).*");
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'appium:deviceName': "*",
'appium:version': '(13|14).*',
'platformName': "Android",
'goog:chromeOptions': {'w3c': True}
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"appium:deviceName": '*',
"appium:version": '(13|14).*',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
PlatformName = "Android"
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name,
["deviceName"] = "*",
["version"] = "(13|14).*"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
Some Examples:
// target multiple specific versions of an iPhone device
capabilities.setCapability("appium:deviceName", "iPhone.*");
capabilities.setCapability("appium:version", "15.0|14.2|14|13.4|13.3");
// find any Galaxy device with Android version 13 or 14
capabilities.setCapability("appium:deviceName", "Galaxy.*");
capabilities.setCapability("appium:version", "(13|14).*");
### Tablet Only
You can specify this capability when you only want to allocate a tablet device.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
deviceName: "*",
tabletOnly: true
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("tabletOnly", true);
tbOptions.setCapability("deviceName", "*");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'deviceName': "*",
'tabletOnly': true,
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"deviceName": '*',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"tabletOnly": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name,
["deviceName"] = "*",
["tabletOnly"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
### Phone Only
You can specify this capability when you only want to allocate a phone device.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
deviceName: "*",
phoneOnly: true
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("phoneOnly", true);
tbOptions.setCapability("deviceName", "*");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "Android");
caps.setCapability("tb:options", tbOptions);
tbOptions = {
'name': 'W3C Sample'
}
chromeOpts = {
'deviceName': "*",
'phoneOnly': true,
'platformName': "Android",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"deviceName": '*',
"platformName": 'Android',
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"phoneOnly": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
PlatformName = "Android",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["name"] = TestContext.CurrentContext.Test.Name,
["deviceName"] = "*",
["phoneOnly"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
### Use a specific version of Selenium
By default we use Selenium version `2.53.1` to run your test.
If you wish to use another Selenium version for your test, please specify one of the following available versions:
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["selenium-version"] = '2.53.1'
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("selenium-version", "2.53.1");
$caps = array(
"selenium-version" => "2.53.1"
);
capabilities = {
"selenium-version" : '2.53.1'
}
const capabilities = {
"selenium-version" : '2.53.1'
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("selenium-version", "2.53.1");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
"selenium-version" : '2.53.1'
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("selenium-version", "2.53.1");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'selenium-version' => '2.53.1'
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'selenium-version': '2.53.1'
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"selenium-version": '2.53.1'
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["selenium-version"] = "2.53.1"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value | Possible Values: |
| --- | --- | --- |
| string | "2.53.1" |
4.41.0
4.40.0
4.39.0
4.38.0
4.37.0
4.36.0
4.35.0
4.34.0
4.33.0
4.32.0
4.31.0
4.30.0
4.29.0
4.28.1
4.28.0
4.27.0
4.26.0
4.25.0
4.24.0
4.23.1
4.23.0
4.22.0
4.21.0
4.20.0
4.19.0
4.18.1
4.18.0
4.17.0
4.16.1
4.16.0
4.15.0
4.14.1
4.14.0
4.13.0
4.12.1
4.12.0
4.11.0
4.10.0
4.9.0
4.8.3
4.8.2
4.8.1
4.8.0
4.7.0
4.6.0
4.5.3
4.5.2
4.5.1
4.5.0
4.4.0
4.3.0
4.2.2
4.2.1
4.2.0
4.1.3
4.1.2
4.1.1
4.1.0
4.0.0
4.0.0-beta-4
4.0.0-beta-3
4.0.0-beta-2
4.0.0-beta-1
4.0.0-alpha-7
4.0.0-alpha-6
3.141.59
3.141.5
3.141.0
3.14.0
3.13.0
3.12.0
3.11.0
3.10.0
3.9.0
3.8.1
3.8.0
3.7.1
3.7.0
3.6.0
3.5.3
3.5.2
3.5.1
3.5.0
3.4.0
3.3.1
3.3.0
3.2.0
3.1.0
3.0.1
2.53.1
2.53.0
2.52.0
2.51.0
2.50.0
2.49.0
2.48.2
2.48.1
2.48.0
|
### Chromedriver
We support using custom ChromeDriver versions during your tests. By default the most recent Chromedriver is used according to the version of Chrome you're testing on.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["chromedriverVersion"] = "76.0.3809.25"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("chromedriverVersion", "76.0.3809.25");
$caps = array(
"chromedriverVersion" => "76.0.3809.25"
);
capabilities = {
"chromedriverVersion" : "76.0.3809.25"
}
const capabilities = {
"chromedriverVersion" : "76.0.3809.25"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("chromedriverVersion", "76.0.3809.25");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
"chromedriverVersion" : "76.0.3809.25"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("chromedriverVersion", "76.0.3809.25");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'chromedriverVersion' => "76.0.3809.25"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'chromedriverVersion': "76.0.3809.25"
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"chromedriverVersion": "76.0.3809.25"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11"
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["chromedriverVersion"] = "76.0.3809.25"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value | Possible Values: |
| --- | --- | --- |
| string | "2.46" |
dev
149.0.7795.2
148.0.7778.40
147.0.7727.3
146.0.7680.0
145.0.7632.76
145.0.7561.2
144.0.7559.3
143.0.7499.40
142.0.7444.59
141.0.7390.54
140.0.7339.80
139.0.7207.2
138.0.7153.0
137.0.7117.2
136.0.7064.0
135.0.6999.2
134.0.6958.2
133.0.6943.16
132.0.6811.2
131.0.6778.3
130.0.6669.2
129.0.6614.3
128.0.6613.18
127.0.6533.88
127.0.6485.0
126.0.6423.2
125.0.6422.4
124.0.6367.60
124.0.6356.2
123.0.6262.5
122.0.6253.3
121.0.6154.0
121.0.6129.0
120.0.6099.5
119.0.6045.9
118.0.5979.0
117.0.5938.0
116.0.5845.82
115.0.5790.170
115.0.5790.90
114.0.5735.90
114.0.5735.16
113.0.5672.63
113.0.5672.24
112.0.5615.49
112.0.5615.28
111.0.5563.64
111.0.5563.41
111.0.5563.19
110.0.5481.77
110.0.5481.30
109.0.5414.74
109.0.5414.25
108.0.5359.71
108.0.5359.22
107.0.5304.62
107.0.5304.18
106.0.5249.61
106.0.5249.21
105.0.5195.52
105.0.5195.19
104.0.5112.79
104.0.5112.29
104.0.5112.20
103.0.5060.134
103.0.5060.24
102.0.5005.61
102.0.5005.27
101.0.4951.41
101.0.4951.15
100.0.4896.60
100.0.4896.20
99.0.4844.51
99.0.4844.35
99.0.4844.17
98.0.4758.80
98.0.4758.48
97.0.4692.71
97.0.4692.36
97.0.4692.20
96.0.4664.45
95.0.4638.10
94.0.4606.41
93.0.4577.15
92.0.4515.43
91.0.4472.101
91.0.4472.19
90.0.4430.24
89.0.4389.23
88.0.4324.96
88.0.4324.27
87.0.4280.88
87.0.4280.20
86.0.4240.22
85.0.4183.87
85.0.4183.83
85.0.4183.38
84.0.4147.30
83.0.4103.39
83.0.4103.14
81.0.4044.69
81.0.4044.20
80.0.3987.16
79.0.3945.36
79.0.3945.16
78.0.3904.70
78.0.3904.11
77.0.3865.40
77.0.3865.10
76.0.3809.68
76.0.3809.25
76.0.3809.12
75.0.3770.140
75.0.3770.90
75.0.3770.8
74.0.3729.6
73.0.3683.68
73.0.3683.20
72.0.3626.7
72.0.3626.69
71.0.3578.80
71.0.3578.33
71.0.3578.30
71.0.3578.137
70.0.3538.97
70.0.3538.67
70.0.3538.16
2.46
2.45
2.44
2.43
2.42
2.41
2.40
2.39
2.38
2.37
2.36
2.35
2.34
2.33
2.32
2.31
2.30
2.29
2.28
2.27
2.26
2.25
2.24
2.23
2.22
2.21
2.20
2.19
2.16
2.15
2.14
2.13
2.12
2.10
|
### Internet Explorer Driver (IEDriver)
We provide both 32-bit and 64-bit versions of the Internet Explorer driver. By default we use the 2.53.1 (32-bit) IEDriver for your tests.
We use 32-bit because of a [slow text entry bug](https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/5116) with the 64-bit version.
For our screenshots, we use the 64-bit version, because of a [screenshot problem](https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/5876) with the 32-bit version.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["iedriverVersion"] = "2.53.1"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("iedriverVersion", "2.53.1");
$caps = array(
"iedriverVersion" => "2.53.1"
);
capabilities = {
"iedriverVersion" : "2.53.1"
}
const capabilities = {
"iedriverVersion" : "2.53.1"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("iedriverVersion", "2.53.1");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "internet explorer",
browserVersion: "latest",
"se:ieOptions": {},
"tb:options": {
"iedriverVersion" : "4.8.0",
"selenium-version": "4.8.0"
}
}
InternetExplorerOptions ieOpts = new InternetExplorerOptions();
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("iedriverVersion", "4.8.0");
tbOptions.setCapability("selenium-version", "4.8.0");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("se:ieOptions", ieOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "internet explorer");
$capabilities = DesiredCapabilities::internetExplorer();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'iedriverVersion' => "4.8.0",
'selenium-version' => "4.8.0"
));
$capabilities->setCapability('se:ieOptions', []);
tbOptions = {
'name': 'W3C Sample',
'iedriverVersion': '4.8.0',
'selenium-version': '4.8.0'
}
ieOpts = {
'browserName': "internet explorer",
'platformName': "WIN11",
'se:ieOptions': {},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=ieOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'internet explorer',
"platformName": 'WIN11',
"se:ieOptions" : {},
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"iedriverVersion": "4.8.0",
"selenium-version": "4.8.0"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var ieOptions = new InternetExplorerOptions()
{
BrowserVersion = "11",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["iedriverVersion"] = "4.8.0",
["selenium-version"] = "4.8.0"
};
ieOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
ieOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value | Possible Values: |
| --- | --- | --- |
| string | 2.53.1 |
x64\_4.14.0
4.14.0
x64\_4.11.0
4.11.0
x64\_4.10.0
4.10.0
x64\_4.8.1
4.8.1
x64\_4.8.0
4.8.0
x64\_4.7.0
4.7.0
x64\_4.6.0
4.6.0
x64\_4.3.0
4.3.0
x64\_4.2.0
4.2.0
x64\_4.0.0
4.0.0
x64\_3.141.59
3.141.59
x64\_3.141.5
3.141.5
x64\_3.141.0
3.141.0
x64\_3.14.0
3.14.0
x64\_3.13.0
3.13.0
x64\_3.12.0
3.12.0
x64\_3.11.1
3.11.1
x64\_3.11.0
3.11.0
x64\_3.10.0
3.10.0
x64\_3.9.0
3.9.0
x64\_3.8.0
3.8.0
x64\_3.7.0
3.7.0
x64\_3.6.0
3.6.0
x64\_3.5.0
3.5.0
x64\_3.4.0
3.4.0
x64\_3.3.0
3.3.0
x64\_3.2.0
3.2.0
x64\_3.1.0
3.1.0
x64\_2.53.1
2.53.1
x64\_2.53.0
2.53.0
x64\_2.52.2
2.52.2
x64\_2.51.0
2.51.0
x64\_2.50.0
2.50.0
x64\_2.49.0
2.49.0
x64\_2.48.0
2.48.0
x64\_2.47.0
2.47.0
x64\_2.46.0
2.46.0
x64\_2.45.0
2.45.0
x64\_2.42.0
2.42.0
|
### Edge Driver (MicrosoftWebDriver)
EdgeDriver is built by Microsoft to automate the Microsoft Edge Browser.
By default, we make sure that the most recent fully-compatible EdgeDriver is used for every test running on the Microsoft Edge Browser.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
"browserName" : "MicrosoftEdge",
"platform": "WINDOWS"
}
caps["edgedriverVersion"] = "110"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("edgedriverVersion", "110");
$caps = array(
"edgedriverVersion" => "110"
);
capabilities = {
"edgedriverVersion" : "110"
}
const capabilities = {
"edgedriverVersion" : "110",
"ms:edgeOptions": {}
}
var edgeOptions = new EdgeOptions()
{
BrowserVersion = "11",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["edgeDriverVersion"] = "110",
["selenium-version"] = "4.8.0"
};
edgeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
edgeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "microsoftedge",
browserVersion: "latest",
"tb:options" => {
"edgedriverVersion" : "110"
},
"ms:edgeOptions": {}
}
EdgeOptions edgeOpts = new EdgeOptions();
edgeOpts.setPlatformName("WIN11");
edgeOpts.setBrowserVersion("latest");
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("edgedriverVersion", "110");
edgeOpts.setCapability("tb:options", tbOptions);
$capabilities = DesiredCapabilities::microsoftEdge();
$capabilities->setCapability('platformName', 'WIN11');
$capabilities->setCapability('tb:options', array(
'edgedriverVersion' => "110"
));
$capabilities->setCapability('ms:edgeOptions', new \stdClass());
tbOptions = {
'name': 'W3C Sample',
'edgedriverVersion': '110'
}
edgeOpts = {
'browserName': "MicrosoftEdge",
'platformName': "WIN11",
'tb:options': tbOptions,
'ms:edgeOptions': {}
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=edgeOpts)
let driver = await new webdriver.Builder().withCapabilities({
"browserName": 'microsoftedge',
"platformName": 'WIN11',
"ms:edgeOptions" : {},
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"edgedriverVersion": "110"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var edgeOptions = new EdgeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["edgedriverVersion"] = "110"
};
edgeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
edgeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value | Possible Values: |
| --- | --- | --- |
| string | 15063 |
141
140
139
138
137
136
135
134
133
132
131
130
129
128
127
126
125
124
123
122
121
120
119
118
117
116
115
114
113
112
111
110
109
108
107
106
105
104
103
102
101
100
99
98
97
96
95
94
93
92
91
90
89
88
87
86
85
84
83
81
dev
insiders
16299
15063
14393
|
### Firefox Driver (Geckodriver)
For Firefox 47 and up, webdriver tests on Firefox need to use [Mozilla's GeckoDriver](https://github.com/mozilla/geckodriver).
Specify this option to choose which Geckodriver we should use. By default, we use the version that is most compatible with the Firefox version you request.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["geckodriverVersion"] = "0.28.0"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("geckodriverVersion", "0.28.0");
$caps = array(
"geckodriverVersion" => "0.28.0"
);
capabilities = {
"geckodriverVersion" : "0.28.0"
}
const capabilities = {
"geckodriverVersion" : "0.28.0"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("geckodriverVersion", "0.28.0");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "firefox",
browserVersion: "latest",
"tb:options": {
"geckodriverVersion" : "0.28.0"
},
"moz:firefoxOptions": {}
}
FirefoxOptions ffOptions = new FirefoxOptions();
ffOptions.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("geckodriverVersion", "0.28.0");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(FirefoxOptions.CAPABILITY, ffOptions);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "firefox");
$options = new FirefoxOptions();
$capabilities = DesiredCapabilities::firefox();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'geckodriverVersion' => "0.28.0"
));
$capabilities->setCapability('moz:firefoxOptions', array());
$capabilities->setCapability(FirefoxOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'geckodriverVersion': "0.28.0"
}
chromeOpts = {
'browserName': "firefox",
'platformName': "WIN11",
'tb:options': tbOptions,
'moz:firefoxOptions': {}
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'firefox',
"platformName": 'WIN11',
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"geckodriverVersion": "0.28.0"
},
'moz:firefoxOptions': {}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var ffOptions = new FirefoxOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["geckodriverVersion"] = "0.28.0"
};
ffOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
ffOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value | Possible Values: |
| --- | --- | --- |
| string | 0.28.0 |
0.36.0
0.35.0
0.34.0
0.33.0
0.32.2
0.32.1
0.32.0
0.31.0
0.30.0
0.29.1
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.1
0.19.1
0.19.0
0.18.0
0.17.0
0.16.1
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.1
0.6.2
|
### Opera Driver
Opera uses the [Chromium OperaDriver](https://github.com/operasoftware/operachromiumdriver/releases) to automate Opera Desktop Browsers.
Specify this option to choose which OperaDriver we should use. By default, we use the version that is most compatible with the Opera version you request.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["operaDriverVersion"] = "90.0.4430.85"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("operaDriverVersion", "90.0.4430.85");
$caps = array(
"operaDriverVersion" => "90.0.4430.85"
);
capabilities = {
"operaDriverVersion" : "90.0.4430.85"
}
const capabilities = {
"operaDriverVersion" : "90.0.4430.85"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("operaDriverVersion", "90.0.4430.85");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "opera",
browserVersion: "latest",
"tb:options" => {
"operaDriverVersion" : "90.0.4430.85"
}
}
MutableCapabilities caps = new MutableCapabilities();
caps.setCapability("browserName", "opera");
caps.setCapability("platformName", "WIN11");
caps.setCapability("browserVersion", "latest");
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("operaDriverVersion", "90.0.4430.85");
caps.setCapability("tb:options", tbOptions);
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setBrowserName('opera');
$capabilities->setCapability('tb:options', array(
'operaDriverVersion' => "90.0.4430.85"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'operaDriverVersion': "90.0.4430.85"
}
chromeOpts = {
'browserName': "opera",
'platformName': "WIN11",
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'opera',
"platformName": 'WIN11',
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"operaDriverVersion": "90.0.4430.85"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var operaOptions = new OperaOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["operaDriverVersion"] = "90.0.4430.85"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
operaOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value | Possible Values: |
| --- | --- | --- |
| string | 145.0.7632.117 |
146.0.7680.178
145.0.7632.117
144.0.7559.173
143.0.7499.194
142.0.7444.243
141.0.7390.125
140.0.7339.249
139.0.7258.156
138.0.7204.251
137.0.7151.122
135.0.7049.115
134.0.6998.205
133.0.6943.143
132.0.6834.209
131.0.6778.86
130.0.6723.137
128.0.6613.162
127.0.6533.120
126.0.6478.127
125.0.6422.143
124.0.6367.62
123.0.6312.59
122.0.6261.95
121.0.6167.140
120.0.6099.200
119.0.6045.124
118.0.5993.89
117.0.5938.132
116.0.5845.97
115.0.5790.171
114.0.5735.110
113.0.5672.127
112.0.5615.87
111.0.5563.65
110.0.5481.100
109.0.5414.120
108.0.5359.99
107.0.5304.88
106.0.5249.119
105.0.5195.102
104.0.5112.81
103.0.5060.66
102.0.5005.61
101.0.4951.64
100.0.4896.127
99.0.4844.51
98.0.4758.82
97.0.4692.71
96.0.4664.45
95.0.4638.54
94.0.4606.61
93.0.4577.63
92.0.4515.107
91.0.4472.77
90.0.4430.85
89.0.4389.82
88.0.4324.104
87.0.4280.67
86.0.4240.80
85.0.4183.102
84.0.4147.89
83.0.4103.97
81.0.4044.113
80.0.3987.100
79.0.3945.79
78.0.3904.87
77.0.3865.120
76.0.3809.132
75.0.3770.100
2.45
2.42
2.41
2.40
2.38
2.37
2.36
2.35
2.33
2.32
2.30
2.229
2.27
2.26
2.25
2.24
2.23
2.22
|
### Taking screenshots during your tests
By default we do not capture screenshots at every step of your test. If you wish to take a screenshot for every step, please add this capability (set to `true`) to your request.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["screenshot"] = true
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("screenshot", true);
$caps = array(
"screenshot" => true
);
capabilities = {
"screenshot" : True
}
const capabilities = {
"screenshot" : true
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("screenshot", true);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
screenshot: true
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("screenshot", true);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'screenshot' => true
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'screenshot': True
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"screenshot": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["screenshot"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value |
| --- | --- |
| boolean | false |
### Make a video of your tests
By default we record a video of your test, which is accessible in the member area. If you do not wish to have this, you can disable it with this option.
Video should not slow down your test considerably.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["screenrecorder"] = true
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("screenrecorder", true);
$caps = array(
"screenrecorder" => true
);
capabilities = {
"screenrecorder" : True
}
const capabilities = {
"screenrecorder" : true
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("screenrecorder", true);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
screenrecorder: true
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("screenrecorder", true);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'screenrecorder' => true
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'name': 'W3C Sample',
'screenrecorder': True
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"screenrecorder": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["screenrecorder"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value |
| --- | --- |
| boolean | true |
### Test Privacy
Make the test results for this test public so that everyone can access the results.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["public"] = false
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("public", false);
$caps = array(
"public" => false
);
capabilities = {
"public" : False
}
const capabilities = {
"public" : false
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("public", false);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
"public" : false
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("public", false);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'public' => 130
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'public': False
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"public": false
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["public"] = false
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value |
| --- | --- |
| boolean | false |
### Blacklist hostnames
The hostnames you specify will be pointed to localhost instead of their real destination. This means you can speed up tests by blocking third party content which you don't need and slows down your test.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["blacklist"] = "site1.com,site2.com"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("blacklist", "site1.com,site2.com");
$caps = array(
"blacklist" => "site1.com,site2.com"
);
capabilities = {
"blacklist" : "site1.com,site2.com"
}
const capabilities = {
"blacklist" : "site1.com,site2.com"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("blacklist", "site1.com,site2.com");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
"blacklist" : "site1.com,site2.com"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("blacklist", "site1.com,site2.com");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'blacklist' => "site1.com,site2.com"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'blacklist': "site1.com,site2.com"
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"blacklist": "site1.com,site2.com"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["blacklist"] = "site1.com,site2.com"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type |
| --- |
| string (comma-separated) |
### Customize Logging
By default, TestingBot records logs of all Selenium actions and its drivers.
Set this option to `false` if you don't want TestingBot to record anything (for example, if you have sensitive data).
You will not see any test logs in our member dashboard.
Set to `strip-parameters` to prevent the POST/GET parameters from being logged on the TestingBot test detail page (does not affect other logs like Selenium logs, Chromedriver logs, ...).
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["recordLogs"] = true
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("recordLogs", true);
$caps = array(
"recordLogs" => true
);
capabilities = {
"recordLogs" : True
}
const capabilities = {
"recordLogs" : true
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("recordLogs", true);
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
"recordLogs" : true
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("recordLogs", true);
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'recordLogs' => true
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'recordLogs': True
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"recordLogs": true
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["recordLogs"] = true
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value | Possible Values: |
| --- | --- | --- |
| string | "true" | `true`, `false`, or `strip-parameters` |
### Custom Time Zones
Change the Time Zone of the Virtual Machine to the Time Zone you specify. You can find a [list of timezones on Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). Only location names are supported (not their paths). See some examples below:
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["timeZone"] = "Etc/UTC"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("timeZone", "Etc/UTC");
$caps = array(
"timeZone" => "Etc/UTC"
);
capabilities = {
"timeZone" : "Etc/UTC"
}
const capabilities = {
"timeZone" : "Etc/UTC"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("timeZone", "Etc/UTC");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
"timeZone" : "Etc/UTC"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("timeZone", "Etc/UTC");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'timeZone' => "Etc/UTC"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'timeZone': "Etc/UTC"
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"timeZone": "Etc/UTC"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["timeZone"] = "Etc/UTC"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value | Possible Values: |
| --- | --- | --- |
| string | "Etc/UTC" | [List of timezones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) |
### Change Screen Resolution
Will adjust the screen resolution during your test.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["screen-resolution"] = "1280x1024"
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("screen-resolution", "1280x1024");
$caps = array(
"screen-resolution" => "1280x1024"
);
capabilities = {
"screen-resolution" : "1280x1024"
}
const capabilities = {
"screen-resolution" : "1280x1024"
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("screen-resolution", "1280x1024");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
"screen-resolution" : "1280x1024"
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("screen-resolution", "1280x1024");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'screen-resolution' => "1280x1024"
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'screen-resolution': "My Test Name"
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"screen-resolution": "1280x1024"
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["screen-resolution"] = "1280x1024"
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type | Default Value | |
| --- | --- | --- |
| string | "1280x1024" |
| Platform | Resolutions |
| --- | --- |
| Windows/Linux |
800x600
1024x768
1152x864
1280x768
1280x800
1280x960
1280x1024
1400x1050
1600x1200
1680x1050
1920x1080
1920x1200
2560x1440
|
| macOS |
800x600
1024x768
1280x768
1280x800
1280x960
1280x1024
1366x768
1440x900
1600x900
1600x1200
1680x1050
1920x1080
1920x1200
2048x1536
|
|
### Customize OS
When you specify a prerun file, we will first download the file and execute it (with optional prerun-arguments).
This is useful when you want to customize/add software before your test starts.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["prerun"] = "https://..."
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("prerun", "https://...");
$caps = array(
"prerun" => "https://..."
);
capabilities = {
"prerun" : "https://..."
}
const capabilities = {
"prerun" : "https://..."
}
DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("prerun", "https://...");
**Selenium W3C Example:**
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = {
platformName: "WIN11",
browserName: "chrome",
browserVersion: "latest",
"tb:options" => {
"prerun" : "https://..."
}
}
ChromeOptions chromeOpts = new ChromeOptions();
chromeOpts.setExperimentalOption("w3c", true);
MutableCapabilities tbOptions = new MutableCapabilities();
tbOptions.setCapability("key", "api_key");
tbOptions.setCapability("secret", "api_secret");
tbOptions.setCapability("prerun", "https://...");
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability(ChromeOptions.CAPABILITY, chromeOpts);
caps.setCapability("platformName", "WIN11");
caps.setCapability("tb:options", tbOptions);
caps.setCapability("browserName", "chrome");
$options = new ChromeOptions();
$capabilities = DesiredCapabilities::chrome();
$capabilities->setPlatform('WIN11');
$capabilities->setCapability('tb:options', array(
'prerun' => "https://..."
));
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);
tbOptions = {
'prerun': "https://..."
}
chromeOpts = {
'browserName': "chrome",
'platformName': "WIN11",
'goog:chromeOptions': {'w3c': True},
'tb:options': tbOptions
}
self.driver = webdriver.Remote(remote_url, desired_capabilities=chromeOpts)
driver = await new webdriver.Builder().withCapabilities({
"browserName": 'chrome',
"platformName": 'WIN11',
/** Google requires "w3c" to be set in "goog:chromeOptions" as true if you're using ChromeDriver version 74 or lower.
* Based on this commit: https://chromium.googlesource.com/chromium/src/+/2b49880e2481658e0702fd6fe494859bca52b39c
* ChromeDriver now uses w3c by default from version 75+ so setting this option will no longer be a requirement **/
"goog:chromeOptions" : { "w3c" : true },
"tb:options": {
"key": "api_key",
"secret": "api_secret",
"prerun": "https://..."
}
}).usingServer("https://hub.testingbot.com/wd/hub").build();
var chromeOptions = new ChromeOptions()
{
BrowserVersion = "latest",
PlatformName = "WIN11",
UseSpecCompliantProtocol = true
};
var tbOptions = new Dictionary
{
["key"] = "api_key",
["secret"] = "api_secret",
["prerun"] = "https://..."
};
chromeOptions.AddAdditionalCapability("tb:options", tbOptions, true);
driver = new RemoteWebDriver(new Uri("https://hub.testingbot.com/wd/hub"),
chromeOptions.ToCapabilities(), TimeSpan.FromSeconds(600));
| Value Type |
| --- |
| string (URL) |
"prerun-args" : "-a -b -c ..."
| Value Type |
| --- |
| string |
With [TestingBot Storage](https://testingbot.com/support/api#upload), you can upload your executable on our servers.
The advantage of this is that our test VMs can immediately download your executable from our own network, which is much faster than downloading from the public internet.
Once the file is uploaded via TestingBot Storage, you can use `"prerun" : "tb://..."`
### Edit hostnames
You can specify an array of objects containing the keys `ip` and `domain`. We will write these values to the hosts file of the VM.
[Ruby](https://testingbot.com#)[Python](https://testingbot.com#)[PHP](https://testingbot.com#)[Java](https://testingbot.com#)[NodeJS](https://testingbot.com#)[C#](https://testingbot.com#)
caps = Selenium::WebDriver::Remote::Capabilities.new
caps["hosts"] = [{ ip: '127.0.0.1', domain: 'mydomain' }]
List