import type { Capabilities } from '@wdio/types'
import { SUPPORTED_BROWSERNAMES } from './constants.js'
const MOBILE_BROWSER_NAMES = ['ipad', 'iphone', 'android']
const MOBILE_CAPABILITIES = [
'appium-version', 'appiumVersion', 'device-type', 'deviceType', 'app', 'appArguments',
'device-orientation', 'deviceOrientation', 'deviceName', 'automationName'
]
* check if session is based on W3C protocol based on the /session response
* @param {Object} capabilities caps of session response
* @return {Boolean} true if W3C (browser)
*/
export function isW3C(capabilities?: WebdriverIO.Capabilities) {
* JSONWire protocol doesn't return a property `capabilities`.
* Also check for Appium response as it is using JSONWire protocol for most of the part.
*/
if (!capabilities) {
return false
}
* assume session to be a WebDriver session when
* - capabilities are returned
* (https://w3c.github.io/webdriver/#dfn-new-sessions)
* - it is an Appium session (since Appium is full W3C compliant)
*/
const isAppium = Boolean(
capabilities['appium:automationName'] ||
capabilities['appium:deviceName'] ||
capabilities['appium:appiumVersion']
)
const hasW3CCaps = Boolean(
* safari docker image may not provide a platformName therefore
* check one of the available "platformName" or "browserVersion"
*/
(capabilities.platformName || capabilities.browserVersion) &&
* local safari and BrowserStack don't provide platformVersion therefore
* check also if setWindowRect is provided
*/
(
capabilities['appium:platformVersion'] ||
Object.prototype.hasOwnProperty.call(capabilities, 'setWindowRect')
)
)
const hasWebdriverFlag = Boolean(capabilities['ms:experimental-webdriver'])
return Boolean(hasW3CCaps || isAppium || hasWebdriverFlag)
}
* check if session is run by Chromedriver
* @param {Object} capabilities caps of session response
* @return {Boolean} true if run by Chromedriver
*/
function isChrome(capabilities?: WebdriverIO.Capabilities) {
if (!capabilities) {
return false
}
return Boolean(capabilities['goog:chromeOptions'] &&
(capabilities.browserName === 'chrome' || capabilities.browserName === 'chrome-headless-shell')
)
}
* check if session is run by Edgedriver
* @param {Object} capabilities caps of session response
* @return {Boolean} true if run by Edgedriver
*/
function isEdge(capabilities?: WebdriverIO.Capabilities) {
if (!capabilities) {
return false
}
return (
Boolean(capabilities.browserName && SUPPORTED_BROWSERNAMES.edge.includes(capabilities.browserName.toLowerCase()) ||
capabilities['ms:edgeOptions'])
)
}
* check if session is run by Geckodriver
* @param {Object} capabilities caps of session response
* @return {Boolean} true if run by Geckodriver
*/
function isFirefox(capabilities?: WebdriverIO.Capabilities) {
if (!capabilities) {
return false
}
return (
capabilities.browserName === 'firefox' ||
Boolean(Object.keys(capabilities).find((cap) => cap.startsWith('moz:')))
)
}
interface ExtendedCapabilities extends WebdriverIO.Capabilities {
automationName?: string;
}
* get the automation name value of the session
*
* @param {Object} capabilities capabilities
* @return {Boolean} true if platform is mobile device
*/
function getAutomationName(capabilities: ExtendedCapabilities) {
return capabilities['appium:options']?.automationName || capabilities['appium:automationName'] || capabilities['automationName']
}
* check if current platform is mobile device
*
* @param {Object} capabilities capabilities
* @return {Boolean} true if platform is mobile device
*/
function isMobile(capabilities: WebdriverIO.Capabilities) {
const browserName = (capabilities.browserName || '').toLowerCase()
const bsOptions = capabilities['bstack:options'] || {}
const browserstackBrowserName = (bsOptions.browserName || '').toLowerCase()
* There are cases where sessions with `appium:*` prefixed capabilities do not fully support
* all "native"-mobile commands. In this case the `appium:automationName` is set with something
* else than the `xcuitest|uiautomator2|flutter|espress|..` value. This can be a browser driver or
* a "wrapped" appium browser-driver. See also https://github.com/webdriverio/webdriverio/issues/13947
* Return `isMobile:false` for those cases. There we also accepts that specific mobile browser
* tests (like the FF one on Android) are not seen as a mobile one
*/
const automationName = getAutomationName(capabilities)
if (automationName && ['gecko', 'safari', 'chrome', 'chromium'].includes(automationName.toLocaleLowerCase())) {
return false
}
* we have mobile capabilities if
*/
return Boolean(
* If the device is ios, tvos or android, the device might be mobile.
*/
capabilities.platformName && capabilities.platformName.match(/ios/i) ||
capabilities.platformName && capabilities.platformName.match(/tvos/i) ||
capabilities.platformName && capabilities.platformName.match(/android/i) ||
/ios/i.test(bsOptions.platformName || '') ||
/tvos/i.test(bsOptions.platformName || '') ||
/android/i.test(bsOptions.platformName || '') ||
* capabilities contain mobile only specific capabilities
*/
Object.keys(capabilities).find((cap) => (
MOBILE_CAPABILITIES.includes(cap) ||
MOBILE_CAPABILITIES.map((c) => `appium:${c}`).includes(cap)
)) ||
* browserName is empty (and eventually app is defined)
*/
capabilities.browserName === '' ||
bsOptions.browserName === '' ||
* browserName is a mobile browser
*/
MOBILE_BROWSER_NAMES.includes(browserName) ||
MOBILE_BROWSER_NAMES.includes(browserstackBrowserName)
)
}
* check if session is run on iOS device
* @param {Object} capabilities of session response
* @return {Boolean} true if run on iOS device
*/
function isIOS(capabilities?: WebdriverIO.Capabilities) {
const bsOptions = capabilities?.['bstack:options'] || {}
if (!capabilities) {
return false
}
return Boolean(
(capabilities.platformName && capabilities.platformName.match(/iOS/i)) ||
(capabilities['appium:deviceName'] && capabilities['appium:deviceName'].match(/(iPad|iPhone)/i)) ||
(/iOS/i.test(bsOptions.platformName || '')) ||
(/(iPad|iPhone)/i.test(bsOptions.deviceName || ''))
)
}
* check if session is run on Android device
* @param {Object} capabilities caps of session response
* @return {Boolean} true if run on Android device
*/
function isAndroid(capabilities?: WebdriverIO.Capabilities) {
const bsOptions = capabilities?.['bstack:options'] || {}
if (!capabilities) {
return false
}
const hasAndroidPlatform = Boolean(
(capabilities.platformName && capabilities.platformName.match(/Android/i)) ||
(/Android/i.test(bsOptions.platformName || '')) ||
(/Android/i.test(bsOptions.browserName || '')) ||
(capabilities.browserName && capabilities.browserName.match(/Android/i))
)
const deviceName = bsOptions.deviceName || ''
const hasAndroidDeviceName = /android|galaxy|pixel|nexus|oneplus|lg|htc|motorola|sony|huawei|vivo|oppo|xiaomi|redmi|realme|samsung/i.test(deviceName)
return Boolean(hasAndroidPlatform || hasAndroidDeviceName)
}
* Check if session uses a specific automation name
* @param {Object} capabilities caps of session response
* @param {String} platform platform to check for (e.g., 'windows', 'mac2')
* @return {Boolean} true if run for specified platform
*/
function matchesAppAutomationName(automationNameValue: string, capabilities?: WebdriverIO.Capabilities): boolean {
if (!capabilities) {
return false
}
const automationName = getAutomationName(capabilities)
if (!automationName) {
return false
}
return Boolean(automationName.match(new RegExp(automationNameValue, 'i')))
}
* Check if session is run for Windows apps
* @param {Object} capabilities caps of session response
* @return {Boolean} true if run for Windows Apps
*/
function isWindowsApp(capabilities?: WebdriverIO.Capabilities): boolean {
return matchesAppAutomationName('windows', capabilities)
}
* Check if session is run for Mac apps
* @param {Object} capabilities caps of session response
* @return {Boolean} true if run for Mac Apps
*/
function isMacApp(capabilities?: WebdriverIO.Capabilities): boolean {
return matchesAppAutomationName('mac2', capabilities)
}
* detects if session is run on Sauce with extended debugging enabled
* @param {object} capabilities session capabilities
* @return {Boolean} true if session is running on Sauce with extended debugging enabled
*/
function isSauce(capabilities?: Capabilities.WithRequestedCapabilities['capabilities']) {
if (!capabilities) {
return false
}
const caps = 'alwaysMatch' in capabilities
? capabilities.alwaysMatch
: capabilities
return Boolean(
caps['sauce:options'] &&
caps['sauce:options'].extendedDebugging
)
}
* Detects if session has support for WebDriver Bidi.
* @param {object} capabilities resolved session capabilities send back from the driver
* @return {Boolean} true if session has WebDriver Bidi support
*/
export function isBidi(capabilities: WebdriverIO.Capabilities) {
if (!capabilities) {
return false
}
return typeof capabilities.webSocketUrl === 'string'
}
* detects if session is run using Selenium Standalone server
* @param {object} capabilities session capabilities
* @return {Boolean} true if session is run with Selenium Standalone Server
*/
function isSeleniumStandalone(capabilities?: WebdriverIO.Capabilities) {
if (!capabilities) {
return false
}
return (
* Selenium v3 and below
*/
Boolean(capabilities['webdriver.remote.sessionid']) ||
* Selenium v4 and up
*/
Boolean(capabilities['se:cdp'])
)
}
* detects if session is run using Chromium protocol
* @param {object} capabilities session capabilities
* @return {Boolean} true if session is run with Chromium protocol
*/
function isChromium(capabilities?: WebdriverIO.Capabilities) {
if (!capabilities) {
return false
}
return (isChrome(capabilities) || isEdge(capabilities))
}
* returns information about the environment before the session is created
* @param {Object} capabilities caps provided by user
* @return {Object} object with environment flags
*/
export function capabilitiesEnvironmentDetector(capabilities: WebdriverIO.Capabilities) {
return {
isChrome: isChrome(capabilities),
isFirefox: isFirefox(capabilities),
isMobile: isMobile(capabilities),
isIOS: isIOS(capabilities),
isAndroid: isAndroid(capabilities),
isSauce: isSauce(capabilities),
isBidi: isBidi(capabilities),
isChromium: isChromium(capabilities),
isWindowsApp: isWindowsApp(capabilities),
isMacApp: isMacApp(capabilities)
}
}
* returns information about the environment when the session is created
* @param {Object} capabilities caps of session response
* @param {Object} requestedCapabilities
* @return {Object} object with environment flags
*/
export function sessionEnvironmentDetector({
capabilities,
requestedCapabilities
}: {
capabilities: WebdriverIO.Capabilities,
requestedCapabilities: Capabilities.RequestedStandaloneCapabilities
}) {
return {
isW3C: isW3C(capabilities),
isChrome: isChrome(capabilities),
isFirefox: isFirefox(capabilities),
isMobile: isMobile(capabilities),
isIOS: isIOS(capabilities),
isAndroid: isAndroid(capabilities),
isSauce: isSauce(requestedCapabilities),
isSeleniumStandalone: isSeleniumStandalone(capabilities),
isBidi: isBidi(capabilities),
isChromium: isChromium(capabilities),
isWindowsApp: isWindowsApp(capabilities),
isMacApp: isMacApp(capabilities)
}
}