mirror of
https://github.com/hanzoai/msw.git
synced 2026-09-24 17:00:34 +00:00
153 lines
3.9 KiB
TypeScript
153 lines
3.9 KiB
TypeScript
import { format } from 'outvariant'
|
|
import { Emitter } from 'strict-event-emitter'
|
|
import { type Page } from '@playwright/test'
|
|
|
|
type WorkerConsoleMessageType =
|
|
| 'log'
|
|
| 'info'
|
|
| 'warn'
|
|
| 'error'
|
|
| 'debug'
|
|
| 'trace'
|
|
| 'dir'
|
|
| 'dirxml'
|
|
| 'table'
|
|
| 'clear'
|
|
| 'startGroup'
|
|
| 'startGroupCollapsed'
|
|
| 'endGroup'
|
|
| 'assert'
|
|
| 'count'
|
|
| 'countReset'
|
|
| 'time'
|
|
| 'timeLog'
|
|
| 'timeEnd'
|
|
| 'group'
|
|
| 'groupCollapsed'
|
|
| 'groupEnd'
|
|
| 'profile'
|
|
| 'profileEnd'
|
|
| 'timeline'
|
|
| 'timelineEnd'
|
|
| 'timeStamp'
|
|
| 'context'
|
|
| 'memory'
|
|
|
|
type WorkerConsoleEventMap = {
|
|
[MessageType in WorkerConsoleMessageType]: [message: string]
|
|
}
|
|
|
|
type InternalWorkerConsoleMessageData = {
|
|
type: 'internal/console'
|
|
payload: {
|
|
messageType: WorkerConsoleMessageType
|
|
args: Array<string>
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
handleIncomingWorkerConsoleMessage: (
|
|
messageType: WorkerConsoleMessageType,
|
|
args: Array<string>,
|
|
) => void
|
|
}
|
|
}
|
|
|
|
export class WorkerConsole extends Emitter<WorkerConsoleEventMap> {
|
|
public messages: Map<WorkerConsoleMessageType, Array<string>> = new Map()
|
|
|
|
private addMessage(
|
|
messageType: WorkerConsoleMessageType,
|
|
message: string,
|
|
): void {
|
|
const messages = this.messages.get(messageType) || []
|
|
this.messages.set(messageType, messages.concat(message))
|
|
}
|
|
|
|
public async init(page: Page): Promise<void> {
|
|
// Ensure the worker is activated before establishing the listener.
|
|
await page.evaluate(() => {
|
|
if (navigator.serviceWorker.controller) {
|
|
// Only await the worker if it actually exists.
|
|
// This prevents the WorkerConsole from throwing
|
|
// when initialized in the scenarios where MSW is
|
|
// expected to fail/skip registration. Respectively,
|
|
// no console message forwarding will be established.
|
|
return navigator.serviceWorker.ready
|
|
}
|
|
})
|
|
|
|
await page.exposeFunction(
|
|
'handleIncomingWorkerConsoleMessage',
|
|
(messageType: WorkerConsoleMessageType, args: Array<string>) => {
|
|
const [template, ...positionals] = args
|
|
const formattedMessage = format(template, ...positionals)
|
|
|
|
this.addMessage(messageType, formattedMessage)
|
|
this.emit(messageType, formattedMessage)
|
|
},
|
|
)
|
|
|
|
await page.evaluate(() => {
|
|
navigator.serviceWorker.addEventListener(
|
|
'message',
|
|
(event: MessageEvent<InternalWorkerConsoleMessageData>) => {
|
|
const { data } = event
|
|
|
|
if (data.type === 'internal/console') {
|
|
const { messageType, args } = data.payload
|
|
window.handleIncomingWorkerConsoleMessage(messageType, args)
|
|
}
|
|
},
|
|
)
|
|
})
|
|
}
|
|
|
|
public removeAllListeners(...args: Array<any>) {
|
|
this.messages.clear()
|
|
return super.removeAllListeners(...args)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @why Service Worker lives in a separate thread and doesn't
|
|
* trigger the Playwright's page console events. No way to access
|
|
* the console events issued in the worker.
|
|
* @see https://github.com/microsoft/playwright/issues/6559
|
|
* @see https://stackoverflow.com/questions/54339039/puppeteer-can-not-listen-service-workers-console
|
|
*/
|
|
export function getWorkerScriptPatch(): string {
|
|
return `
|
|
// EVERYTHING BELOW THIS LINE HAS BEEN APPENDED
|
|
// TO FORWARD WORKER CONSOLE MESSAGES TO THE CLIENTS.
|
|
|
|
async function sendToAll(payload) {
|
|
const clients = await self.clients.matchAll()
|
|
for (const client of clients) {
|
|
client.postMessage(payload)
|
|
}
|
|
}
|
|
|
|
function interceptConsoleCalls(listener) {
|
|
globalThis.console = new Proxy(globalThis.console, {
|
|
get(target, property) {
|
|
if (property in target) {
|
|
return (...args) => {
|
|
listener(property, args)
|
|
return Reflect.get(target, property)
|
|
}
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
interceptConsoleCalls((messageType, args) => {
|
|
sendToAll({
|
|
type: 'internal/console',
|
|
payload: { messageType, args }
|
|
})
|
|
})
|
|
`
|
|
}
|