From 4cd03571f55a9825c5b30c4f3eccfb6bc03dc84a Mon Sep 17 00:00:00 2001 From: jbouwman-zig Date: Mon, 12 May 2025 14:11:02 +0200 Subject: [PATCH 1/5] Add Playwright connector --- connector/Playwright.js | 174 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 connector/Playwright.js diff --git a/connector/Playwright.js b/connector/Playwright.js new file mode 100644 index 0000000..5dd02eb --- /dev/null +++ b/connector/Playwright.js @@ -0,0 +1,174 @@ +const fs = require('fs'); +const path = require('path'); +const { output } = require('codeceptjs'); + +class PlaywrightConnector { + constructor(Playwright, options = {}) { + if (!Playwright.page) throw new Error('Playwright page must be initialized'); + + this.Playwright = Playwright; + this.page = Playwright.page; + this.options = options; + this.routes = []; + this.connected = false; + + this.recordingsDir = options.recordingsDir || './data/requests'; + this.recording = false; + this.replaying = false; + this.recordedRequests = []; + this.replayMap = new Map(); + this.title = ''; + } + + async connect(title = 'default-session') { + if (this.connected) return; + + this.title = title; + + await this.page.route('**/*', async (route, request) => { + const url = request.url(); + const method = request.method(); + + // REPLAY mode + if (this.replaying) { + const key = `${method}:${url}`; + if (this.replayMap.has(key)) { + const response = this.replayMap.get(key); + output.debug(`Replayed ➞ ${method} ${url}`); + return route.fulfill({ + status: response.status, + headers: response.headers, + body: response.body, + }); + } + } + + // Mock route + for (const handler of this.routes) { + if (handler.method === method && + handler.urls.some(u => url.includes(u))) { + output.debug(`Mocked ➞ ${method} ${url}`); + return handler.callback(route, request); + } + } + + // Passthrough (with optional recording) + const response = await this.page.request.fetch(request); + const body = await response.body(); + + if (this.recording) { + const record = { + method, + url, + status: response.status(), + headers: response.headers(), + body: body.toString(), + }; + this.recordedRequests.push(record); + output.debug(`Recorded ➞ ${method} ${url}`); + } + + return route.fulfill({ + status: response.status(), + headers: response.headers(), + body, + }); + }); + + this.connected = true; + } + + async isConnected() { + return this.connected; + } + + async checkConnection() { + if (!this.connected) { + await this.connect(); + } + } + + async mockRequest(method, oneOrMoreUrls, dataOrStatusCode, additionalData = null) { + const urls = Array.isArray(oneOrMoreUrls) ? oneOrMoreUrls : [oneOrMoreUrls]; + + const callback = (route, _) => { + if (typeof dataOrStatusCode === 'number') { + const status = dataOrStatusCode; + const body = additionalData ? JSON.stringify(additionalData) : undefined; + return route.fulfill({ + status, + contentType: 'application/json', + body, + }); + } else { + const body = JSON.stringify(dataOrStatusCode); + return route.fulfill({ + status: 200, + contentType: 'application/json', + body, + }); + } + }; + + this.routes.push({ method, urls, callback }); + } + + async mockServer(configFn) { + await configFn(this.page); + } + + async record(title = this.title) { + this.recording = true; + this.title = title; + this.recordedRequests = []; + } + + async replay(title = this.title) { + this.replaying = true; + this.title = title; + + const filePath = path.join(this.recordingsDir, `${title}.json`); + if (!fs.existsSync(filePath)) { + throw new Error(`Replay file not found: ${filePath}`); + } + + const data = JSON.parse(fs.readFileSync(filePath, 'utf-8')); + this.replayMap.clear(); + for (const req of data) { + const key = `${req.method}:${req.url}`; + this.replayMap.set(key, { + status: req.status, + headers: req.headers, + body: Buffer.from(req.body), + }); + } + } + + async flush() { + if (!this.recording || !this.recordedRequests.length) return; + + const filePath = path.join(this.recordingsDir, `${this.title}.json`); + fs.mkdirSync(this.recordingsDir, { recursive: true }); + fs.writeFileSync(filePath, JSON.stringify(this.recordedRequests, null, 2)); + + output.log(`Saved recording: ${filePath}`); + this.recording = false; + this.recordedRequests = []; + } + + async disconnect() { + try { + await this.page.unroute('**/*'); + this.routes = []; + this.replaying = false; + this.recording = false; + this.recordedRequests = []; + this.replayMap.clear(); + this.connected = false; + } catch (err) { + output.log('Error during Playwright disconnect:', err.message); + } + } +} + +module.exports = PlaywrightConnector; \ No newline at end of file From 26b449ed4cf6861db7bd2c818b85f250f72871e2 Mon Sep 17 00:00:00 2001 From: jbouwman-zig Date: Mon, 12 May 2025 14:14:00 +0200 Subject: [PATCH 2/5] Add initial Playwright configuration --- test/codecept.playwright.conf.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 test/codecept.playwright.conf.js diff --git a/test/codecept.playwright.conf.js b/test/codecept.playwright.conf.js new file mode 100644 index 0000000..5e5d2e7 --- /dev/null +++ b/test/codecept.playwright.conf.js @@ -0,0 +1,26 @@ +module.exports.config = { + tests: './*_test.js', + timeout: 10000, + output: './output', + helpers: { + Playwright: { + url: 'http://0.0.0.0:8000', + show: false, + chrome: { + args: [ + '--disable-web-security', + '--no-sandbox', + '--disable-setuid-sandbox', + ], + }, + }, + FileSystem: {}, + MockRequestHelper: { + require: '../index.js' + }, + }, + include: {}, + bootstrap: false, + mocha: {}, + name: 'acceptance', +}; \ No newline at end of file From bc5fd71a52cb27e27cfd9c4ac793555d05dccd00 Mon Sep 17 00:00:00 2001 From: jbouwman-zig Date: Mon, 12 May 2025 15:33:52 +0200 Subject: [PATCH 3/5] Update connector/Playwright.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- connector/Playwright.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/Playwright.js b/connector/Playwright.js index 5dd02eb..a6e3ee1 100644 --- a/connector/Playwright.js +++ b/connector/Playwright.js @@ -132,7 +132,7 @@ class PlaywrightConnector { throw new Error(`Replay file not found: ${filePath}`); } - const data = JSON.parse(fs.readFileSync(filePath, 'utf-8')); + const data = JSON.parse(await fs.readFile(filePath, 'utf-8')); this.replayMap.clear(); for (const req of data) { const key = `${req.method}:${req.url}`; From 814f0f2cc4af81c65580df09fbc00cccd9d510e7 Mon Sep 17 00:00:00 2001 From: jbouwman-zig Date: Mon, 12 May 2025 15:34:00 +0200 Subject: [PATCH 4/5] Update connector/Playwright.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- connector/Playwright.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/Playwright.js b/connector/Playwright.js index a6e3ee1..ffca27f 100644 --- a/connector/Playwright.js +++ b/connector/Playwright.js @@ -148,8 +148,8 @@ class PlaywrightConnector { if (!this.recording || !this.recordedRequests.length) return; const filePath = path.join(this.recordingsDir, `${this.title}.json`); - fs.mkdirSync(this.recordingsDir, { recursive: true }); - fs.writeFileSync(filePath, JSON.stringify(this.recordedRequests, null, 2)); + await fs.mkdir(this.recordingsDir, { recursive: true }); + await fs.writeFile(filePath, JSON.stringify(this.recordedRequests, null, 2)); output.log(`Saved recording: ${filePath}`); this.recording = false; From 356831837b5b24c0532725a73ae516afd08b89ce Mon Sep 17 00:00:00 2001 From: jbouwman-zig Date: Tue, 13 May 2025 08:58:10 +0200 Subject: [PATCH 5/5] Update connector/Playwright.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- connector/Playwright.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/Playwright.js b/connector/Playwright.js index ffca27f..7080122 100644 --- a/connector/Playwright.js +++ b/connector/Playwright.js @@ -46,7 +46,7 @@ class PlaywrightConnector { // Mock route for (const handler of this.routes) { if (handler.method === method && - handler.urls.some(u => url.includes(u))) { + handler.urls.some(u => new RegExp(u).test(url))) { output.debug(`Mocked ➞ ${method} ${url}`); return handler.callback(route, request); }