import path from 'node:path'
import { describe, expect, expectTypeOf, test, vi } from 'vitest'
import type { ClickOptions } from '../src/index.js'
import { remote, multiremote } from '../src/index.js'
vi.mock('fetch')
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
const remoteConfig = {
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar'
}
}
const multiremoteConfig = {
browserA: {
logLevel: 'debug',
capabilities: {
browserName: 'chrome'
}
},
browserB: {
logLevel: 'debug',
port: 4445,
capabilities: {
browserName: 'firefox'
}
}
}
const error1 = Error('Thrown 1!')
const error2 = new Error('Thrown 2!')
const customElementCommand = async (origCmd: Function, origCmdArg: any[], arg: any) => {
const result = await new Promise(
(resolve) => setTimeout(async () => resolve(await origCmd(origCmdArg)), 1))
return `${result} ${origCmdArg} ${arg}`
}
const customBrowserCommand = async (origCmd: Function, origCmdArg: number, arg = 0) => {
const start = Date.now() - 1
await origCmd(origCmdArg + arg)
return Date.now() - start
}
describe('overwriteCommand', () => {
describe('remote', () => {
describe('given browser scope', () => {
test('should be able to handle async', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand('pause', customBrowserCommand)
expect(await browser.pause(10, 10)).toBeGreaterThanOrEqual(20)
})
test('should be able to handle async', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand('pause', customBrowserCommand)
expect(await browser.pause(10, 10)).toBeGreaterThanOrEqual(20)
})
test('should still work on browser calls after fetching an element', async () => {
const browser = await remote(remoteConfig)
await browser.$('#foo')
browser.overwriteCommand('pause', customBrowserCommand)
expect(await browser.pause(9)).toBeGreaterThanOrEqual(9)
})
test('should properly throw exceptions on the browser scope', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand('waitUntil', function () {
throw error1
})
browser.overwriteCommand('url', async function () {
await browser.$('#foo')
throw error2
})
await expect(() => browser.waitUntil()).rejects.toThrow(error1)
await expect(browser.url()).rejects.toThrow(error2)
})
test('should resolve return type properly', async () => {
const browser = await remote(remoteConfig)
let origCommand
browser.overwriteCommand(
'pause',
async function (originalFunction ) {
origCommand = originalFunction
const promise: Promise<void> = originalFunction(10)
return await promise
},
false,
)
await expect(browser.pause()).resolves.toBeUndefined()
expect(origCommand).toBeDefined()
})
test('should get a ts error and runtime error when trying to overwrite an non-existing command', async () => {
const browser = await remote(remoteConfig)
expect(() => browser.overwriteCommand(
'click',
async function () {}
)).toThrow('overwriteCommand: no command to be overwritten: click')
})
test('should infer properly the argument this of the func to the Browser type', async () => {
const browser = await remote(remoteConfig)
vi.spyOn(browser, 'scroll')
browser.overwriteCommand('pause', async function (this) {
this.scroll(0, 100)
})
await browser.pause(10)
expect(browser.scroll).toHaveBeenCalledTimes(1)
})
})
describe('given element scope', () => {
const isElementScope = true
test('should propagate element commands for all prototypes', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand('getAttribute', customElementCommand, isElementScope)
const elems = await browser.$$('.someRandomElement')
expect(await elems[0].getAttribute('0', 'q')).toBe('0-value 0 q')
expect(await elems[1].getAttribute('1', 'w')).toBe('1-value 1 w')
expect(await elems[2].getAttribute('2', 'e')).toBe('2-value 2 e')
})
test('should propagate element commands to sub elements', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand('getAttribute', customElementCommand, isElementScope)
const elem = await browser.$('#foo')
const subElem = await elem.$('.subElem')
expect(await subElem.getAttribute('bar', 'foo')).toBe('bar-value bar foo')
})
test('should be able to overwrite element command', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand('getAttribute', customElementCommand, isElementScope)
const elem = await browser.$('#foo')
expect(await elem.getAttribute('foo', 'bar')).toBe('foo-value foo bar')
})
test('should properly throw exceptions on the element scope', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand('click', function () {
throw error1
}, isElementScope)
browser.overwriteCommand('waitForDisplayed', async function () {
await browser.$('#foo')
throw error2
}, isElementScope)
const elem = await browser.$('#foo')
await expect(() => elem.click()).rejects.toThrow(error1)
await expect(elem.waitForDisplayed()).rejects.toThrow(error2)
})
test('should resolve the return of the original command function type properly', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand(
'getText',
async function (originalFunction ) {
const text: string = await originalFunction()
return text + ' - overwritten'
},
isElementScope,
)
const element = await browser.$('.someRandomElement')
vi.spyOn(element, 'getElementText').mockResolvedValue('some text')
expect(await element.getText()).toBe('some text - overwritten')
})
test('should resolve the parameters of the original command function type properly', async () => {
const browser = await remote(remoteConfig)
let origFunction
browser.overwriteCommand(
'click',
async function (originalFunction ) {
origFunction = originalFunction
expectTypeOf<Parameters<typeof originalFunction>[0]>().toEqualTypeOf<Partial<ClickOptions> | undefined>()
const clickOptions: Partial<ClickOptions> = { skipRelease: true }
return originalFunction(clickOptions)
},
isElementScope,
)
const element = await browser.$('.someRandomElement')
await expect(element.click()).resolves.toBeUndefined()
expect(origFunction).toBeDefined()
})
test('should resolve the this parameters type by inference automatically', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand(
'click',
async function (this ) {
return this.getText()
},
isElementScope,
)
const element = await browser.$('.someRandomElement')
vi.spyOn(element, 'getText')
vi.spyOn(element, 'getElementText').mockResolvedValue('some text')
expect(await element.click()).toBe('some text')
expect(element.getText).toHaveBeenCalledTimes(1)
})
test('should get a ts error error when trying to overwrite an non-existing command', async () => {
const browser = await remote(remoteConfig)
browser.overwriteCommand(
'press',
async function () {},
isElementScope)
})
})
})
describe('multiremote', () => {
test('should allow to overwrite commands', async () => {
const browser = await multiremote(multiremoteConfig as any)
browser.overwriteCommand('pause', customBrowserCommand)
expect(await browser.pause(10, 10)).toBeGreaterThanOrEqual(20)
})
test.skip('should allow to overwrite commands for a single multiremote instance', async () => {
const browser = await multiremote(multiremoteConfig as any)
browser.getInstance('browserA').overwriteCommand('pause', customBrowserCommand)
expect(await browser.browserA.pause(10, 10)).toBeGreaterThanOrEqual(20)
expect(await browser.browserB.pause(10)).toBe(undefined)
const results = await browser.pause(10)
expect(results[0]).toBeGreaterThanOrEqual(10)
expect(results[1]).toBe(undefined)
})
test('should be able to overwrite element command in multiremote mode', async () => {
const browser = await multiremote(multiremoteConfig as any)
browser.overwriteCommand('getAttribute', customElementCommand, true)
const elem = await browser.$('#foo')
expect(await elem.getAttribute('foo', 'bar')).toEqual([
'foo-value foo bar', 'foo-value foo bar'
])
})
})
})