Cypress中WebSocket Mock响应写入文件遇异步问题求解决方案
我在使用Cypress Mock WebSocket时,希望把接收到的消息写入文件,但在Socket的事件处理器里执行cy.writeFile()这类异步Cypress命令会报错,需要合适的解决办法。
代码示例
export function trackWebSocketTraffic() { let messages: string[] = [] cy.window().then((win) => { cy.stub(win, 'WebSocketFactory').callsFake( (url: string, handlers: WebSocketEventHandlers) => { console.log('WebSocket URL:', url) const webSocket = new WebSocket(url) webSocket.onopen = handlers.onopen webSocket.onmessage = (event: MessageEvent) => { messages.push(event.data) handlers.onmessage(event) } webSocket.onclose = (event: CloseEvent) => { cy.writeFile('./test', messages) // !!! Can't put cy.writeFile in async handler handlers.onclose(event) } return webSocket } ) }) }
触发的错误信息
以下错误源自您的测试代码,而非Cypress。
Cypress检测到您从一个命令返回了Promise,同时在该Promise中调用了一个或多个cy命令。
返回Promise的命令是:
cy.get()
您在Promise中调用的cy命令是:
cy.writeFile()
由于Cypress命令本身已类似Promise,您无需对其进行包装或返回自定义Promise。
Cypress会以最终的Cypress命令返回值来解析您的命令。
将此设为错误而非警告的原因是,Cypress内部会串行排队命令,而Promise会在被调用时立即执行。尝试协调二者会导致Cypress永远无法解析。
当Cypress检测到源自测试代码的未捕获错误时,会自动使当前测试失败。
解决方案
1. 用cy.wrap()将操作纳入Cypress命令队列
WebSocket事件处理器属于异步回调,不在Cypress的命令执行流中,直接调用cy.writeFile()会触发错误。可以通过cy.wrap()把消息数组包装后,再执行写入命令,确保操作进入Cypress的串行队列:
修改onclose事件处理器:
webSocket.onclose = (event: CloseEvent) => { handlers.onclose(event) // 通过cy.wrap将messages注入Cypress命令流 cy.wrap(messages).then(msgList => { cy.writeFile('./test', msgList) }) }
2. 借助Cypress Task调用Node.js文件系统
如果不需要通过Cypress命令队列执行,可利用Cypress的task机制,在Node.js环境中完成文件写入(避免浏览器上下文限制):
- 在
cypress.config.js中注册自定义task:
const { defineConfig } = require('cypress') const fs = require('fs') module.exports = defineConfig({ e2e: { setupNodeEvents(on, config) { on('task', { writeWebSocketMessages: (messages) => { // 用同步写入避免Promise嵌套,也可使用fs.writeFile异步版本 fs.writeFileSync('./test', JSON.stringify(messages)) return null } }) }, }, })
- 在WebSocket的
onclose回调中调用该task:
webSocket.onclose = (event: CloseEvent) => { handlers.onclose(event) cy.task('writeWebSocketMessages', messages) }
3. 测试流程中统一收集并写入
如果可以在测试用例中明确控制WebSocket关闭时机,可将消息挂载到window对象,在测试流程的合适节点统一执行写入:
- 修改
trackWebSocketTraffic函数,将消息数组挂载到window:
export function trackWebSocketTraffic() { cy.window().then((win) => { win.webSocketMessages = [] // 挂载到window供测试用例访问 cy.stub(win, 'WebSocketFactory').callsFake( (url: string, handlers: WebSocketEventHandlers) => { const webSocket = new WebSocket(url) webSocket.onopen = handlers.onopen webSocket.onmessage = (event: MessageEvent) => { win.webSocketMessages.push(event.data) handlers.onmessage(event) } webSocket.onclose = handlers.onclose return webSocket } ) }) }
- 在测试用例中等待关闭后执行写入:
trackWebSocketTraffic() // 执行触发WebSocket关闭的操作(示例) cy.get('#close-socket-btn').click() // 从window获取消息并写入 cy.window().then(win => { cy.writeFile('./test', win.webSocketMessages) })
内容的提问来源于stack exchange,提问作者Nick

