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) // @ts-expect-error command overwritten 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) // @ts-expect-error command overwritten 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 }) // @ts-expect-error command overwritten await expect(() => browser.waitUntil()).rejects.toThrow(error1) // @ts-expect-error command overwritten 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 /* (milliseconds?: number | undefined) => Promise */) { origCommand = originalFunction const promise: Promise = 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( // @ts-expect-error cannot overwrite non-existing command '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') // @ts-expect-error command overwritten expect(await elems[0].getAttribute('0', 'q')).toBe('0-value 0 q') // @ts-expect-error command overwritten expect(await elems[1].getAttribute('1', 'w')).toBe('1-value 1 w') // @ts-expect-error command overwritten 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') // @ts-expect-error command overwritten 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') // @ts-expect-error command overwritten 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 /* Expecting return a Promise */) { 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 /* Expecting the type to be (options: Partial) => Promise */) { origFunction = originalFunction expectTypeOf[0]>().toEqualTypeOf | undefined>() const clickOptions: Partial = { 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 /* Expect to be WebdriverIO.Element */ ) { 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( // @ts-expect-error cannot overwrite non-existing command 'press', async function () {}, isElementScope) }) }) }) describe('multiremote', () => { test('should allow to overwrite commands', async () => { const browser = await multiremote(multiremoteConfig as any) browser.overwriteCommand('pause', customBrowserCommand) // @ts-expect-error command overwritten 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) // @ts-expect-error command overwritten expect(await browser.browserA.pause(10, 10)).toBeGreaterThanOrEqual(20) // @ts-expect-error command overwritten 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') // @ts-expect-error command overwritten expect(await elem.getAttribute('foo', 'bar')).toEqual([ 'foo-value foo bar', 'foo-value foo bar' ]) }) }) })