import { EventEmitter } from 'node:events' import { describe, it, expect, vi } from 'vitest' import { type local, type remote } from 'webdriver' import WebDriverInterception from '../../../src/utils/interception/index.js' import logger from '@wdio/logger' describe('WebDriverInterception', () => { it('initiate', async () => { const browser: any = { on: vi.fn(), sessionSubscribe: vi.fn().mockReturnValue(Promise.resolve()), networkAddIntercept: vi.fn().mockReturnValue(Promise.resolve({ intercept: '123' })) } const mock = await WebDriverInterception.initiate('http://foobar.com:1234/foo/bar.html?foo=bar', { method: 'GET', requestHeaders: { foo: 'bar' }, responseHeaders: { bar: 'foo' }, statusCode: 200 }, browser) expect(mock).toBeInstanceOf(WebDriverInterception) expect(browser.on).toHaveBeenCalledTimes(2) expect(browser.sessionSubscribe).toHaveBeenCalledTimes(1) expect(browser.networkAddIntercept).toHaveBeenCalledTimes(1) expect(browser.networkAddIntercept.mock.calls).toMatchInlineSnapshot(` [ [ { "phases": [ "beforeRequestSent", "responseStarted", ], "urlPatterns": [ { "hostname": "foobar.com", "pathname": "/foo/bar.html", "port": "1234", "protocol": "http", "search": "foo=bar", "type": "pattern", }, ], }, ], ] `) }) it('handleBeforeRequestSent', async () => { const browser: any = new EventEmitter() browser.sessionSubscribe = vi.fn().mockReturnValue(Promise.resolve()) browser.networkAddIntercept = vi.fn().mockReturnValue(Promise.resolve({ intercept: '123' })) browser.networkContinueRequest = vi.fn().mockReturnValue(Promise.resolve()) browser.networkFailRequest = vi.fn().mockReturnValue(Promise.resolve()) const mock = await WebDriverInterception.initiate('http://foobar.com:1234/foo/bar.html?foo=bar', { method: 'GET', requestHeaders: { foo: 'bar' }, responseHeaders: { bar: 'foo' }, statusCode: 200 }, browser) browser.emit('network.beforeRequestSent', { request: { url: 'http://foobar.com:1234/foo/bar.html?foo=bar', method: 'GET', headers: [{ name: 'foo', value: { type: 'string', value: 'bar' } }] } }) expect(browser.networkFailRequest).toHaveBeenCalledTimes(0) expect(browser.networkContinueRequest).toHaveBeenCalledTimes(0) mock.abort() browser.emit('network.beforeRequestSent', { isBlocked: true, request: { request: 123, url: 'http://foobar.com:1234/foo/bar.html?foo=bar', method: 'POST', headers: [] } }) expect(browser.networkFailRequest).toHaveBeenCalledTimes(0) expect(browser.networkContinueRequest).toHaveBeenCalledWith({ request: 123 }) browser.networkContinueRequest.mockClear() browser.networkFailRequest.mockClear() mock.reset() const blockedRequest = { isBlocked: true, request: { request: 123, url: 'http://foobar.com:1234/foo/bar.html?foo=bar', method: 'GET', headers: [{ name: 'foo', value: { type: 'string', value: 'bar' } }] } } browser.emit('network.beforeRequestSent', blockedRequest) expect(browser.networkFailRequest).toHaveBeenCalledTimes(0) expect(browser.networkContinueRequest).toHaveBeenCalledTimes(1) expect(browser.networkContinueRequest).toHaveBeenCalledWith({ request: 123 }) browser.networkContinueRequest.mockClear() mock.redirect('https://webdriver.io') browser.emit('network.beforeRequestSent', blockedRequest) expect(browser.networkFailRequest).toHaveBeenCalledTimes(0) expect(browser.networkContinueRequest).toHaveBeenCalledWith({ request: 123, url: 'https://webdriver.io' }) browser.networkContinueRequest.mockClear() mock.abort() browser.emit('network.beforeRequestSent', blockedRequest) expect(browser.networkFailRequest).toHaveBeenCalledWith({ request: 123 }) expect(browser.networkContinueRequest).toHaveBeenCalledTimes(0) browser.networkContinueRequest.mockClear() browser.networkFailRequest.mockClear() mock.request({ method: 'POST', headers: { foo: 'bar' }, body: 'foobar' }) browser.emit('network.beforeRequestSent', blockedRequest) expect(browser.networkFailRequest).toHaveBeenCalledTimes(0) expect(browser.networkContinueRequest.mock.calls).toMatchInlineSnapshot(` [ [ { "body": { "type": "string", "value": "foobar", }, "headers": [ { "name": "foo", "value": { "type": "string", "value": "bar", }, }, ], "method": "POST", "request": 123, }, ], ] `) browser.networkContinueRequest.mockClear() browser.networkFailRequest.mockClear() /** * allows to overwrite mock behavior, e.g. by having a mock to abort */ mock.abort() browser.emit('network.beforeRequestSent', blockedRequest) expect(browser.networkFailRequest).toHaveBeenCalledWith({ request: 123 }) expect(browser.networkContinueRequest).toHaveBeenCalledTimes(0) browser.networkContinueRequest.mockClear() browser.networkFailRequest.mockClear() mock.requestOnce({ method: 'POST', headers: { foo: 'bar' }, body: 'foobar' }) mock.abortOnce() browser.emit('network.beforeRequestSent', blockedRequest) expect(browser.networkFailRequest).toHaveBeenCalledTimes(0) expect(browser.networkContinueRequest.mock.calls).toMatchInlineSnapshot(` [ [ { "body": { "type": "string", "value": "foobar", }, "headers": [ { "name": "foo", "value": { "type": "string", "value": "bar", }, }, ], "method": "POST", "request": 123, }, ], ] `) browser.networkContinueRequest.mockClear() browser.networkFailRequest.mockClear() browser.emit('network.beforeRequestSent', blockedRequest) expect(browser.networkFailRequest).toHaveBeenCalledWith({ request: 123 }) expect(browser.networkContinueRequest).toHaveBeenCalledTimes(0) browser.networkContinueRequest.mockClear() browser.networkFailRequest.mockClear() browser.emit('network.beforeRequestSent', blockedRequest) expect(browser.networkContinueRequest).toHaveBeenCalledWith({ request: 123 }) expect(browser.networkFailRequest).toHaveBeenCalledTimes(0) browser.networkContinueRequest.mockClear() browser.networkFailRequest.mockClear() const binaryBody = Buffer.from('binary data') mock.request({ body: binaryBody }) browser.emit('network.beforeRequestSent', blockedRequest) expect(browser.networkFailRequest).toHaveBeenCalledTimes(0) expect(browser.networkContinueRequest).toHaveBeenCalledWith({ request: 123, body: { type: 'base64', value: Buffer.from(JSON.stringify(binaryBody)).toString('base64') } }) }) it('handleResponseStarted', async () => { const browser: any = new EventEmitter() browser.sessionSubscribe = vi.fn().mockReturnValue(Promise.resolve()) browser.networkProvideResponse = vi.fn().mockReturnValue(Promise.resolve()) browser.networkAddIntercept = vi.fn().mockReturnValue(Promise.resolve({ intercept: '123' })) const mock = await WebDriverInterception.initiate('http://foobar.com:1234/foo/bar.html?foo=bar', { method: 'get', requestHeaders: { foo: 'bar' }, responseHeaders: { bar: 'foo' }, statusCode: 200 }, browser) browser.emit('network.responseStarted', { request: { url: 'http://foobar.com:1234/foo/bar.html?foo=bar', method: 'GET', headers: [{ name: 'foo', value: { type: 'string', value: 'bar' } }] } }) expect(browser.networkProvideResponse).toHaveBeenCalledTimes(0) browser.emit('network.responseStarted', { isBlocked: true, request: { url: 'http://foobar.com:1234/foo/bar.html?foo=bar', method: 'GET', request: 123, headers: [{ name: 'foo', value: { type: 'string', value: 'bar' } }] } }) expect(browser.networkProvideResponse).toHaveBeenCalledTimes(1) expect(browser.networkProvideResponse).toHaveBeenCalledWith({ request: 123, }) vi.mocked(browser.networkProvideResponse).mockClear() mock.respondOnce({ foo: 'bar' }) browser.emit('network.responseStarted', { isBlocked: true, request: { url: 'http://foobar.com:1234/foo/bar.html?foo=bar', method: 'GET', request: 123, headers: [{ name: 'foo', value: { type: 'string', value: 'bar' } }] } }) expect(browser.networkProvideResponse).toHaveBeenCalledTimes(1) expect(browser.networkProvideResponse).toHaveBeenCalledWith({ request: 123, body: { type: 'string', value: '{"foo":"bar"}' } }) browser.emit('network.responseStarted', { isBlocked: true, request: { url: 'http://foobar.com:1234/foo/bar.html?foo=bar', method: 'GET', request: 123, headers: [{ name: 'foo', value: { type: 'string', value: 'bar' } }] } }) expect(browser.networkProvideResponse).toHaveBeenCalledTimes(2) expect(browser.networkProvideResponse).toHaveBeenCalledWith({ request: 123 }) vi.mocked(browser.networkProvideResponse).mockClear() mock.clear() vi.mocked(browser.networkProvideResponse).mockClear() mock.respondOnce('hello') browser.emit('network.responseStarted', { isBlocked: true, request: { url: 'http://foobar.com:1234/foo/bar.html?foo=bar', method: 'get', request: 123, headers: [{ name: 'foo', value: { type: 'string', value: 'bar' } }] }, response: { status: 200, headers: [{ name: 'bar', value: { type: 'string', value: 'foo' } }] } }) expect(browser.networkProvideResponse).toHaveBeenCalledTimes(1) expect(browser.networkProvideResponse).toHaveBeenCalledWith({ request: 123, body: { type: 'string', value: 'hello' } }) expect(mock.getBinaryResponse('123')).toBeNull() }) it('handles non-binary response correctly', async () => { const browser: any = new EventEmitter() browser.sessionSubscribe = vi.fn().mockReturnValue(Promise.resolve()) browser.networkProvideResponse = vi.fn().mockReturnValue(Promise.resolve()) browser.networkAddIntercept = vi.fn().mockReturnValue(Promise.resolve({ intercept: 'mock-id' })) const mock = await WebDriverInterception.initiate('http://localhost/test/*', {}, browser) const mockResponse = { data: { someProperty: 123 } } mock.respond(mockResponse) const mockRequest: local.NetworkResponseCompletedParameters = { context: 'mock-context', navigation: null, redirectCount: 0, timestamp: 0, request: { request: 'req-123', url: 'http://localhost/test/api', method: 'POST', headers: [], cookies: [], headersSize: 0, bodySize: 0, timings: { timeOrigin: 0, requestTime: 0, redirectStart: 0, redirectEnd: 0, fetchStart: 0, dnsStart: 0, dnsEnd: 0, connectStart: 0, connectEnd: 0, tlsStart: 0, requestStart: 0, responseStart: 0, responseEnd: 0, }, }, response: { url: 'http://localhost/test/api', status: 200, headers: [], mimeType: 'application/json', bytesReceived: 0, headersSize: 0, bodySize: 0, content: { size: 0 }, protocol: 'http', statusText: 'OK', fromCache: false, }, isBlocked: true, } mock.simulateResponseStarted(mockRequest) expect(browser.networkProvideResponse).toHaveBeenCalledTimes(1) expect(browser.networkProvideResponse.mock.calls).toMatchInlineSnapshot(` [ [ { "body": { "type": "string", "value": "{"data":{"someProperty":123}}", }, "request": "req-123", }, ], ] `) expect(mock.getBinaryResponse('req-123')).toBeNull() }) it('handles binary response body and clear', async () => { const browser: any = new EventEmitter() browser.sessionSubscribe = vi.fn().mockReturnValue(Promise.resolve()) browser.networkProvideResponse = vi.fn().mockReturnValue(Promise.resolve()) browser.networkAddIntercept = vi.fn().mockReturnValue(Promise.resolve({ intercept: 'mock-id' })) const mock = await WebDriverInterception.initiate('http://localhost/test/*', {}, browser) const binaryData = Buffer.from('binary data') mock.respond(binaryData) const mockRequest: local.NetworkResponseCompletedParameters = { context: 'mock-context', navigation: null, redirectCount: 0, timestamp: 0, request: { request: 'req-123', url: 'http://localhost/test/bin', method: 'GET', headers: [], cookies: [], headersSize: 0, bodySize: 0, timings: { timeOrigin: 0, requestTime: 0, redirectStart: 0, redirectEnd: 0, fetchStart: 0, dnsStart: 0, dnsEnd: 0, connectStart: 0, connectEnd: 0, tlsStart: 0, requestStart: 0, responseStart: 0, responseEnd: 0, }, }, response: { url: 'http://localhost/test/bin', status: 200, headers: [], mimeType: 'application/octet-stream', bytesReceived: 0, headersSize: 0, bodySize: 0, content: { size: 0 }, protocol: 'http', statusText: 'OK', fromCache: false, }, isBlocked: true, } mock.simulateResponseStarted(mockRequest) expect(browser.networkProvideResponse).toHaveBeenCalledTimes(1) expect(browser.networkProvideResponse.mock.calls).toMatchInlineSnapshot(` [ [ { "body": { "type": "base64", "value": "YmluYXJ5IGRhdGE=", }, "request": "req-123", }, ], ] `) expect(mock.getBinaryResponse('req-123')).toEqual(binaryData) mock.clear() expect(mock.getBinaryResponse('req-123')).toBeNull() }) it('handles invalid and unusual base64 data in getBinaryResponse', async () => { const browser: any = new EventEmitter() browser.sessionSubscribe = vi.fn().mockReturnValue(Promise.resolve()) browser.networkProvideResponse = vi.fn().mockReturnValue(Promise.resolve()) browser.networkAddIntercept = vi.fn().mockReturnValue(Promise.resolve({ intercept: 'mock-id' })) const mock = await WebDriverInterception.initiate('http://localhost/test/*', {}, browser) const logWarnSpy = vi.spyOn(logger('WebDriverInterception'), 'warn') mock.debugResponseBodies().set('req-123', { type: 'base64', value: 'invalid!' }) expect(mock.getBinaryResponse('req-123')).toBeNull() expect(logWarnSpy).toHaveBeenCalledTimes(1) expect(logWarnSpy.mock.calls).toMatchInlineSnapshot(` [ [ "Invalid base64 data for request req-123", ], ] `) mock.debugResponseBodies().set('req-123', { type: 'string', value: 'text' }) expect(mock.getBinaryResponse('req-123')).toBeNull() expect(logWarnSpy).toHaveBeenCalledTimes(1) mock.debugResponseBodies().set('req-123', { type: 'base64', value: '' }) expect(mock.getBinaryResponse('req-123')).toEqual(Buffer.from('')) expect(logWarnSpy).toHaveBeenCalledTimes(1) mock.debugResponseBodies().set('req-123', { type: 'base64', value: ' YmluYXJ5IGRhdGE= ' }) expect(mock.getBinaryResponse('req-123')).toEqual(Buffer.from('binary data')) expect(logWarnSpy).toHaveBeenCalledTimes(1) logWarnSpy.mockRestore() }) })