🏡 index : quine-global/webdriverio.git

import path from 'node:path'
import { expect, describe, it, afterEach, vi } from 'vitest'
import logger from '@wdio/logger'
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('element tap test', () => {
    it('should log a warning when the tap command for mobile web is executed and options are provided', async () => {
        const logSpy = vi.spyOn(log, 'warn')
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
            } as any
        })
        const elem = await browser.$('#foo')
        const clickSpy = vi.spyOn(elem, 'click').mockResolvedValue(undefined)

        await elem.tap({ maxScrolls: 3 })

        expect(clickSpy).toHaveBeenCalledWith()
        expect(logSpy).toHaveBeenCalledWith('The options object is not supported in Web environments and will be ignored.')

        clickSpy.mockRestore()
    })

    it('should call the click command for mobile web when a tap is executed where no options are provided', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
            } as any
        })
        const elem = await browser.$('#foo')
        const clickSpy = vi.spyOn(elem, 'click').mockResolvedValue(undefined)

        await elem.tap()

        expect(clickSpy).toHaveBeenCalledWith()

        clickSpy.mockRestore()
    })

    it('should call the Android mobile tap command for native mobile', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                app: 'app',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })
        const elem = await browser.$('#foo')
        const clickSpy = vi.spyOn(elem, 'click').mockResolvedValue(undefined)

        await elem.tap()

        // @ts-expect-error mock implementation
        expect(vi.mocked(fetch).mock.calls[2][0].pathname)
            .toBe('/session/foobar-123/execute/sync')
        expect(JSON.parse(vi.mocked(fetch).mock.calls[2][1]?.body as any))
            .toMatchSnapshot()

        clickSpy.mockRestore()
    })

    it('should call the iOS mobile tap command for native mobile', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'iOS',
                app: 'app',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })
        const elem = await browser.$('#foo')
        const clickSpy = vi.spyOn(elem, 'click').mockResolvedValue(undefined)

        await elem.tap()

        // @ts-expect-error mock implementation
        expect(vi.mocked(fetch).mock.calls[2][0].pathname)
            .toBe('/session/foobar-123/execute/sync')
        expect(JSON.parse(vi.mocked(fetch).mock.calls[2][1]?.body as any))
            .toMatchSnapshot()

        clickSpy.mockRestore()
    })

    it('should call the scrollIntoView command when the element is not in view', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })
        const elem = await browser.$('#foo')
        const nativeTapSpy = vi.spyOn(browser, 'execute')
            .mockRejectedValueOnce(
                new Error('no such element')
            )
            .mockResolvedValueOnce(undefined)
        const scrollSpy = vi.spyOn(elem, 'scrollIntoView').mockResolvedValue(undefined)

        await elem.tap()

        expect(scrollSpy).toHaveBeenCalledWith({})
        expect(nativeTapSpy).toHaveBeenCalledTimes(2)

        nativeTapSpy.mockRestore()
        scrollSpy.mockRestore()
    })

    it('should call the scrollIntoView which throws a mocked error and the execution stops', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })
        const elem = await browser.$('#foo')
        const nativeTapSpy = vi.spyOn(browser, 'execute')
            .mockRejectedValueOnce(
                new Error('no such element')
            )
        const scrollSpy = vi.spyOn(elem, 'scrollIntoView')
            .mockRejectedValueOnce(
                new Error('scroll failed')
            )

        await expect(elem.tap()).rejects.toThrow('scroll failed')
        expect(nativeTapSpy).toHaveBeenCalledTimes(1)

        nativeTapSpy.mockRestore()
        scrollSpy.mockRestore()
    })

    it('should call the scrollIntoView which throws a `Element not found within scroll limit of` error', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })
        const elem = await browser.$('#foo')
        const nativeTapSpy = vi.spyOn(browser, 'execute')
            .mockRejectedValueOnce(
                new Error('no such element')
            )
            .mockResolvedValueOnce(undefined)
        const scrollSpy = vi.spyOn(elem, 'scrollIntoView')
            .mockRejectedValueOnce(
                new Error('Element not found within scroll limit of')
            )

        await expect(elem.tap()).rejects.toThrowErrorMatchingSnapshot()
        expect(nativeTapSpy).toHaveBeenCalledTimes(1)

        nativeTapSpy.mockRestore()
        scrollSpy.mockRestore()
    })

    it('should call the scrollIntoView which throws a `Default scrollable element` error', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })
        const elem = await browser.$('#foo')
        const nativeTapSpy = vi.spyOn(browser, 'execute')
            .mockRejectedValueOnce(
                new Error('no such element')
            )
            .mockResolvedValueOnce(undefined)
        const scrollSpy = vi.spyOn(elem, 'scrollIntoView')
            .mockRejectedValueOnce(
                new Error('Default scrollable element \'`-ios predicate string:type == "XCUIElementTypeApplication`\' was not found')
            )

        await expect(elem.tap()).rejects.toThrowErrorMatchingSnapshot()
        expect(nativeTapSpy).toHaveBeenCalledTimes(1)

        nativeTapSpy.mockRestore()
        scrollSpy.mockRestore()
    })

    it('should call the scrollIntoView which throws a `Default scrollable element` error for an unknown default selector', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })
        const elem = await browser.$('#foo')
        const nativeTapSpy = vi.spyOn(browser, 'execute')
            .mockRejectedValueOnce(
                new Error('no such element')
            )
            .mockResolvedValueOnce(undefined)
        const scrollSpy = vi.spyOn(elem, 'scrollIntoView')
            .mockRejectedValueOnce(
                new Error('Default scrollable element `foo-bar` was not found')
            )

        await expect(elem.tap()).rejects.toThrowErrorMatchingSnapshot()
        expect(nativeTapSpy).toHaveBeenCalledTimes(1)

        nativeTapSpy.mockRestore()
        scrollSpy.mockRestore()
    })

    afterEach(() => {
        vi.mocked(fetch).mockClear()
    })
})

