From d6ae9d3c67c11f3fce9ace05d2e41f795ecff252 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Thu, 2 Oct 2025 09:57:00 +0000 Subject: [PATCH 1/6] mock server --- .github/workflows/acceptance-tests.yml | 16 ++++++++- .github/workflows/playwright.yml | 12 ++++++- .github/workflows/puppeteer.yml | 12 ++++++- .github/workflows/test.yml | 20 +++++++++++ .github/workflows/webdriver.yml | 12 ++++++- package.json | 3 ++ test/acceptance/config_test.js | 2 +- test/data/app/view/form/fetch_call.php | 2 +- test/helper/Playwright_test.js | 18 +++++----- test/helper/Puppeteer_test.js | 4 +-- test/mock-server/server.js | 50 ++++++++++++++++++++++++++ test/mock-server/start-mock-server.js | 41 +++++++++++++++++++++ test/rest/REST_test.js | 2 +- 13 files changed, 176 insertions(+), 18 deletions(-) create mode 100644 test/mock-server/server.js create mode 100644 test/mock-server/start-mock-server.js diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index e92699122..ffdad599e 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -21,7 +21,7 @@ jobs: matrix: node-version: [20.x] - steps: + steps: # Checkout the repository - name: Checkout Repository uses: actions/checkout@v5 @@ -32,6 +32,16 @@ jobs: sudo apt-get update --allow-releaseinfo-change sudo apt-get install -y docker-compose + # Start mock server + - name: Start mock server + run: nohup npm run mock-server:start & + - name: Wait for mock server + run: | + for i in {1..20}; do + curl -sSf http://localhost:3001/api/users && break + sleep 1 + done + # Run rest tests using docker-compose - name: Run REST Tests run: docker-compose run --rm test-rest @@ -46,3 +56,7 @@ jobs: - name: Run Faker BDD Tests run: docker-compose run --rm test-bdd.faker working-directory: test + + # Stop mock server + - name: Stop mock server + run: npm run mock-server:stop diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f4a7182fb..6a57af0e2 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -21,7 +21,7 @@ jobs: matrix: node-version: [20.x] - steps: + steps: - uses: actions/checkout@v5 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v5 @@ -40,6 +40,14 @@ jobs: sudo apt-get update --allow-releaseinfo-change - name: Install browsers and deps run: npx playwright install && npx playwright install-deps + - name: Start mock server + run: nohup npm run mock-server:start & + - name: Wait for mock server + run: | + for i in {1..20}; do + curl -sSf http://localhost:3001/api/users && break + sleep 1 + done - name: check run: './bin/codecept.js check -c test/acceptance/codecept.Playwright.js' - name: start a server @@ -58,3 +66,5 @@ jobs: run: 'BROWSER=webkit node ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug' - name: run chromium unit tests run: ./node_modules/.bin/mocha test/helper/Playwright_test.js --timeout 5000 + - name: Stop mock server + run: npm run mock-server:stop diff --git a/.github/workflows/puppeteer.yml b/.github/workflows/puppeteer.yml index 336517ebd..cbee83d24 100644 --- a/.github/workflows/puppeteer.yml +++ b/.github/workflows/puppeteer.yml @@ -21,7 +21,7 @@ jobs: matrix: node-version: [20.x] - steps: + steps: - uses: actions/checkout@v5 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v5 @@ -35,6 +35,14 @@ jobs: npm i --force && npm i puppeteer --force env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true + - name: Start mock server + run: nohup npm run mock-server:start & + - name: Wait for mock server + run: | + for i in {1..20}; do + curl -sSf http://localhost:3001/api/users && break + sleep 1 + done - name: start a server run: 'php -S 127.0.0.1:8000 -t test/data/app &' - uses: browser-actions/setup-chrome@v2 @@ -43,3 +51,5 @@ jobs: run: './bin/codecept.js run-workers 2 -c test/acceptance/codecept.Puppeteer.js --grep @Puppeteer --debug' - name: run unit tests run: ./node_modules/.bin/mocha test/helper/Puppeteer_test.js + - name: Stop mock server + run: npm run mock-server:stop diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5bb4a1752..a4b954c28 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,17 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + - name: Start mock server + run: nohup npm run mock-server:start & + - name: Wait for mock server + run: | + for i in {1..20}; do + curl -sSf http://localhost:3001/api/users && break + sleep 1 + done - run: npm run test:unit + - name: Stop mock server + run: npm run mock-server:stop runner-tests: name: Runner tests @@ -47,6 +57,16 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + - name: Start mock server + run: nohup npm run mock-server:start & + - name: Wait for mock server + run: | + for i in {1..20}; do + curl -sSf http://localhost:3001/api/users && break + sleep 1 + done - run: npm run test:runner + - name: Stop mock server + run: npm run mock-server:stop # Note: Runner tests are mocha-based, so sharding doesn't apply here. # For CodeceptJS sharding examples, see sharding-demo.yml workflow. diff --git a/.github/workflows/webdriver.yml b/.github/workflows/webdriver.yml index a9b7f7317..84f809c3d 100644 --- a/.github/workflows/webdriver.yml +++ b/.github/workflows/webdriver.yml @@ -20,7 +20,7 @@ jobs: matrix: node-version: [20.x] - steps: + steps: - run: docker run -d --net=host --shm-size=2g selenium/standalone-chrome:4.27 - uses: actions/checkout@v5 - name: Use Node.js ${{ matrix.node-version }} @@ -36,6 +36,14 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + - name: Start mock server + run: nohup npm run mock-server:start & + - name: Wait for mock server + run: | + for i in {1..20}; do + curl -sSf http://localhost:3001/api/users && break + sleep 1 + done - name: start a server run: 'php -S 127.0.0.1:8000 -t test/data/app &' - name: check @@ -44,3 +52,5 @@ jobs: run: ./node_modules/.bin/mocha test/helper/WebDriver_test.js --exit - name: run tests run: './bin/codecept.js run -c test/acceptance/codecept.WebDriver.js --grep @WebDriver --debug' + - name: Stop mock server + run: npm run mock-server:stop diff --git a/package.json b/package.json index b652ab752..7414556bd 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,9 @@ "repository": "Codeception/codeceptjs", "scripts": { "test-server": "node bin/test-server.js test/data/rest/db.json --host 0.0.0.0 -p 8010", + "mock-server:start": "node test/mock-server/start-mock-server.js", + "mock-server:stop": "kill -9 $(lsof -t -i:3001)", + "test:with-mock-server": "npm run mock-server:start && npm test", "json-server:graphql": "node test/data/graphql/index.js", "lint": "eslint bin/ examples/ lib/ test/ translations/ runok.js", "lint-fix": "eslint bin/ examples/ lib/ test/ translations/ runok.js --fix", diff --git a/test/acceptance/config_test.js b/test/acceptance/config_test.js index d8415aad5..6d0817470 100644 --- a/test/acceptance/config_test.js +++ b/test/acceptance/config_test.js @@ -32,7 +32,7 @@ Scenario('change config 5 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { Scenario('make API call and check response @Playwright', ({ I }) => { I.amOnPage('/') - I.makeApiRequest('get', 'https://reqres.in/api/users?page=2', { headers: {'x-api-key': 'reqres-free-v1'}}) + I.makeApiRequest('get', 'http://localhost:3001/api/users?page=2', { headers: {'x-api-key': 'reqres-free-v1'}}) I.seeResponseCodeIsSuccessful() }) diff --git a/test/data/app/view/form/fetch_call.php b/test/data/app/view/form/fetch_call.php index b79ee6aad..826ea2795 100644 --- a/test/data/app/view/form/fetch_call.php +++ b/test/data/app/view/form/fetch_call.php @@ -53,7 +53,7 @@ const getPostData = () => getData("https://jsonplaceholder.typicode.com/posts/1"); const getCommentsData = () => - getData("https://reqres.in/api/comments/1"); + getData("http://localhost:3001/api/comments/1"); const getUsersData = () => getData("https://jsonplaceholder.typicode.com/users/1"); diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index d16af3281..97ba07a3f 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -1022,7 +1022,7 @@ describe('Playwright', function () { describe('#mockRoute, #stopMockingRoute', () => { it('should mock a route', async () => { await I.amOnPage('/form/fetch_call') - await I.mockRoute('https://reqres.in/api/comments/1', route => { + await I.mockRoute('http://localhost:3001/api/comments/1', route => { route.fulfill({ status: 200, headers: { 'Access-Control-Allow-Origin': '*' }, @@ -1032,7 +1032,7 @@ describe('Playwright', function () { }) await I.click('GET COMMENTS') await I.see('this was mocked') - await I.stopMockingRoute('https://reqres.in/api/comments/1') + await I.stopMockingRoute('http://localhost:3001/api/comments/1') await I.click('GET COMMENTS') await I.see('data') await I.dontSee('this was mocked') @@ -1041,9 +1041,9 @@ describe('Playwright', function () { describe('#makeApiRequest', () => { it('should make 3rd party API request', async () => { - const response = await I.makeApiRequest('get', 'https://reqres.in/api/users?page=2') - expect(response.status()).to.equal(200) - expect(await response.json()).to.include.keys(['page']) + const response = await I.makeApiRequest('get', 'http://localhost:3001/api/users?page=2') + expect(response.status()).to.equal(200) + expect(await response.json()).to.include.keys(['data']) }) it('should make local API request', async () => { @@ -1054,10 +1054,10 @@ describe('Playwright', function () { it('should convert to axios response with onResponse hook', async () => { let response I.config.onResponse = resp => (response = resp) - await I.makeApiRequest('get', 'https://reqres.in/api/users?page=2') - expect(response).to.be.ok - expect(response.status).to.equal(200) - expect(response.data).to.include.keys(['page', 'total']) + await I.makeApiRequest('get', 'http://localhost:3001/api/users?page=2') + expect(response).to.be.ok + expect(response.status).to.equal(200) + expect(response.data).to.include.keys(['data']) }) }) diff --git a/test/helper/Puppeteer_test.js b/test/helper/Puppeteer_test.js index 06ef40e05..0d124f6ca 100644 --- a/test/helper/Puppeteer_test.js +++ b/test/helper/Puppeteer_test.js @@ -1032,7 +1032,7 @@ describe('Puppeteer', function () { describe('#mockRoute, #stopMockingRoute', () => { it('should mock a route', async () => { await I.amOnPage('/form/fetch_call') - await I.mockRoute('https://reqres.in/api/comments/1', request => { + await I.mockRoute('http://localhost:3001/api/comments/1', request => { request.respond({ status: 200, headers: { 'Access-Control-Allow-Origin': '*' }, @@ -1042,7 +1042,7 @@ describe('Puppeteer', function () { }) await I.click('GET COMMENTS') await I.see('this was mocked') - await I.stopMockingRoute('https://reqres.in/api/comments/1') + await I.stopMockingRoute('http://localhost:3001/api/comments/1') await I.click('GET COMMENTS') await I.see('data') await I.dontSee('this was mocked') diff --git a/test/mock-server/server.js b/test/mock-server/server.js new file mode 100644 index 000000000..c13e945f1 --- /dev/null +++ b/test/mock-server/server.js @@ -0,0 +1,50 @@ +const express = require('express'); +const app = express(); +app.use(express.json()); + +// Example users data +let users = [ + { id: 1, name: 'John Doe', email: 'john@example.com' }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com' } +]; + +// GET /api/users +app.get('/api/users', (req, res) => { + res.json({ data: users }); +}); + +// GET /api/users/:id +app.get('/api/users/:id', (req, res) => { + const user = users.find(u => u.id === parseInt(req.params.id)); + if (user) return res.json(user); + res.status(404).json({ error: 'User not found' }); +}); + +// POST /api/users +app.post('/api/users', (req, res) => { + const { name, email } = req.body; + const newUser = { id: users.length + 1, name, email }; + users.push(newUser); + res.status(201).json(newUser); +}); + +// PUT /api/users/:id +app.put('/api/users/:id', (req, res) => { + const user = users.find(u => u.id === parseInt(req.params.id)); + if (!user) return res.status(404).json({ error: 'User not found' }); + user.name = req.body.name || user.name; + user.email = req.body.email || user.email; + res.json(user); +}); + +// DELETE /api/users/:id +app.delete('/api/users/:id', (req, res) => { + users = users.filter(u => u.id !== parseInt(req.params.id)); + res.status(204).send(); +}); + +// Start server +const PORT = process.env.PORT || 3001; +app.listen(PORT, () => { + console.log(`Mock REST server running on port ${PORT}`); +}); diff --git a/test/mock-server/start-mock-server.js b/test/mock-server/start-mock-server.js new file mode 100644 index 000000000..6fcb66b59 --- /dev/null +++ b/test/mock-server/start-mock-server.js @@ -0,0 +1,41 @@ +const { spawn } = require('child_process'); +const http = require('http'); + +const PORT = process.env.PORT || 3001; +const MAX_RETRIES = 20; +const RETRY_DELAY = 500; + +function waitForServer(retries = 0) { + return new Promise((resolve, reject) => { + http.get(`http://localhost:${PORT}/api/users`, res => { + if (res.statusCode === 200) return resolve(true); + res.resume(); + reject(); + }).on('error', () => { + if (retries < MAX_RETRIES) { + setTimeout(() => { + resolve(waitForServer(retries + 1)); + }, RETRY_DELAY); + } else { + reject(new Error('Mock server did not start in time')); + } + }); + }); +} + +const serverProcess = spawn('node', ['server.js'], { + cwd: __dirname, + stdio: 'inherit', + env: process.env, +}); + +console.log('Starting mock server...'); +waitForServer() + .then(() => { + console.log('Mock server is up and running!'); + }) + .catch(err => { + console.error(err.message); + serverProcess.kill(); + process.exit(1); + }); diff --git a/test/rest/REST_test.js b/test/rest/REST_test.js index 317fdd333..44567a316 100644 --- a/test/rest/REST_test.js +++ b/test/rest/REST_test.js @@ -150,7 +150,7 @@ describe('REST', () => { }) it('should be able to parse JSON responses', async () => { - await I.sendGetRequest('https://reqres.in/api/comments/1', { 'x-api-key': 'reqres-free-v1'}) + await I.sendGetRequest('http://localhost:3001/api/comments/1', { 'x-api-key': 'reqres-free-v1'}) await jsonResponse.seeResponseCodeIsSuccessful() await jsonResponse.seeResponseContainsKeys(['data', 'support']) }) From 6b4b9b287d4e70aed5a3b2038195307f39016a04 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:01:35 +0000 Subject: [PATCH 2/6] mock server --- .github/workflows/acceptance-tests.yml | 2 +- .github/workflows/playwright.yml | 2 +- .github/workflows/puppeteer.yml | 2 +- .github/workflows/webdriver.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index ffdad599e..66a80859c 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -21,7 +21,7 @@ jobs: matrix: node-version: [20.x] - steps: + steps: # Checkout the repository - name: Checkout Repository uses: actions/checkout@v5 diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 6a57af0e2..cb4eb2e10 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -21,7 +21,7 @@ jobs: matrix: node-version: [20.x] - steps: + steps: - uses: actions/checkout@v5 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v5 diff --git a/.github/workflows/puppeteer.yml b/.github/workflows/puppeteer.yml index cbee83d24..87415f36e 100644 --- a/.github/workflows/puppeteer.yml +++ b/.github/workflows/puppeteer.yml @@ -21,7 +21,7 @@ jobs: matrix: node-version: [20.x] - steps: + steps: - uses: actions/checkout@v5 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v5 diff --git a/.github/workflows/webdriver.yml b/.github/workflows/webdriver.yml index 84f809c3d..666fed540 100644 --- a/.github/workflows/webdriver.yml +++ b/.github/workflows/webdriver.yml @@ -20,7 +20,7 @@ jobs: matrix: node-version: [20.x] - steps: + steps: - run: docker run -d --net=host --shm-size=2g selenium/standalone-chrome:4.27 - uses: actions/checkout@v5 - name: Use Node.js ${{ matrix.node-version }} From d17bfba8e7509adb7565a4e498b8fe4d9c405341 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:05:32 +0000 Subject: [PATCH 3/6] lint fix --- lib/helper/JSONResponse.js | 8 +-- lib/helper/WebDriver.js | 6 +- lib/listener/steps.js | 4 +- lib/plugin/htmlReporter.js | 2 +- test/acceptance/config_test.js | 2 +- test/helper/Playwright_test.js | 14 ++--- .../helper/WebDriver.noSeleniumServer_test.js | 1 - test/mock-server/server.js | 56 +++++++++---------- test/mock-server/start-mock-server.js | 54 +++++++++--------- test/rest/REST_test.js | 2 +- test/runner/html-reporter-plugin_test.js | 12 ++-- test/unit/workerStorage_test.js | 36 ++++++------ test/unit/worker_test.js | 14 ++--- 13 files changed, 106 insertions(+), 105 deletions(-) diff --git a/lib/helper/JSONResponse.js b/lib/helper/JSONResponse.js index bc02f934a..29a44514c 100644 --- a/lib/helper/JSONResponse.js +++ b/lib/helper/JSONResponse.js @@ -72,11 +72,11 @@ class JSONResponse extends Helper { if (!this.helpers[this.options.requestHelper]) { throw new Error(`Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`) } - const origOnResponse = this.helpers[this.options.requestHelper].config.onResponse; + const origOnResponse = this.helpers[this.options.requestHelper].config.onResponse this.helpers[this.options.requestHelper].config.onResponse = response => { - this.response = response; - if (typeof origOnResponse === 'function') origOnResponse(response); - }; + this.response = response + if (typeof origOnResponse === 'function') origOnResponse(response) + } } _before() { diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js index 2c511a0d1..e1ac68c5e 100644 --- a/lib/helper/WebDriver.js +++ b/lib/helper/WebDriver.js @@ -998,7 +998,7 @@ class WebDriver extends Helper { * {{ react }} */ async click(locator, context = null) { - const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick' + const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick' const locateFn = prepareLocateFn.call(this, context) const res = await findClickable.call(this, locator, locateFn) @@ -1217,7 +1217,7 @@ class WebDriver extends Helper { * {{> checkOption }} */ async checkOption(field, context = null) { - const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick' + const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick' const locateFn = prepareLocateFn.call(this, context) const res = await findCheckable.call(this, field, locateFn) @@ -1237,7 +1237,7 @@ class WebDriver extends Helper { * {{> uncheckOption }} */ async uncheckOption(field, context = null) { - const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick' + const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick' const locateFn = prepareLocateFn.call(this, context) const res = await findCheckable.call(this, field, locateFn) diff --git a/lib/listener/steps.js b/lib/listener/steps.js index a71bcd75c..524cd0ccd 100644 --- a/lib/listener/steps.js +++ b/lib/listener/steps.js @@ -79,14 +79,14 @@ module.exports = function () { return currentHook.steps.push(step) } if (!currentTest || !currentTest.steps) return - + // Check if we're in a session that should be excluded from main test steps const currentSessionId = recorder.getCurrentSessionId() if (currentSessionId && EXCLUDED_SESSIONS.includes(currentSessionId)) { // Skip adding this step to the main test steps return } - + currentTest.steps.push(step) }) diff --git a/lib/plugin/htmlReporter.js b/lib/plugin/htmlReporter.js index 8a0bcf2b0..97e7d0d0f 100644 --- a/lib/plugin/htmlReporter.js +++ b/lib/plugin/htmlReporter.js @@ -288,7 +288,7 @@ module.exports = function (config) { finalState: test.state, duration: test.duration || 0, }) - output.debug(`HTML Reporter: Fallback retry detection for failed test ${test.title}, attempts: ${fallbackAttempts}`) + output.debug(`HTML Reporter: Fallback retry detection for failed test ${test.title}, attempts: ${fallbackAttempts}`) } }) diff --git a/test/acceptance/config_test.js b/test/acceptance/config_test.js index 6d0817470..879d833b4 100644 --- a/test/acceptance/config_test.js +++ b/test/acceptance/config_test.js @@ -32,7 +32,7 @@ Scenario('change config 5 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { Scenario('make API call and check response @Playwright', ({ I }) => { I.amOnPage('/') - I.makeApiRequest('get', 'http://localhost:3001/api/users?page=2', { headers: {'x-api-key': 'reqres-free-v1'}}) + I.makeApiRequest('get', 'http://localhost:3001/api/users?page=2', { headers: { 'x-api-key': 'reqres-free-v1' } }) I.seeResponseCodeIsSuccessful() }) diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index 97ba07a3f..9f5d5566a 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -1041,9 +1041,9 @@ describe('Playwright', function () { describe('#makeApiRequest', () => { it('should make 3rd party API request', async () => { - const response = await I.makeApiRequest('get', 'http://localhost:3001/api/users?page=2') - expect(response.status()).to.equal(200) - expect(await response.json()).to.include.keys(['data']) + const response = await I.makeApiRequest('get', 'http://localhost:3001/api/users?page=2') + expect(response.status()).to.equal(200) + expect(await response.json()).to.include.keys(['data']) }) it('should make local API request', async () => { @@ -1054,10 +1054,10 @@ describe('Playwright', function () { it('should convert to axios response with onResponse hook', async () => { let response I.config.onResponse = resp => (response = resp) - await I.makeApiRequest('get', 'http://localhost:3001/api/users?page=2') - expect(response).to.be.ok - expect(response.status).to.equal(200) - expect(response.data).to.include.keys(['data']) + await I.makeApiRequest('get', 'http://localhost:3001/api/users?page=2') + expect(response).to.be.ok + expect(response.status).to.equal(200) + expect(response.data).to.include.keys(['data']) }) }) diff --git a/test/helper/WebDriver.noSeleniumServer_test.js b/test/helper/WebDriver.noSeleniumServer_test.js index 622953f3b..c45c8540a 100644 --- a/test/helper/WebDriver.noSeleniumServer_test.js +++ b/test/helper/WebDriver.noSeleniumServer_test.js @@ -382,7 +382,6 @@ describe('WebDriver - No Selenium server started', function () { }) }) - describe('#seeTitleEquals', () => { it('should check that title is equal to provided one', async () => { await wd.amOnPage('/') diff --git a/test/mock-server/server.js b/test/mock-server/server.js index c13e945f1..7bad332ff 100644 --- a/test/mock-server/server.js +++ b/test/mock-server/server.js @@ -1,50 +1,50 @@ -const express = require('express'); -const app = express(); -app.use(express.json()); +const express = require('express') +const app = express() +app.use(express.json()) // Example users data let users = [ { id: 1, name: 'John Doe', email: 'john@example.com' }, - { id: 2, name: 'Jane Smith', email: 'jane@example.com' } -]; + { id: 2, name: 'Jane Smith', email: 'jane@example.com' }, +] // GET /api/users app.get('/api/users', (req, res) => { - res.json({ data: users }); -}); + res.json({ data: users }) +}) // GET /api/users/:id app.get('/api/users/:id', (req, res) => { - const user = users.find(u => u.id === parseInt(req.params.id)); - if (user) return res.json(user); - res.status(404).json({ error: 'User not found' }); -}); + const user = users.find(u => u.id === parseInt(req.params.id)) + if (user) return res.json(user) + res.status(404).json({ error: 'User not found' }) +}) // POST /api/users app.post('/api/users', (req, res) => { - const { name, email } = req.body; - const newUser = { id: users.length + 1, name, email }; - users.push(newUser); - res.status(201).json(newUser); -}); + const { name, email } = req.body + const newUser = { id: users.length + 1, name, email } + users.push(newUser) + res.status(201).json(newUser) +}) // PUT /api/users/:id app.put('/api/users/:id', (req, res) => { - const user = users.find(u => u.id === parseInt(req.params.id)); - if (!user) return res.status(404).json({ error: 'User not found' }); - user.name = req.body.name || user.name; - user.email = req.body.email || user.email; - res.json(user); -}); + const user = users.find(u => u.id === parseInt(req.params.id)) + if (!user) return res.status(404).json({ error: 'User not found' }) + user.name = req.body.name || user.name + user.email = req.body.email || user.email + res.json(user) +}) // DELETE /api/users/:id app.delete('/api/users/:id', (req, res) => { - users = users.filter(u => u.id !== parseInt(req.params.id)); - res.status(204).send(); -}); + users = users.filter(u => u.id !== parseInt(req.params.id)) + res.status(204).send() +}) // Start server -const PORT = process.env.PORT || 3001; +const PORT = process.env.PORT || 3001 app.listen(PORT, () => { - console.log(`Mock REST server running on port ${PORT}`); -}); + console.log(`Mock REST server running on port ${PORT}`) +}) diff --git a/test/mock-server/start-mock-server.js b/test/mock-server/start-mock-server.js index 6fcb66b59..32d2d280e 100644 --- a/test/mock-server/start-mock-server.js +++ b/test/mock-server/start-mock-server.js @@ -1,41 +1,43 @@ -const { spawn } = require('child_process'); -const http = require('http'); +const { spawn } = require('child_process') +const http = require('http') -const PORT = process.env.PORT || 3001; -const MAX_RETRIES = 20; -const RETRY_DELAY = 500; +const PORT = process.env.PORT || 3001 +const MAX_RETRIES = 20 +const RETRY_DELAY = 500 function waitForServer(retries = 0) { return new Promise((resolve, reject) => { - http.get(`http://localhost:${PORT}/api/users`, res => { - if (res.statusCode === 200) return resolve(true); - res.resume(); - reject(); - }).on('error', () => { - if (retries < MAX_RETRIES) { - setTimeout(() => { - resolve(waitForServer(retries + 1)); - }, RETRY_DELAY); - } else { - reject(new Error('Mock server did not start in time')); - } - }); - }); + http + .get(`http://localhost:${PORT}/api/users`, res => { + if (res.statusCode === 200) return resolve(true) + res.resume() + reject() + }) + .on('error', () => { + if (retries < MAX_RETRIES) { + setTimeout(() => { + resolve(waitForServer(retries + 1)) + }, RETRY_DELAY) + } else { + reject(new Error('Mock server did not start in time')) + } + }) + }) } const serverProcess = spawn('node', ['server.js'], { cwd: __dirname, stdio: 'inherit', env: process.env, -}); +}) -console.log('Starting mock server...'); +console.log('Starting mock server...') waitForServer() .then(() => { - console.log('Mock server is up and running!'); + console.log('Mock server is up and running!') }) .catch(err => { - console.error(err.message); - serverProcess.kill(); - process.exit(1); - }); + console.error(err.message) + serverProcess.kill() + process.exit(1) + }) diff --git a/test/rest/REST_test.js b/test/rest/REST_test.js index 44567a316..e6978ff37 100644 --- a/test/rest/REST_test.js +++ b/test/rest/REST_test.js @@ -150,7 +150,7 @@ describe('REST', () => { }) it('should be able to parse JSON responses', async () => { - await I.sendGetRequest('http://localhost:3001/api/comments/1', { 'x-api-key': 'reqres-free-v1'}) + await I.sendGetRequest('http://localhost:3001/api/comments/1', { 'x-api-key': 'reqres-free-v1' }) await jsonResponse.seeResponseCodeIsSuccessful() await jsonResponse.seeResponseContainsKeys(['data', 'support']) }) diff --git a/test/runner/html-reporter-plugin_test.js b/test/runner/html-reporter-plugin_test.js index 75c6345b4..56a7a7ef8 100644 --- a/test/runner/html-reporter-plugin_test.js +++ b/test/runner/html-reporter-plugin_test.js @@ -132,34 +132,34 @@ describe('CodeceptJS html-reporter-plugin', function () { // Read and validate HTML report content for BDD features const reportContent = fs.readFileSync(reportFile, 'utf8') - + // Check for BDD-specific elements expect(reportContent).toContain('bdd-test') // CSS class for BDD tests expect(reportContent).toContain('Scenario:') // BDD scenario prefix expect(reportContent).toContain('Feature:') // BDD feature prefix expect(reportContent).toContain('Gherkin') // BDD badge - + // Check for BDD steps styling expect(reportContent).toContain('bdd-steps-section') expect(reportContent).toContain('bdd-step-item') expect(reportContent).toContain('bdd-keyword') expect(reportContent).toContain('bdd-step-text') - + // Check for feature information section expect(reportContent).toContain('bdd-feature-section') expect(reportContent).toContain('feature-info') expect(reportContent).toContain('HTML Reporter BDD Test') // Feature name - + // Check for BDD-specific CSS styles expect(reportContent).toContain('bdd-badge') expect(reportContent).toContain('feature-name') expect(reportContent).toContain('feature-description') - + // Check for test type filter expect(reportContent).toContain('typeFilter') expect(reportContent).toContain('BDD/Gherkin') expect(reportContent).toContain('data-type=') - + // Should contain scenario steps with proper keywords expect(reportContent).toMatch(/Given|When|Then|And/) diff --git a/test/unit/workerStorage_test.js b/test/unit/workerStorage_test.js index 8a1f95750..5ae81619c 100644 --- a/test/unit/workerStorage_test.js +++ b/test/unit/workerStorage_test.js @@ -1,10 +1,10 @@ -const { expect } = require('expect'); -const WorkerStorage = require('../../lib/workerStorage'); -const { Worker } = require('worker_threads'); -const event = require('../../lib/event'); +const { expect } = require('expect') +const WorkerStorage = require('../../lib/workerStorage') +const { Worker } = require('worker_threads') +const event = require('../../lib/event') describe('WorkerStorage', () => { - it('should handle share message correctly without circular dependency', (done) => { + it('should handle share message correctly without circular dependency', done => { // Create a mock worker to test the functionality const mockWorker = { threadId: 'test-thread-1', @@ -12,24 +12,24 @@ describe('WorkerStorage', () => { if (eventName === 'message') { // Simulate receiving a share message setTimeout(() => { - callback({ event: 'share', data: { testKey: 'testValue' } }); - done(); - }, 10); + callback({ event: 'share', data: { testKey: 'testValue' } }) + done() + }, 10) } }, - postMessage: () => {} - }; + postMessage: () => {}, + } // Add the mock worker to storage - WorkerStorage.addWorker(mockWorker); - }); + WorkerStorage.addWorker(mockWorker) + }) it('should not crash when sharing data', () => { - const testData = { user: 'test', password: '123' }; - + const testData = { user: 'test', password: '123' } + // This should not throw an error expect(() => { - WorkerStorage.share(testData); - }).not.toThrow(); - }); -}); + WorkerStorage.share(testData) + }).not.toThrow() + }) +}) diff --git a/test/unit/worker_test.js b/test/unit/worker_test.js index 1759cc8e5..825ffb59f 100644 --- a/test/unit/worker_test.js +++ b/test/unit/worker_test.js @@ -334,12 +334,12 @@ describe('Workers', function () { expect('pool').not.equal('suite') }) - it('should handle pool mode result accumulation correctly', (done) => { + it('should handle pool mode result accumulation correctly', done => { const workerConfig = { by: 'pool', testConfig: './test/data/sandbox/codecept.workers.conf.js', } - + let resultEventCount = 0 const workers = new Workers(2, workerConfig) @@ -347,20 +347,20 @@ describe('Workers', function () { const originalResult = Container.result() const mockStats = { passes: 0, failures: 0, tests: 0 } const originalAddStats = originalResult.addStats.bind(originalResult) - - originalResult.addStats = (newStats) => { + + originalResult.addStats = newStats => { resultEventCount++ mockStats.passes += newStats.passes || 0 - mockStats.failures += newStats.failures || 0 + mockStats.failures += newStats.failures || 0 mockStats.tests += newStats.tests || 0 return originalAddStats(newStats) } - workers.on(event.all.result, (result) => { + workers.on(event.all.result, result => { // In pool mode, we should receive consolidated results, not individual test results // The number of result events should be limited (one per worker, not per test) expect(resultEventCount).to.be.lessThan(10) // Should be much less than total number of tests - + // Restore original method originalResult.addStats = originalAddStats done() From a9f59d4ee0f0b8ab2ed5efba62e93bb7efeba69a Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:08:45 +0000 Subject: [PATCH 4/6] fix: acceptance tests cannot access mock server --- .github/workflows/acceptance-tests.yml | 14 -------------- test/docker-compose.yml | 13 +++++++++++++ test/mock-server/server.js | 18 ++++++++++++++++++ test/rest/REST_test.js | 3 ++- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 66a80859c..e92699122 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -32,16 +32,6 @@ jobs: sudo apt-get update --allow-releaseinfo-change sudo apt-get install -y docker-compose - # Start mock server - - name: Start mock server - run: nohup npm run mock-server:start & - - name: Wait for mock server - run: | - for i in {1..20}; do - curl -sSf http://localhost:3001/api/users && break - sleep 1 - done - # Run rest tests using docker-compose - name: Run REST Tests run: docker-compose run --rm test-rest @@ -56,7 +46,3 @@ jobs: - name: Run Faker BDD Tests run: docker-compose run --rm test-bdd.faker working-directory: test - - # Stop mock server - - name: Stop mock server - run: npm run mock-server:stop diff --git a/test/docker-compose.yml b/test/docker-compose.yml index 45d8c1507..2bae7fa0e 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -7,9 +7,12 @@ services: env_file: .env volumes: - ./:/codecept/test + environment: + - MOCK_SERVER_HOST=mock_server command: ['/codecept/node_modules/.bin/mocha', 'test/rest'] depends_on: - json_server + - mock_server test-acceptance.webdriverio: build: .. @@ -73,6 +76,16 @@ services: - '8010:8010' # Expose to host restart: always # Automatically restart the container if it fails or becomes unhealthy + mock_server: + <<: *test-service + entrypoint: [] + command: npm run mock-server:start + ports: + - '3001:3001' # Expose to host + restart: always # Automatically restart the container if it fails or becomes unhealthy + environment: + - PORT=3001 + puppeteer-image: image: ghcr.io/puppeteer/puppeteer:22.4.1 diff --git a/test/mock-server/server.js b/test/mock-server/server.js index 7bad332ff..ba757d2b2 100644 --- a/test/mock-server/server.js +++ b/test/mock-server/server.js @@ -8,11 +8,29 @@ let users = [ { id: 2, name: 'Jane Smith', email: 'jane@example.com' }, ] +// Example comments data +let comments = [{ id: 1, postId: 1, text: 'Great post!' }] + // GET /api/users app.get('/api/users', (req, res) => { res.json({ data: users }) }) +// GET /api/comments/:id +app.get('/api/comments/:id', (req, res) => { + const comment = comments.find(c => c.id === parseInt(req.params.id)) + if (comment) { + return res.json({ + data: comment, + support: { + url: 'http://example.com/support', + text: 'Support information', + }, + }) + } + res.status(404).json({ error: 'Comment not found' }) +}) + // GET /api/users/:id app.get('/api/users/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)) diff --git a/test/rest/REST_test.js b/test/rest/REST_test.js index e6978ff37..99d005e4b 100644 --- a/test/rest/REST_test.js +++ b/test/rest/REST_test.js @@ -150,7 +150,8 @@ describe('REST', () => { }) it('should be able to parse JSON responses', async () => { - await I.sendGetRequest('http://localhost:3001/api/comments/1', { 'x-api-key': 'reqres-free-v1' }) + const mockServerHost = process.env.MOCK_SERVER_HOST || 'localhost' + await I.sendGetRequest(`http://${mockServerHost}:3001/api/comments/1`, { 'x-api-key': 'reqres-free-v1' }) await jsonResponse.seeResponseCodeIsSuccessful() await jsonResponse.seeResponseContainsKeys(['data', 'support']) }) From 7d298cfe2e896bbcd7d7e02aafc93b4b3f45b8f4 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:15:39 +0000 Subject: [PATCH 5/6] fix: acceptance tests cannot access mock server --- test/data/app/view/form/fetch_call.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/data/app/view/form/fetch_call.php b/test/data/app/view/form/fetch_call.php index 826ea2795..ae7ca67c8 100644 --- a/test/data/app/view/form/fetch_call.php +++ b/test/data/app/view/form/fetch_call.php @@ -53,7 +53,7 @@ const getPostData = () => getData("https://jsonplaceholder.typicode.com/posts/1"); const getCommentsData = () => - getData("http://localhost:3001/api/comments/1"); + getData("http://localhost:3001/api/comments/1"); const getUsersData = () => getData("https://jsonplaceholder.typicode.com/users/1"); From 20034189297513ecbcbf325adecdc9ed9c23474e Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:25:18 +0000 Subject: [PATCH 6/6] fix: flaky timeout test --- test/runner/timeout_test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/runner/timeout_test.js b/test/runner/timeout_test.js index 64090acb8..2f1d8c1d5 100644 --- a/test/runner/timeout_test.js +++ b/test/runner/timeout_test.js @@ -16,7 +16,8 @@ describe('CodeceptJS Timeouts', function () { exec(config_run_config('codecept.conf.js', 'timed out'), (err, stdout) => { debug_this_test && console.log(stdout) expect(stdout).toContain('Timeout 2s exceeded (with Before hook)') - expect(stdout).toContain('Timeout 1s exceeded (with Before hook)') + // The second scenario can show either format depending on which timeout triggers first + expect(stdout.includes('Timeout 1s exceeded (with Before hook)') || stdout.includes('timed out after 1s')).toBeTruthy() expect(err).toBeTruthy() done() })