import path from 'node:path'
import { expect, describe, it, vi, beforeEach } from 'vitest'
import logger from '@wdio/logger'
import type { IosDetailedContext } from '../../../src/index.js'
import { remote } from '../../../src/index.js'
vi.mock('fetch')
const log = logger('test')
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
describe('switchContext test', () => {
let browser: WebdriverIO.Browser
let androidContexts = [] as any
let iOSContexts = [] as IosDetailedContext[]
let logSpy
beforeEach(async () => {
androidContexts = [
{ id: 'NATIVE_APP' },
{
androidWebviewData: {
attached: true,
empty: false,
height: 2589,
neverAttached: false,
screenX: 0,
screenY: 151,
visible: true,
width: 1344
},
id: 'WEBVIEW_com.wdiodemoapp',
title: 'WebdriverIO · Next-gen browser and mobile automation test framework for Node.js | WebdriverIO',
url: 'https://webdriver.io/',
packageName: 'com.wdiodemoapp',
webviewPageId: 'B32A3DC8B0FEA92C435CE26D51F46986',
},
{
androidWebviewData: {
attached: true,
empty: false,
height: 2589,
neverAttached: false,
screenX: 0,
screenY: 151,
visible: true,
width: 1344
},
id: 'WEBVIEW_com.otherApp',
title: 'Other Apps · I am just another app',
url: 'https://otherapp.io/',
packageName: 'com.otherApp',
webviewPageId: '6751C1E052A63B0CA27F839216AEF4B8',
},
{
androidWebviewData: {
attached: true,
empty: false,
height: 2589,
neverAttached: false,
screenX: 0,
screenY: 151,
visible: false,
width: 1344
},
id: 'WEBVIEW_com.otherNonVisibleApp',
title: 'Other Apps · I am just another app which is not visible',
url: 'https://othernotvisibleapp.io/',
packageName: 'com.otherNonVisibleApp',
webviewPageId: 'EE01B05A515BF58395772B5EA297BE78',
},
{
androidWebviewData: {
attached: false,
empty: false,
height: 0,
neverAttached: false,
screenX: 0,
screenY: 0,
visible: false,
width: 0
},
id: 'WEBVIEW_chrome',
title: 'Android | Get more done with Google on Android-phones and devices',
url: 'https://www.android.com/',
packageName: 'com.android.chrome',
webviewPageId: '0',
},
]
iOSContexts = [
{ id: 'NATIVE_APP' },
{
id: 'WEBVIEW_86150.1',
title: 'WebdriverIO · Next-gen browser and mobile automation test framework for Node.js | WebdriverIO',
url: 'https://webdriver.io/',
bundleId: 'org.reactjs.native.example.wdiodemoapp'
},
{
id: 'WEBVIEW_86152.1',
title: 'Apple',
url: 'https://www.apple.com/',
bundleId: 'com.apple.mobilesafari'
}
]
vi.mocked(fetch).mockClear()
})
it('should call the default Appium endpoint if the Webview option as a string is provided', async () => {
logSpy = vi.spyOn(log, 'info')
browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar',
mobileMode: true,
} as any
})
await browser.switchContext('WEBVIEW_29051')
const { calls } = vi.mocked(fetch).mock
expect(calls).toHaveLength(2)
const [[sessionCallUrl], [callUrl]] = calls as any
expect(sessionCallUrl.pathname).toEqual('/session')
expect(callUrl.pathname).toEqual('/session/foobar-123/context')
expect(logSpy).toHaveBeenCalledWith('The standard Appium `context`-method is used. If you want to switch to a webview with a specific title or url, please provide an object with the `title` or `url` property. See https://webdriver.io/docs/api/mobile/switchContext for more information.')
logSpy.mockRestore()
})
it('should find a matching context for iOS and switch to it', async () => {
logSpy = vi.spyOn(log, 'info')
browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar',
mobileMode: true,
platformName: 'iOS',
} as any
})
const getContextsSpy = vi.spyOn(browser, 'getContexts').mockResolvedValue(iOSContexts)
const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue({ bundleId:'com.apple.mobilesafari' })
const switchAppiumContextSpy = vi.spyOn(browser, 'switchAppiumContext')
await browser.switchContext({ title: 'Apple' })
expect(getContextsSpy).toHaveBeenCalledTimes(1)
expect(getContextsSpy).toHaveBeenCalledWith({
'filterByCurrentAndroidApp': false,
'isAndroidWebviewVisible': false,
'returnAndroidDescriptionData': true,
'returnDetailedContexts': true,
})
expect(executeSpy).toHaveBeenCalledTimes(1)
expect(executeSpy).toHaveBeenCalledWith('mobile: activeAppInfo')
expect(logSpy).toHaveBeenCalledWith('WebdriverIO found a matching context:', JSON.stringify(iOSContexts[2], null, 2))
expect(switchAppiumContextSpy).toHaveBeenCalledWith(iOSContexts[2].id)
logSpy.mockRestore()
getContextsSpy.mockRestore()
executeSpy.mockRestore()
switchAppiumContextSpy.mockRestore()
})
it('should find a matching context for Android and switch to it', async () => {
logSpy = vi.spyOn(log, 'info')
browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar',
mobileMode: true,
platformName: 'Android',
} as any
})
const getContextsSpy = vi.spyOn(browser, 'getContexts').mockResolvedValue(androidContexts)
const getCurrentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.wdiodemoapp')
const switchAppiumContextSpy = vi.spyOn(browser, 'switchAppiumContext')
const switchToWindowSpy = vi.spyOn(browser, 'switchToWindow')
await browser.switchContext({ title: /.*WebdriverIO.*/, url: /.*webdriver.io/ })
expect(getContextsSpy).toHaveBeenCalledTimes(1)
expect(getContextsSpy).toHaveBeenCalledWith({
'filterByCurrentAndroidApp': false,
'isAndroidWebviewVisible': false,
'returnAndroidDescriptionData': true,
'returnDetailedContexts': true,
})
expect(getCurrentPackageSpy).toHaveBeenCalledTimes(1)
expect(logSpy).toHaveBeenCalledWith('WebdriverIO found a matching context:', JSON.stringify(androidContexts[1], null, 2))
expect(switchAppiumContextSpy).toHaveBeenCalledWith(androidContexts[1].id)
expect(switchToWindowSpy).toHaveBeenCalledWith(androidContexts[1].webviewPageId)
logSpy.mockRestore()
getContextsSpy.mockRestore()
getCurrentPackageSpy.mockRestore()
switchAppiumContextSpy.mockRestore()
switchToWindowSpy.mockRestore()
})
it('should throw an error for non-mobile platforms', async () => {
browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar',
} as any
})
await expect(browser.switchContext('foo')).rejects.toThrow('The `switchContext` command is only available for mobile platforms.')
})
it('should throw an error when no options are provided', async () => {
browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar',
mobileMode: true,
platformName: 'Android',
} as any
})
await expect(browser.switchContext()).rejects.toThrow('You need to provide at least a context name to switch to. See https://webdriver.io/docs/api/mobile/switchContext for more information.')
})
it('should throw an error when no title and url is provided', async () => {
browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar',
mobileMode: true,
platformName: 'Android',
} as any
})
await expect(browser.switchContext({})).rejects.toThrow('You need to provide at least a `title` or `url` property to use full potential of the `switchContext` command. See https://webdriver.io/docs/api/mobile/switchContext for more information.')
})
it('should throw an error when no matching context is found for iOS based on the title', async () => {
browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar',
mobileMode: true,
platformName: 'iOS',
} as any
})
const getContextsSpy = vi.spyOn(browser, 'getContexts').mockResolvedValue(iOSContexts)
const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue({ bundleId:'com.foo' })
const switchAppiumContextSpy = vi.spyOn(browser, 'switchAppiumContext')
await expect(browser.switchContext({ title: 'No matching Title' })).rejects.toThrowErrorMatchingSnapshot()
getContextsSpy.mockRestore()
executeSpy.mockRestore()
switchAppiumContextSpy.mockRestore()
})
it('should throw an error when no matching context is found for Android based on the url', async () => {
browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar',
mobileMode: true,
platformName: 'Android',
} as any
})
const getContextsSpy = vi.spyOn(browser, 'getContexts').mockResolvedValue(androidContexts)
const getCurrentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.foo')
const switchAppiumContextSpy = vi.spyOn(browser, 'switchAppiumContext')
await expect(browser.switchContext({ url: 'https://no-matching-url.io' })).rejects.toThrowErrorMatchingSnapshot()
getContextsSpy.mockRestore()
getCurrentPackageSpy.mockRestore()
switchAppiumContextSpy.mockRestore()
})
it('should throw an error when no matching context is found for Android based on the title and url regex', async () => {
browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar',
mobileMode: true,
platformName: 'Android',
} as any
})
const getContextsSpy = vi.spyOn(browser, 'getContexts').mockResolvedValue(androidContexts)
const getCurrentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.foo')
const switchAppiumContextSpy = vi.spyOn(browser, 'switchAppiumContext')
await expect(browser.switchContext({
title: /No matching Title/,
url: /.*io/,
androidWebviewConnectionRetryTime: 1000,
androidWebviewConnectTimeout: 1000,
})).rejects.toThrowErrorMatchingSnapshot()
getContextsSpy.mockRestore()
getCurrentPackageSpy.mockRestore()
switchAppiumContextSpy.mockRestore()
})
})