describe('screen tap test', () => {
    it('should call the Android mobile tap command for native mobile', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                app: 'app',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })

        await browser.tap({ x: 10, y: 20 })

        // @ts-expect-error mock implementation
        expect(vi.mocked(fetch).mock.calls[1][0].pathname)
            .toBe('/session/foobar-123/execute/sync')
        expect(JSON.parse(vi.mocked(fetch).mock.calls[1][1]?.body as any))
            .toMatchSnapshot()
    })

    it('should call the iOS mobile tap command for native mobile', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'iOS',
                app: 'app',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })

        await browser.tap({ x: 10, y: 20 })

        // @ts-expect-error mock implementation
        expect(vi.mocked(fetch).mock.calls[1][0].pathname)
            .toBe('/session/foobar-123/execute/sync')
        expect(JSON.parse(vi.mocked(fetch).mock.calls[1][1]?.body as any))
            .toMatchSnapshot()
    })

    it('should call the action command for mobile web when a tap is executed with x and y coordinates', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'iOS',
                browserName: 'foobar',
                mobileMode: true,
            } as any
        })

        await browser.tap({ x: 10, y: 20 })

        // @ts-expect-error mock implementation
        expect(vi.mocked(fetch).mock.calls[1][0].pathname)
            .toBe('/session/foobar-123/actions')
        expect(JSON.parse(vi.mocked(fetch).mock.calls[1][1]?.body as any).actions[0])
            .toMatchSnapshot()
    })

    afterEach(() => {
        vi.mocked(fetch).mockClear()
    })
})

describe('generic error test', () => {
    it('should throw an error if tap command is called for a desktop session', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
            } as any
        })
        const elem = await browser.$('#foo')

        await expect(elem.tap()).rejects.toThrowErrorMatchingSnapshot()
    })

    it('should throw an error if the passed argument to the tap command is not an options object', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
            } as any
        })
        const elem = await browser.$('#foo')

        // @ts-expect-error invalid param
        await expect(elem.tap([])).rejects.toThrowErrorMatchingSnapshot()
    })

    it('should throw an error if only x or y is provided', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
            } as any
        })
        const elem = await browser.$('#foo')

        await expect(elem.tap({ x: 50 })).rejects.toThrowErrorMatchingSnapshot()
        await expect(elem.tap({ y: 50 })).rejects.toThrowErrorMatchingSnapshot()
    })

    it('should throw an error if x and y are provided along with other arguments', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                browserName: 'foobar',
                mobileMode: true,
            } as any
        })
        const elem = await browser.$('#foo')

        await expect(elem.tap({ x: 50, y: 100, direction: 'down' })).rejects.toThrowErrorMatchingSnapshot()
    })

    it('should throw an error if invalid coordinates are provided for screen tap', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })

        await expect(browser.tap({ x: -10, y: 50 }))
            .rejects.toThrow('The x value must be positive.')
    })

    it('should throw an error if invalid coordinates are provided for screen tap', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })

        await expect(browser.tap({ x: 10, y: -50 }))
            .rejects.toThrow('The y value must be positive.')
    })

    it('should throw an error if invalid coordinates are provided for screen tap', async () => {
        const browser = await remote({
            baseUrl: 'http://foobar.com',
            capabilities: {
                platformName: 'Android',
                mobileMode: true,
                nativeAppMode: true,
            } as any
        })

        await expect(browser.tap({ x: -10, y: -50 }))
            .rejects.toThrow('The x and y values must be positive.')
    })

    afterEach(() => {
        vi.mocked(fetch).mockClear()
    })
})