import { URL } from 'node:url' import type { MockedFunction } from 'vitest' import { transformCommandLogResult } from '@wdio/utils' import { describe, it, expect, vi } from 'vitest' import '../../src/browser.js' import { WebDriverResponseError, WebDriverRequestError } from '../../src/request/error.js' const cmdURL = new URL('http://localhost:1234/session/abc123/url') const init = { method: 'POST' } vi.mock('@wdio/utils') describe('WebDriverResponseError', () => { it('captures errors without body', () => { const error = new WebDriverResponseError({} as any, cmdURL, init) expect(error.message).toBe('WebDriverError: Response has empty body when running "url" with method "POST"') expect(error.stack).toMatch('Response has empty body') }) it('captures errors with unknown body', () => { const error = new WebDriverResponseError({ body: {} } as any, cmdURL, init) expect(error.message).toBe('WebDriverError: unknown error when running "url" with method "POST"') expect(error.stack).toMatch('unknown error') }) it('captures Firefox errors', () => { //Firefox const error = new WebDriverResponseError({ body: { value: { error: 'foo', message: 'bar' } } } as any, cmdURL, init) expect(error.name).toBe('foo') expect(error.message).toBe('WebDriverError: bar when running "url" with method "POST"') }) it('captures Chrome errors', () => { const error = new WebDriverResponseError({ body: { value: { message: 'stale element reference' } } } as any, cmdURL, init) expect(error.name).toBe('stale element reference') expect(error.message).toBe('WebDriverError: stale element reference when running "url" with method "POST"') expect(error.stack).toMatch('stale element reference') expect(error.stack).toMatch('stale element reference') }) it('captures errors with message', () => { const error = new WebDriverResponseError({ body: { value: { message: 'message error' } } } as any, cmdURL, init) expect(error.name).toBe('WebDriver Error') expect(error.message).toBe('WebDriverError: message error when running "url" with method "POST"') expect(error.stack).toMatch('WebDriver Error') expect(error.stack).toMatch('message') }) it('captures errors with class', () => { const error = new WebDriverResponseError({ body: { value: { class: 'class error' } } } as any, cmdURL, init) expect(error.name).toBe('WebDriver Error') expect(error.message).toBe('WebDriverError: class error when running "url" with method "POST"') expect(error.stack).toMatch('WebDriver Error') expect(error.stack).toMatch('class') }) it('captures errors with name', () => { const error = new WebDriverResponseError({ body: { value: { name: 'Protocol Error' } } } as any, cmdURL, init) expect(error.name).toBe('Protocol Error') expect(error.message).toBe('WebDriverError: unknown error when running "url" with method "POST"') expect(error.stack).toMatch('Protocol Error') expect(error.stack).toMatch('unknown error') }) it('captures errors without value', () => { const error = new WebDriverResponseError({ body: { value: { } } } as any, cmdURL, init) expect(error.name).toBe('WebDriver Error') expect(error.message).toBe('WebDriverError: unknown error when running "url" with method "POST"') expect(error.stack).toMatch('WebDriver Error') expect(error.stack).toMatch('unknown error') }) it('captures invalid selector errors', () => { const error = new WebDriverResponseError( { body: { value: { message: 'invalid locator' } } } as any, cmdURL, { body: { using: 'css selector', value: '!!' }, method: 'POST' } as any ) expect(error.message).toMatchInlineSnapshot('"WebDriverError: The selector "!!" used with strategy "css selector" is invalid! when running "url" with method "POST" and args "{"using":"css selector","value":"!!"}""') }) it('captures unknown command errors', () => { const error = new WebDriverResponseError( { body: { message: 'Command not found: POST /some/command', error: 'unknown method', name: 'Protocol Error' } } as any, cmdURL, init ) expect(error.message).toMatchInlineSnapshot('"WebDriverError: Command not found: POST /some/command when running "url" with method "POST""') expect(error.name).toBe('unknown method') }) it('command name as full endpoint', async () => { const timeoutErr = new WebDriverResponseError( { body: { message: 'Timeout' } } as any, new URL('https://localhost:4445/wd/hub/session'), { method: 'GET' } ) expect(timeoutErr.message).toEqual(expect.stringMatching('when running "https://localhost:4445/wd/hub/session"')) }) it('command args as stringified object', async () => { const timeoutErr = new WebDriverResponseError( { body: { message: 'Timeout' } } as any, new URL('https://localhost:4445/default/method'), { method: 'GET', body: { foo: 'bar' } as any } ) expect(timeoutErr.message).toEqual( expect.stringMatching(new RegExp(`when running .+ with method .+ and args "${JSON.stringify({ foo: 'bar' })}"`)) ) }) it('command args with base64 script', async () => { (transformCommandLogResult as MockedFunction).mockReturnValueOnce('""') const cmdArgs = { script: Buffer.from('script').toString('base64') } const reqOpts = { body: cmdArgs, method: 'POST' } const timeoutErr = new WebDriverResponseError( { body: { message: 'Timeout' } } as any, new URL('https://localhost:4445/default/method'), reqOpts as any ) expect(timeoutErr.message).toEqual( expect.stringMatching(/when running .+ with method .+ and args ""/) ) }) it('command args with function script without extra wrapper', async () => { const cmdArgs = { script: 'return (function() {\nconsole.log("hi")\n}).apply(null, arguments)' } const reqOpts = { body: cmdArgs, method: 'POST' } const timeoutErr = new WebDriverResponseError( { body: { message: 'Timeout' } } as any, new URL('https://localhost:4445/default/method'), reqOpts as any ) expect(timeoutErr.message).toEqual( expect.stringMatching(/when running .+ with method .+ and args "function\(\) {\nconsole\.log\("hi"\)\n}/) ) }) it('command args with base64 screenshot', async () => { (transformCommandLogResult as MockedFunction).mockReturnValueOnce('""') const cmdArgs = { file: Buffer.from('screen').toString('base64') } const reqOpts = { body: cmdArgs, method: 'POST' } const timeoutErr = new WebDriverResponseError( { body: { message: 'Timeout' } } as any, new URL('https://localhost:4445/default/method'), reqOpts as any ) expect(timeoutErr.message).toEqual( expect.stringMatching(/when running .+ with method .+ and args ""/) ) }) }) describe('WebDriverRequestError', () => { const url = new URL('http://localhost:1234') const init: any = { method: 'POST' } it('captures basic error', () => { const error = new WebDriverRequestError(new Error('ups'), url, init) expect(error.message).toBe('WebDriverError: ups when running "http://localhost:1234/" with method "POST"') }) it('captures fetch errors', () => { const err = new Error('fetch failed') err.cause = { code: 'ECONNREFUSED' } const error = new WebDriverRequestError(err, url, init) expect(error.message).toBe('WebDriverError: Request failed with error code ECONNREFUSED when running "http://localhost:1234/" with method "POST"') }) it('captures timeout errors', () => { const err = new Error('fetch failed') err.cause = { code: 'UND_ERR_CONNECT_TIMEOUT' } const error = new WebDriverRequestError(err, url, init) expect(error.message).toBe('WebDriverError: Request timed out! Consider increasing the "connectionRetryTimeout" option. when running "http://localhost:1234/" with method "POST"') }) })