diff --git a/docs/helpers/Playwright.md b/docs/helpers/Playwright.md index fb63eadcc..0c327c4e7 100644 --- a/docs/helpers/Playwright.md +++ b/docs/helpers/Playwright.md @@ -2217,6 +2217,8 @@ This also resets recorded websocket messages. await I.startRecordingWebSocketMessages(); ``` +Returns **void** automatically synchronized promise through #recorder + ### stopMockingRoute Stops network mocking created by `mockRoute`. @@ -2249,6 +2251,8 @@ Stops recording WS messages. Recorded WS messages is not flashed. await I.stopRecordingWebSocketMessages(); ``` +Returns **void** automatically synchronized promise through #recorder + ### switchTo Switches frame or in case of null locator reverts to parent. diff --git a/docs/helpers/Puppeteer.md b/docs/helpers/Puppeteer.md index 856f4b1e5..6daed6c2e 100644 --- a/docs/helpers/Puppeteer.md +++ b/docs/helpers/Puppeteer.md @@ -811,6 +811,10 @@ Returns **void** automatically synchronized promise through #recorder This action supports [React locators](https://codecept.io/react#locators) +### flushWebSocketMessages + +Resets all recorded WS messages. + ### focus Calls [focus][8] on the matching element. @@ -1236,6 +1240,12 @@ const webElements = await I.grabWebElements('#button'); Returns **[Promise][13]<any>** WebElement of being used Web helper +### grabWebSocketMessages + +Grab the recording WS messages + +Returns **[Array][15]<any>** + ### handleDownloads Sets a directory to where save files. Allows to test file downloads. @@ -1877,6 +1887,17 @@ I.setPuppeteerRequestHeaders({ - `customHeaders` **[object][4]** headers to set +### startRecordingWebSocketMessages + +Starts recording of websocket messages. +This also resets recorded websocket messages. + +```js +await I.startRecordingWebSocketMessages(); +``` + +Returns **void** automatically synchronized promise through #recorder + ### stopMockingRoute Stops network mocking created by `mockRoute`. @@ -1889,6 +1910,16 @@ I.stopMockingRoute(/(.png$)|(.jpg$)/); - `url` **([string][6] | [RegExp][18])?** URL, regex or pattern for to match URL +### stopRecordingWebSocketMessages + +Stops recording WS messages. Recorded WS messages is not flashed. + +```js +await I.stopRecordingWebSocketMessages(); +``` + +Returns **void** automatically synchronized promise through #recorder + ### switchTo Switches frame or in case of null locator reverts to parent. diff --git a/docs/webapi/startRecordingWebSocketMessages.mustache b/docs/webapi/startRecordingWebSocketMessages.mustache new file mode 100644 index 000000000..0a2f95fa1 --- /dev/null +++ b/docs/webapi/startRecordingWebSocketMessages.mustache @@ -0,0 +1,8 @@ +Starts recording of websocket messages. +This also resets recorded websocket messages. + +```js +await I.startRecordingWebSocketMessages(); +``` + +@returns {void} automatically synchronized promise through #recorder diff --git a/docs/webapi/stopRecordingWebSocketMessages.mustache b/docs/webapi/stopRecordingWebSocketMessages.mustache new file mode 100644 index 000000000..837db966c --- /dev/null +++ b/docs/webapi/stopRecordingWebSocketMessages.mustache @@ -0,0 +1,7 @@ +Stops recording WS messages. Recorded WS messages is not flashed. + +```js +await I.stopRecordingWebSocketMessages(); +``` + +@returns {void} automatically synchronized promise through #recorder diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index a56d2314d..8587be57d 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -3287,13 +3287,7 @@ class Playwright extends Helper { } /** - * Starts recording of websocket messages. - * This also resets recorded websocket messages. - * - * ```js - * await I.startRecordingWebSocketMessages(); - * ``` - * + * {{> startRecordingWebSocketMessages }} */ async startRecordingWebSocketMessages() { this.flushWebSocketMessages(); @@ -3327,11 +3321,7 @@ class Playwright extends Helper { } /** - * Stops recording WS messages. Recorded WS messages is not flashed. - * - * ```js - * await I.stopRecordingWebSocketMessages(); - * ``` + * {{> stopRecordingWebSocketMessages }} */ async stopRecordingWebSocketMessages() { await this.cdpSession.send('Network.disable'); diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index 1399f44e9..8a6caa5c6 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -226,6 +226,12 @@ class Puppeteer extends Helper { this.sessionPages = {}; this.activeSessionName = ''; + // for websocket messages + this.webSocketMessages = []; + this.recordingWebSocketMessages = false; + this.recordedWebSocketMessagesAtLeastOnce = false; + this.cdpSession = null; + // override defaults with config this._setConfig(config); } @@ -2507,6 +2513,93 @@ class Puppeteer extends Helper { } }); } + + async getNewCDPSession() { + const client = await this.page.target().createCDPSession(); + return client; + } + + /** + * {{> startRecordingWebSocketMessages }} + */ + async startRecordingWebSocketMessages() { + this.flushWebSocketMessages(); + this.recordingWebSocketMessages = true; + this.recordedWebSocketMessagesAtLeastOnce = true; + + this.cdpSession = await this.getNewCDPSession(); + await this.cdpSession.send('Network.enable'); + await this.cdpSession.send('Page.enable'); + + this.cdpSession.on( + 'Network.webSocketFrameReceived', + (payload) => { + this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload)); + }, + ); + + this.cdpSession.on( + 'Network.webSocketFrameSent', + (payload) => { + this._logWebsocketMessages(this._getWebSocketLog('SENT', payload)); + }, + ); + + this.cdpSession.on( + 'Network.webSocketFrameError', + (payload) => { + this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload)); + }, + ); + } + + /** + * {{> stopRecordingWebSocketMessages }} + */ + async stopRecordingWebSocketMessages() { + await this.cdpSession.send('Network.disable'); + await this.cdpSession.send('Page.disable'); + this.page.removeAllListeners('Network'); + this.recordingWebSocketMessages = false; + } + + /** + * Grab the recording WS messages + * + * @return { Array } + * + */ + grabWebSocketMessages() { + if (!this.recordingWebSocketMessages) { + if (!this.recordedWebSocketMessagesAtLeastOnce) { + throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.'); + } + } + return this.webSocketMessages; + } + + /** + * Resets all recorded WS messages. + */ + flushWebSocketMessages() { + this.webSocketMessages = []; + } + + _getWebSocketMessage(payload) { + if (payload.errorMessage) { + return payload.errorMessage; + } + + return payload.response.payloadData; + } + + _getWebSocketLog(prefix, payload) { + return `${prefix} ID: ${payload.requestId} TIMESTAMP: ${payload.timestamp} (${new Date().toISOString()})\n\n${this._getWebSocketMessage(payload)}\n\n`; + } + + _logWebsocketMessages(message) { + this.webSocketMessages += message; + } } module.exports = Puppeteer; diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index b7e8701aa..81ac807b6 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -1067,51 +1067,6 @@ describe('Playwright', function () { }); }); - describe('#startRecordingWebSocketMessages, #grabWebSocketMessages, #stopRecordingWebSocketMessages', () => { - it('should throw error when calling grabWebSocketMessages before startRecordingWebSocketMessages', () => { - if (process.env.BROWSER === 'firefox') this.skip(); - try { - I.amOnPage('https://websocketstest.com/'); - I.waitForText('Work for You!'); - I.grabWebSocketMessages(); - } catch (e) { - expect(e.message).to.equal('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.'); - } - }); - - it('should flush the WS messages', async () => { - if (process.env.BROWSER === 'firefox') this.skip(); - await I.startRecordingWebSocketMessages(); - I.amOnPage('https://websocketstest.com/'); - I.waitForText('Work for You!'); - I.flushNetworkTraffics(); - const wsMessages = I.grabWebSocketMessages(); - expect(wsMessages.length).to.equal(0); - }); - - it('should see recording WS messages', async () => { - if (process.env.BROWSER === 'firefox') this.skip(); - await I.startRecordingWebSocketMessages(); - await I.amOnPage('https://websocketstest.com/'); - I.waitForText('Work for You!'); - const wsMessages = I.grabWebSocketMessages(); - expect(wsMessages.length).to.greaterThan(0); - }); - - it('should not see recording WS messages', async () => { - if (process.env.BROWSER === 'firefox') this.skip(); - await I.startRecordingWebSocketMessages(); - await I.amOnPage('https://websocketstest.com/'); - I.waitForText('Work for You!'); - const wsMessages = I.grabWebSocketMessages(); - await I.stopRecordingWebSocketMessages(); - await I.amOnPage('https://websocketstest.com/'); - I.waitForText('Work for You!'); - const afterWsMessages = I.grabWebSocketMessages(); - expect(wsMessages.length).to.equal(afterWsMessages.length); - }); - }); - describe('#makeApiRequest', () => { it('should make 3rd party API request', async () => { const response = await I.makeApiRequest('get', 'https://reqres.in/api/users?page=2'); diff --git a/test/helper/webapi.js b/test/helper/webapi.js index 8c32a1c29..fc3853034 100644 --- a/test/helper/webapi.js +++ b/test/helper/webapi.js @@ -1,6 +1,8 @@ let assert; +let expect; import('chai').then(chai => { assert = chai.assert; + expect = chai.expect; }); const path = require('path'); @@ -1609,4 +1611,49 @@ module.exports.tests = function () { await I.see('Textarea not focused', '#textareaMessage'); }); }); + + describe('#startRecordingWebSocketMessages, #grabWebSocketMessages, #stopRecordingWebSocketMessages', () => { + beforeEach(function () { + if (isHelper('TestCafe') || isHelper('WebDriver') || process.env.BROWSER === 'firefox') this.skip(); + }); + + it('should throw error when calling grabWebSocketMessages before startRecordingWebSocketMessages', () => { + try { + I.amOnPage('https://websocketstest.com/'); + I.waitForText('Work for You!'); + I.grabWebSocketMessages(); + } catch (e) { + expect(e.message).to.equal('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.'); + } + }); + + it('should flush the WS messages', async () => { + await I.startRecordingWebSocketMessages(); + I.amOnPage('https://websocketstest.com/'); + I.waitForText('Work for You!'); + I.flushWebSocketMessages(); + const wsMessages = I.grabWebSocketMessages(); + expect(wsMessages.length).to.equal(0); + }); + + it('should see recording WS messages', async () => { + await I.startRecordingWebSocketMessages(); + await I.amOnPage('https://websocketstest.com/'); + I.waitForText('Work for You!'); + const wsMessages = await I.grabWebSocketMessages(); + expect(wsMessages.length).to.greaterThan(0); + }); + + it('should not see recording WS messages', async () => { + await I.startRecordingWebSocketMessages(); + await I.amOnPage('https://websocketstest.com/'); + I.waitForText('Work for You!'); + const wsMessages = I.grabWebSocketMessages(); + await I.stopRecordingWebSocketMessages(); + await I.amOnPage('https://websocketstest.com/'); + I.waitForText('Work for You!'); + const afterWsMessages = I.grabWebSocketMessages(); + expect(wsMessages.length).to.equal(afterWsMessages.length); + }); + }); };