From 2267b428cf78b7ac4406594af14950a3b19677b2 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 23 Jul 2026 14:20:12 -0300 Subject: [PATCH 1/3] test: deflake livechat business-hours and room.forward queue tests - 19-business-hours: closing the default BH recalculates agent status asynchronously; the [CE][BH] closed test created the new agent before the close propagated, so it came up available. Poll the existing agent until it loses the BH assignment before creating the new one. - 00-rooms: the waiting_queue forward test leaves the room queued (never served); cleanup used room.closeByUser, which rejects a room that is not being served (400). Close it as the visitor instead. --- .../tests/end-to-end/api/livechat/00-rooms.ts | 4 +++- .../end-to-end/api/livechat/19-business-hours.ts | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index 3105c34cf917b..644f84fbfe99c 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -1787,10 +1787,12 @@ describe('LIVECHAT - rooms', () => { expect(inquiry).to.have.property('department', targetDepartment._id); expect(inquiry).to.have.property('status', 'queued'); + // the room ends queued (never taken), so close it as the visitor — room.closeByUser rejects a room that is not being served + await request.post(api('livechat/room.close')).send({ rid: newRoom._id, token: newVisitor.token }).expect(200); + await Promise.all([ deleteDepartment(initialDepartment._id), deleteDepartment(targetDepartment._id), - closeOmnichannelRoom(newRoom._id), deleteVisitor(newVisitor.token), deleteUser(manager), updateSetting('Livechat_waiting_queue', false), diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index 3aa8f6baba33e..b2ab0a99853ff 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -32,6 +32,17 @@ import type { TestUser } from '../../../data/users.helper'; import { setUserActiveStatus, createUser, deleteUser, getMe, getUserByUsername, login } from '../../../data/users.helper'; import { IS_EE } from '../../../e2e/config/constants'; +const waitForAgent = async (creds: Credentials, predicate: (agent: ILivechatAgent) => boolean, attempts = 20): Promise => { + for (let i = 0; i < attempts; i++) { + const current: ILivechatAgent = await getMe(creds); + if (predicate(current)) { + return; + } + await sleep(250); + } + throw new Error('timed out waiting for agent business hours to update'); +}; + describe('LIVECHAT - business hours', () => { before((done) => getCredentials(done)); @@ -962,6 +973,10 @@ describe('LIVECHAT - business hours', () => { it('should create a new agent and verify if it is assigned to the default business hour which is closed', async () => { await openOrCloseBusinessHour(defaultBH, false); + // wait for the close to propagate (existing agent loses the BH assignment) before creating the new agent, + // otherwise the new agent can be created while the BH still counts as open and comes up available + await waitForAgent(agentCredentials, (a) => (a.openBusinessHours?.length ?? 0) === 0); + const newUser: ILivechatAgent = await createUser(); const newUserCredentials = await login(newUser.username, password); await createAgent(newUser.username); From 2f6e8107fd85f1cc7364ead1d18d4a2dee736ded Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 23 Jul 2026 16:09:08 -0300 Subject: [PATCH 2/3] test: use existing retry helper for BH close propagation wait --- .../api/livechat/19-business-hours.ts | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index b2ab0a99853ff..9ef34492f9f1d 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -31,17 +31,7 @@ import { password } from '../../../data/user'; import type { TestUser } from '../../../data/users.helper'; import { setUserActiveStatus, createUser, deleteUser, getMe, getUserByUsername, login } from '../../../data/users.helper'; import { IS_EE } from '../../../e2e/config/constants'; - -const waitForAgent = async (creds: Credentials, predicate: (agent: ILivechatAgent) => boolean, attempts = 20): Promise => { - for (let i = 0; i < attempts; i++) { - const current: ILivechatAgent = await getMe(creds); - if (predicate(current)) { - return; - } - await sleep(250); - } - throw new Error('timed out waiting for agent business hours to update'); -}; +import { retry } from '../helpers/retry'; describe('LIVECHAT - business hours', () => { before((done) => getCredentials(done)); @@ -973,9 +963,16 @@ describe('LIVECHAT - business hours', () => { it('should create a new agent and verify if it is assigned to the default business hour which is closed', async () => { await openOrCloseBusinessHour(defaultBH, false); - // wait for the close to propagate (existing agent loses the BH assignment) before creating the new agent, - // otherwise the new agent can be created while the BH still counts as open and comes up available - await waitForAgent(agentCredentials, (a) => (a.openBusinessHours?.length ?? 0) === 0); + // the BH close recalculates agent statuses asynchronously; wait for it to propagate (existing agent loses the + // BH assignment) before creating the new agent, otherwise it is created while the BH still counts as open + await retry( + 'BH close propagation is async, so the existing agent may still be assigned on the first fetch', + async () => { + const current: ILivechatAgent = await getMe(agentCredentials); + expect(current.openBusinessHours ?? []).to.have.lengthOf(0); + }, + { retries: 20, delayMs: 250 }, + ); const newUser: ILivechatAgent = await createUser(); const newUserCredentials = await login(newUser.username, password); From d2e3b6de8ee387ae9c667908cc10dbaeeef0a1bc Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 24 Jul 2026 18:49:38 -0300 Subject: [PATCH 3/3] test: parallelize cleanup in rooms-served-by-agents test The test's heavy sequential setup plus cleanup exceeded the 10s mocha timeout on the slower FIPS runner. Close both rooms in parallel, then remove visitors and departments in parallel (keeping close-before-delete ordering), cutting several sequential round-trips off the test. --- .../tests/end-to-end/api/livechat/00-rooms.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index 644f84fbfe99c..a660163e40eca 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -664,11 +664,14 @@ describe('LIVECHAT - rooms', () => { expect(body.rooms.some((room: IOmnichannelRoom) => room._id === expectedRoom._id)).to.be.true; expect(body.rooms.some((room: IOmnichannelRoom) => room._id === expectedRoom2._id)).to.be.true; - await closeOmnichannelRoom(expectedRoom._id); - await closeOmnichannelRoom(expectedRoom2._id); - await deleteVisitor(expectedVisitor.token); - await deleteVisitor(expectedVisitor2.token); - await Promise.all([deleteDepartment(department._id), deleteDepartment(department2._id)]); + // close both rooms before removing visitors/departments (deleting a department with an open room fails) + await Promise.all([closeOmnichannelRoom(expectedRoom._id), closeOmnichannelRoom(expectedRoom2._id)]); + await Promise.all([ + deleteVisitor(expectedVisitor.token), + deleteVisitor(expectedVisitor2.token), + deleteDepartment(department._id), + deleteDepartment(department2._id), + ]); }); (IS_EE ? it : it.skip)('should return only rooms with the given tags', async () => { const tag = await saveTags();