diff --git a/src/browser/setupWorker/start/createRequestListener.ts b/src/browser/setupWorker/start/createRequestListener.ts index e530e69b0..627617e49 100644 --- a/src/browser/setupWorker/start/createRequestListener.ts +++ b/src/browser/setupWorker/start/createRequestListener.ts @@ -48,7 +48,7 @@ export const createRequestListener = ( context.emitter, { onPassthroughResponse() { - messageChannel.postMessage('NOT_FOUND') + messageChannel.postMessage('PASSTHROUGH') }, async onMockedResponse(response, { handler, parsedResult }) { // Clone the mocked response so its body could be read diff --git a/src/browser/setupWorker/start/createResponseListener.ts b/src/browser/setupWorker/start/createResponseListener.ts index 222635d27..1425c8587 100644 --- a/src/browser/setupWorker/start/createResponseListener.ts +++ b/src/browser/setupWorker/start/createResponseListener.ts @@ -21,6 +21,8 @@ export function createResponseListener(context: SetupWorkerInternalContext) { const request = context.requests.get(requestId)! context.requests.delete(requestId) + console.log('RESPONSE LISTENER', responseJson, context.requests) + /** * CORS requests with `mode: "no-cors"` result in "opaque" responses. * That kind of responses cannot be manipulated in JavaScript due diff --git a/src/browser/setupWorker/start/utils/createMessageChannel.ts b/src/browser/setupWorker/start/utils/createMessageChannel.ts index 2a2cd0d5e..f0638a6cd 100644 --- a/src/browser/setupWorker/start/utils/createMessageChannel.ts +++ b/src/browser/setupWorker/start/utils/createMessageChannel.ts @@ -16,7 +16,7 @@ interface WorkerChannelEventsMap { data: StringifiedResponse, transfer?: [ReadableStream], ] - NOT_FOUND: [] + PASSTHROUGH: [] } export class WorkerChannel { diff --git a/src/core/utils/handleRequest.ts b/src/core/utils/handleRequest.ts index 45f9ebe6f..e685a143b 100644 --- a/src/core/utils/handleRequest.ts +++ b/src/core/utils/handleRequest.ts @@ -52,7 +52,7 @@ export async function handleRequest( ): Promise { emitter.emit('request:start', { request, requestId }) - // Perform bypassed requests (i.e. issued via "ctx.fetch") as-is. + // Perform bypassed requests (i.e. wrapped in "bypass()") as-is. if (request.headers.get('x-msw-intention') === 'bypass') { emitter.emit('request:end', { request, requestId }) handleRequestOptions?.onPassthroughResponse?.(request) diff --git a/src/mockServiceWorker.js b/src/mockServiceWorker.js index 6e6f415c9..04132a385 100644 --- a/src/mockServiceWorker.js +++ b/src/mockServiceWorker.js @@ -206,13 +206,6 @@ async function getResponse(event, client, requestId) { return passthrough() } - // Bypass requests with the explicit bypass header. - // Such requests can be issued by "ctx.fetch()". - const mswIntention = request.headers.get('x-msw-intention') - if (['bypass', 'passthrough'].includes(mswIntention)) { - return passthrough() - } - // Notify the client that a request has been intercepted. const requestBuffer = await request.arrayBuffer() const clientMessage = await sendToClient( @@ -244,7 +237,7 @@ async function getResponse(event, client, requestId) { return respondWithMock(clientMessage.data) } - case 'MOCK_NOT_FOUND': { + case 'PASSTHROUGH': { return passthrough() } } diff --git a/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts b/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts index 5ca89c165..bcc2cdbd0 100644 --- a/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts +++ b/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts @@ -1,4 +1,10 @@ -import { HttpResponse, http, LifeCycleEventsMap } from 'msw' +import { + HttpResponse, + http, + LifeCycleEventsMap, + passthrough, + bypass, +} from 'msw' import { setupWorker } from 'msw/browser' const worker = setupWorker( @@ -8,6 +14,12 @@ const worker = setupWorker( http.post('*/no-response', () => { return }), + http.get('*/passthrough', () => { + return passthrough() + }), + http.get('*/bypass', async ({ request }) => { + return fetch(bypass(request, { method: 'POST' })) + }), http.get('*/unhandled-exception', () => { throw new Error('Unhandled resolver error') }), diff --git a/test/browser/msw-api/setup-worker/life-cycle-events/on.test.ts b/test/browser/msw-api/setup-worker/life-cycle-events/on.test.ts index a4c420c64..d01368db8 100644 --- a/test/browser/msw-api/setup-worker/life-cycle-events/on.test.ts +++ b/test/browser/msw-api/setup-worker/life-cycle-events/on.test.ts @@ -1,4 +1,4 @@ -import type { SetupWorker } from 'msw/lib/browser' +import type { SetupWorker } from 'msw/browser' import { HttpServer } from '@open-draft/test-server/http' import type { ConsoleMessages } from 'page-with' import { test, expect } from '../../../playwright.extend' @@ -11,7 +11,20 @@ declare namespace window { const ON_EXAMPLE = require.resolve('./on.mocks.ts') -let server: HttpServer +const server = new HttpServer((app) => { + app.post('/no-response', (_req, res) => { + res.send('original-response') + }) + app.get('/unknown-route', (_req, res) => { + res.send('majestic-unknown') + }) + app.get('/passthrough', (_req, res) => { + res.send('passthrough-response') + }) + app.post('/bypass', (_req, res) => { + res.send('bypassed-response') + }) +}) export function getRequestId(messages: ConsoleMessages) { const requestStartMessage = messages.get('warning')?.find((message) => { @@ -20,15 +33,12 @@ export function getRequestId(messages: ConsoleMessages) { return requestStartMessage?.split(' ')?.[3] } -test.beforeEach(async ({ createServer }) => { - server = await createServer((app) => { - app.post('/no-response', (req, res) => { - res.send('original-response') - }) - app.get('/unknown-route', (req, res) => { - res.send('majestic-unknown') - }) - }) +test.beforeAll(async () => { + await server.listen() +}) + +test.afterAll(async () => { + await server.close() }) test('emits events for a handled request and mocked response', async ({ @@ -111,6 +121,74 @@ test('emits events for an unhandled request', async ({ ]) }) +test('emits events for a passthrough request', async ({ + loadExample, + spyOnConsole, + fetch, + waitFor, +}) => { + const consoleSpy = spyOnConsole() + await loadExample(ON_EXAMPLE) + + // Explicit "passthrough()" request must go through the + // same request processing pipeline to contain both + // "request" and "response" in the life-cycle event listener. + const url = server.http.url('/passthrough') + await fetch(url) + const requestId = getRequestId(consoleSpy) + + await waitFor(() => { + expect(consoleSpy.get('warning')).toEqual([ + `[request:start] GET ${url} ${requestId}`, + `[request:end] GET ${url} ${requestId}`, + `[response:bypass] ${url} passthrough-response GET ${url} ${requestId}`, + ]) + }) +}) + +test('emits events for a bypassed request', async ({ + loadExample, + spyOnConsole, + fetch, + waitFor, + page, +}) => { + const consoleSpy = spyOnConsole() + await loadExample(ON_EXAMPLE) + + const pageErrors: Array = [] + page.on('pageerror', (error) => pageErrors.push(error)) + + const url = server.http.url('/bypass') + await fetch(url) + + await waitFor(() => { + // First, must print the events for the original (mocked) request. + expect(consoleSpy.get('warning')).toEqual( + expect.arrayContaining([ + expect.stringContaining(`[request:start] GET ${url}`), + expect.stringContaining(`[request:end] GET ${url}`), + expect.stringContaining( + `[response:mocked] ${url} bypassed-response GET ${url}`, + ), + ]), + ) + + // Then, must also print events for the bypassed request. + expect(consoleSpy.get('warning')).toEqual( + expect.arrayContaining([ + expect.stringContaining(`[request:start] POST ${url}`), + expect.stringContaining(`[request:end] POST ${url}`), + expect.stringContaining( + `[response:bypass] ${url} bypassed-response POST ${url}`, + ), + ]), + ) + + expect(pageErrors).toEqual([]) + }) +}) + test('emits unhandled exceptions in the request handler', async ({ loadExample, spyOnConsole,