Files
2026-04-06 14:29:03 +02:00

218 lines
5.8 KiB
TypeScript

import type { SetupWorkerApi } from 'msw/browser'
import { HttpServer } from '@open-draft/test-server/lib/http.js'
import type { ConsoleMessages } from 'page-with'
import { test, expect } from '../../../playwright.extend'
declare namespace window {
export const msw: {
worker: SetupWorkerApi
}
}
const ON_EXAMPLE = new URL('./on.mocks.ts', import.meta.url)
const server = new HttpServer((app) => {
app.post('/no-response', (_req, res) => {
res.send('original-response')
})
app.get('/unknown-route', (_req, res) => {
res.status(404)
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) => {
return message.startsWith('[request:start]')
})
return requestStartMessage?.split(' ')?.[3]
}
test.beforeAll(async () => {
await server.listen()
})
test.afterAll(async () => {
await server.close()
})
test('emits events for a handled request and mocked response', async ({
loadExample,
spyOnConsole,
fetch,
}) => {
const consoleSpy = spyOnConsole()
await loadExample(ON_EXAMPLE)
const url = server.http.url('/user')
await fetch(url)
const requestId = getRequestId(consoleSpy)
await expect
.poll(() => consoleSpy.get('warning'))
.toContainEqual(expect.stringContaining('[response:mocked]'))
expect(consoleSpy.get('warning')).toEqual([
`[request:start] GET ${url} ${requestId}`,
`[request:match] GET ${url} ${requestId}`,
`[request:end] GET ${url} ${requestId}`,
`[response:mocked] 400 ${url} response-body GET ${url} ${requestId}`,
])
})
test('emits events for a handled request with no response', async ({
loadExample,
spyOnConsole,
fetch,
}) => {
const consoleSpy = spyOnConsole()
await loadExample(ON_EXAMPLE)
const url = server.http.url('/no-response')
await fetch(url, { method: 'POST' })
const requestId = getRequestId(consoleSpy)
await expect
.poll(() => consoleSpy.get('warning'))
.toContainEqual(expect.stringContaining('[response:bypass]'))
expect(consoleSpy.get('warning')).toEqual([
`[request:start] POST ${url} ${requestId}`,
`[request:match] POST ${url} ${requestId}`,
`[request:end] POST ${url} ${requestId}`,
`[response:bypass] 200 ${url} original-response POST ${url} ${requestId}`,
])
})
test('emits events for an unhandled request', async ({
loadExample,
spyOnConsole,
fetch,
}) => {
const consoleSpy = spyOnConsole()
await loadExample(ON_EXAMPLE)
const url = server.http.url('/unknown-route')
await fetch(url)
const requestId = getRequestId(consoleSpy)
await expect
.poll(() => consoleSpy.get('warning'))
.toContainEqual(expect.stringContaining('[response:bypass]'))
expect(consoleSpy.get('warning')).toEqual([
`[request:start] GET ${url} ${requestId}`,
`[request:unhandled] GET ${url} ${requestId}`,
`[request:end] GET ${url} ${requestId}`,
`[response:bypass] 404 ${url} majestic-unknown GET ${url} ${requestId}`,
])
})
test('emits events for a passthrough request', async ({
loadExample,
spyOnConsole,
fetch,
}) => {
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 expect
.poll(() => consoleSpy.get('warning'))
.toEqual([
`[request:start] GET ${url} ${requestId}`,
`[request:match] GET ${url} ${requestId}`,
`[request:end] GET ${url} ${requestId}`,
`[response:bypass] 200 ${url} passthrough-response GET ${url} ${requestId}`,
])
})
test('emits events for a bypassed request', async ({
loadExample,
spyOnConsole,
fetch,
page,
}) => {
const consoleSpy = spyOnConsole()
await loadExample(ON_EXAMPLE)
const pageErrors: Array<Error> = []
page.on('pageerror', (error) => pageErrors.push(error))
const url = server.http.url('/bypass')
await fetch(url)
// First, must print the events for the original (mocked) request.
await expect
.poll(() => consoleSpy.get('warning'))
.toEqual(
expect.arrayContaining([
expect.stringContaining(`[request:start] GET ${url}`),
expect.stringContaining(`[request:end] GET ${url}`),
expect.stringContaining(
`[response:mocked] 200 ${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] 200 ${url} bypassed-response POST ${url}`,
),
]),
)
expect(pageErrors).toEqual([])
})
test('emits unhandled exceptions in the request handler', async ({
loadExample,
spyOnConsole,
fetch,
}) => {
const consoleSpy = spyOnConsole()
await loadExample(ON_EXAMPLE)
const url = server.http.url('/unhandled-exception')
await fetch(url)
const requestId = getRequestId(consoleSpy)
expect(consoleSpy.get('warning')).toContain(
`[unhandledException] GET ${url} ${requestId} Unhandled resolver error`,
)
})
test('stops emitting events once the worker is stopped', async ({
loadExample,
spyOnConsole,
page,
}) => {
const consoleSpy = spyOnConsole()
await loadExample(ON_EXAMPLE)
await page.evaluate(() => {
return window.msw.worker.stop()
})
const url = server.http.url('/passthrough')
await page.evaluate((url) => fetch(url), url)
expect(consoleSpy.get('warning')).toBeUndefined()
})