🏡 index : quine-global/webdriverio.git

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('getContexts test', () => {
    let browser: WebdriverIO.Browser
    let androidChromeInternalContexts = [] as any
    let iOSContexts = [] as IosDetailedContext[]
    let logSpy

    beforeEach(async () => {
        androidChromeInternalContexts = [
            {
                proc: '@webview_devtools_remote_29051',
                webview: 'WEBVIEW_29051',
                info: {
                    'Android-Package': 'com.wdiodemoapp',
                    Browser: 'Chrome/113.0.5672.136',
                    'Protocol-Version': '1.3',
                    'User-Agent': 'Mozilla/5.0 (Linux; Android 14; sdk_gphone64_arm64 Build/UE1A.230829.036; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.136 Mobile Safari/537.36',
                    'V8-Version': '11.3.244.11',
                    'WebKit-Version': '537.36 (@2d54072eb2f350d37f3f304c4ba0fafcddbd7e82)',
                    webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/browser'
                },
                pages: [
                    {
                        description: '{"attached":true,"empty":false,"height":2682,"never_attached":false,"screenX":0,"screenY":144,"visible":true,"width":1440}',
                        devtoolsFrontendUrl: 'https://chrome-devtools-frontend.appspot.com/serve_internal_file/@2d54072eb2f350d37f3f304c4ba0fafcddbd7e82/inspector.html?ws=127.0.0.1:10900/devtools/page/6751C1E052A63B0CA27F839216AEF4B8',
                        faviconUrl: 'https://webdriver.io/img/favicon.png',
                        id: '6751C1E052A63B0CA27F839216AEF4B8',
                        title: 'WebdriverIO · Next-gen browser and mobile automation test framework for Node.js | WebdriverIO',
                        type: 'page',
                        url: 'https://webdriver.io/',
                        webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/page/6751C1E052A63B0CA27F839216AEF4B8'
                    },
                    {
                        description: '',
                        devtoolsFrontendUrl: 'https://chrome-devtools-frontend.appspot.com/serve_internal_file/@2d54072eb2f350d37f3f304c4ba0fafcddbd7e82/worker_app.html?ws=127.0.0.1:10900/devtools/page/BB0EE977F0C88F5DF6E50F902A855CDC',
                        id: 'BB0EE977F0C88F5DF6E50F902A855CDC',
                        title: 'Service Worker https://webdriver.io/sw.js?params=%7B%22offlineMode%22%3Afalse%2C%22debug%22%3Afalse%7D',
                        type: 'service_worker',
                        url: 'https://webdriver.io/sw.js?params=%7B%22offlineMode%22%3Afalse%2C%22debug%22%3Afalse%7D',
                        webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/page/BB0EE977F0C88F5DF6E50F902A855CDC'
                    }
                ],
                webviewName: 'WEBVIEW_com.wdiodemoapp'
            },
            {
                proc: '@webview_devtools_remote_29051',
                webview: 'WEBVIEW_29066',
                info: {
                    'Android-Package': 'com.otherApp',
                    Browser: 'Chrome/113.0.5672.136',
                    'Protocol-Version': '1.3',
                    'User-Agent': 'Mozilla/5.0 (Linux; Android 14; sdk_gphone64_arm64 Build/UE1A.230829.036; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.136 Mobile Safari/537.36',
                    'V8-Version': '11.3.244.11',
                    'WebKit-Version': '537.36 (@2d54072eb2f350d37f3f304c4ba0fafcddbd7e82)',
                    webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/browser'
                },
                pages: [
                    {
                        description: '{"attached":false,"empty":false,"height":2682,"never_attached":false,"screenX":0,"screenY":144,"visible":true,"width":1440}',
                        devtoolsFrontendUrl: 'https://chrome-devtools-frontend.appspot.com/serve_internal_file/@2d54072eb2f350d37f3f304c4ba0fafcddbd7e82/inspector.html?ws=127.0.0.1:10900/devtools/page/6751C1E052A63B0CA27F839216AEF4B8',
                        faviconUrl: 'https://otherapp.io/img/favicon.png',
                        id: '6751C1E052A63B0CA27F839216AEF4B8',
                        title: 'Other Apps · I am just another app',
                        type: 'page',
                        url: 'https://otherapp.io/',
                        webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/page/6751C1E052A63B0CA27F839216AEF4B8'
                    },
                ],
                webviewName: 'WEBVIEW_com.otherApp'
            },
            {
                proc: '@webview_devtools_remote_29051',
                webview: 'WEBVIEW_29066',
                info: {
                    'Android-Package': 'com.otherNonVisibleApp',
                    Browser: 'Chrome/113.0.5672.136',
                    'Protocol-Version': '1.3',
                    'User-Agent': 'Mozilla/5.0 (Linux; Android 14; sdk_gphone64_arm64 Build/UE1A.230829.036; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.136 Mobile Safari/537.36',
                    'V8-Version': '11.3.244.11',
                    'WebKit-Version': '537.36 (@2d54072eb2f350d37f3f304c4ba0fafcddbd7e82)',
                    webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/browser'
                },
                pages: [
                    {
                        description: '{"attached":true,"empty":false,"height":2682,"never_attached":false,"screenX":0,"screenY":144,"visible":false,"width":1440}',
                        devtoolsFrontendUrl: 'https://chrome-devtools-frontend.appspot.com/serve_internal_file/@2d54072eb2f350d37f3f304c4ba0fafcddbd7e82/inspector.html?ws=127.0.0.1:10900/devtools/page/6751C1E052A63B0CA27F839216AEF4B8',
                        faviconUrl: 'https://othernotvisibleapp.io/img/favicon.png',
                        id: '6751C1E052A63B0CA27F839216AEF4B8',
                        title: 'Other Apps · I am just another app which is not visible',
                        type: 'page',
                        url: 'https://othernotvisibleapp.io/',
                        webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/page/6751C1E052A63B0CA27F839216AEF4B8'
                    },
                ],
                webviewName: 'WEBVIEW_com.otherNonVisibleApp'
            },
            {
                proc: '@chrome_devtools_remote',
                webview: 'WEBVIEW_chrome',
                info: {
                    'Android-Package': 'com.android.chrome',
                    Browser: 'Chrome/124.0.6367.219',
                    'Protocol-Version': '1.3',
                    'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',
                    'V8-Version': '12.4.254.16',
                    'WebKit-Version': '537.36 (@7741f71cbce3b7dfe3eb5890976d2596364b0733)',
                    webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/browser'
                },
                pages: [
                    {
                        description: '',
                        devtoolsFrontendUrl: 'https://chrome-devtools-frontend.appspot.com/serve_internal_file/@7741f71cbce3b7dfe3eb5890976d2596364b0733/inspector.html?ws=127.0.0.1:10900/devtools/page/0',
                        id: '0',
                        title: 'Android | Get more done with Google on Android-phones and devices',
                        type: 'page',
                        url: 'https://www.android.com/',
                        webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/page/0'
                    }
                ],
                'webviewName': 'WEBVIEW_chrome'
            },
            {
                proc: '@chrome_devtools_remote',
                webview: 'WEBVIEW_373737',
                info: {
                    'Android-Package': 'com.no.webviews',
                    Browser: 'Chrome/124.0.6367.219',
                    'Protocol-Version': '1.3',
                    'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',
                    'V8-Version': '12.4.254.16',
                    'WebKit-Version': '537.36 (@7741f71cbce3b7dfe3eb5890976d2596364b0733)',
                    webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/browser'
                },
                pages: [
                    {
                        description: '{"attached":true,"empty":true,"height":2682,"never_attached":false,"screenX":0,"screenY":144,"visible":false,"width":1440}',
                        devtoolsFrontendUrl: 'https://chrome-devtools-frontend.appspot.com/serve_internal_file/@2d54072eb2f350d37f3f304c4ba0fafcddbd7e82/inspector.html?ws=127.0.0.1:10900/devtools/page/6751C1E052A63B0CA27F839216AEF4B8',
                        faviconUrl: 'https://emptypage.io/img/favicon.png',
                        id: '6751C1E052A63B0CA27F839216AEF4B8',
                        title: 'Empty Page',
                        type: 'page',
                        url: 'https://emptypage.io/',
                        webSocketDebuggerUrl: 'ws://127.0.0.1:10900/devtools/page/6751C1E052A63B0CA27F839216AEF4B8'
                    },
                ],
                'webviewName': 'WEBVIEW_com.no.webviews'
            }
        ]
        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 no options are provided', async () => {
        logSpy = vi.spyOn(log, 'info')
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
            } as any
        })
        await browser.getContexts()
        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/contexts')
        expect(logSpy).toHaveBeenCalledWith('The standard Appium `contexts` method is used. If you want to get more detailed data, you can set `returnDetailedContexts` to `true`.')
        logSpy.mockRestore()
    })

    it('should call the default Appium endpoint if returnDetailedContexts is not provided', async () => {
        logSpy = vi.spyOn(log, 'info')
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
            } as any
        })
        await browser.getContexts({})
        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/contexts')
        expect(logSpy).toHaveBeenCalledWith('The standard Appium `contexts` method is used. If you want to get more detailed data, you can set `returnDetailedContexts` to `true`.')
        logSpy.mockRestore()
    })

    it('should call the default Appium endpoint if returnDetailedContexts is set to false', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
            } as any
        })
        await browser.getContexts({ returnDetailedContexts: false })
        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/contexts')
    })

    it('should return the detailed contexts for iOS if returnDetailedContexts is set to true', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
                platformName: 'iOS',
            } as any
        })
        const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue(iOSContexts)
        const contexts = await browser.getContexts({ returnDetailedContexts: true })

        expect(executeSpy).toHaveBeenCalledTimes(1)
        expect(executeSpy).toHaveBeenCalledWith('mobile: getContexts')
        expect(contexts).toEqual(iOSContexts)
    })

    it('should return the detailed contexts for Android if returnDetailedContexts is set to true', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
                platformName: 'Android',
            } as any
        })
        const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue(androidChromeInternalContexts)
        const currentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.wdiodemoapp')
        const contexts = await browser.getContexts({ returnDetailedContexts: true })

        expect(executeSpy).toHaveBeenCalledTimes(1)
        expect(currentPackageSpy).toHaveBeenCalledTimes(1)
        expect(contexts).toMatchSnapshot()
    })

    it('should return the description data for Android if returnAndroidDescriptionData is set to true', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
                platformName: 'Android',
            } as any
        })
        const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue(androidChromeInternalContexts)
        const currentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.wdiodemoapp')
        const contexts = await browser.getContexts({
            returnAndroidDescriptionData: true,
            returnDetailedContexts: true,
        })

        expect(executeSpy).toHaveBeenCalledTimes(1)
        expect(currentPackageSpy).toHaveBeenCalledTimes(1)
        expect(contexts).toMatchSnapshot()
    })

    it('should return the current app data for Android if filterByCurrentAndroidApp is set to true', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
                platformName: 'Android',
            } as any
        })
        const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue(androidChromeInternalContexts)
        const currentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.wdiodemoapp')
        const contexts = await browser.getContexts({
            filterByCurrentAndroidApp: true,
            returnDetailedContexts: true,
        })

        expect(executeSpy).toHaveBeenCalledTimes(1)
        expect(currentPackageSpy).toHaveBeenCalledTimes(1)
        expect(contexts).toMatchSnapshot()
    })

    it('should return the visible app data for Android if isAndroidWebviewVisible is set to true', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
                platformName: 'Android',
            } as any
        })
        const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue(androidChromeInternalContexts)
        const currentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.wdiodemoapp')
        const contexts = await browser.getContexts({
            isAndroidWebviewVisible: true,
            returnDetailedContexts: true,
        })

        expect(executeSpy).toHaveBeenCalledTimes(1)
        expect(currentPackageSpy).toHaveBeenCalledTimes(1)
        expect(contexts).toMatchSnapshot()
    })

    it('should return the all app data for Android if isAndroidWebviewVisible is set to false', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
                platformName: 'Android',
            } as any
        })
        const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue(androidChromeInternalContexts)
        const currentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.wdiodemoapp')
        const contexts = await browser.getContexts({
            isAndroidWebviewVisible: false,
            returnDetailedContexts: true,
        })

        expect(executeSpy).toHaveBeenCalledTimes(1)
        expect(currentPackageSpy).toHaveBeenCalledTimes(1)
        expect(contexts).toMatchSnapshot()
    })

    it('should return the all app data for Android if isAndroidWebviewVisible is set to false and returnAndroidDescriptionData is set to true', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
                platformName: 'Android',
            } as any
        })
        const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue(androidChromeInternalContexts)
        const currentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.wdiodemoapp')
        const contexts = await browser.getContexts({
            isAndroidWebviewVisible: false,
            returnAndroidDescriptionData: true,
            returnDetailedContexts: true,
        })

        expect(executeSpy).toHaveBeenCalledTimes(1)
        expect(currentPackageSpy).toHaveBeenCalledTimes(1)
        expect(contexts).toMatchSnapshot()
    })

    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.getContexts()).rejects.toThrow('The `getContexts` command is only available for mobile platforms.')
    })

    it('should throw an error when the current app does not match', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
                platformName: 'Android',
            } as any
        })
        const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue(androidChromeInternalContexts)
        const currentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.nonMatchingApp')
        await expect(browser.getContexts({
            // Reduce the timeout to speed up the test
            androidWebviewConnectTimeout: 5,
            androidWebviewConnectionRetryTime: 5,
            isAndroidWebviewVisible: false,
            returnAndroidDescriptionData: true,
            returnDetailedContexts: true,
        })).rejects.toThrow('The packageName \'com.nonMatchingApp\' could not be found!')

        expect(executeSpy).toHaveBeenCalledTimes(1)
        expect(currentPackageSpy).toHaveBeenCalledTimes(1)
    })

    it('should throw an error when the current app does not have any webviews', async () => {
        browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
                platformName: 'Android',
            } as any
        })
        const executeSpy = vi.spyOn(browser, 'execute').mockResolvedValue([androidChromeInternalContexts[0], androidChromeInternalContexts[androidChromeInternalContexts.length - 1]])
        const currentPackageSpy = vi.spyOn(browser, 'getCurrentPackage').mockResolvedValue('com.no.webviews')
        await expect(browser.getContexts({
            // Reduce the timeout to speed up the test
            androidWebviewConnectTimeout: 5,
            androidWebviewConnectionRetryTime: 5,
            isAndroidWebviewVisible: false,
            returnAndroidDescriptionData: true,
            returnDetailedContexts: true,
        })).rejects.toThrowErrorMatchingSnapshot()

        expect(executeSpy).toHaveBeenCalledTimes(1)
        expect(currentPackageSpy).toHaveBeenCalledTimes(1)
    })
})