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()
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()
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 })
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 })
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 })
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')
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()
})
})