Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d3fbcf9
move offline agents filtering to query root
nazabucciarelli Mar 10, 2026
04dc75a
fix user presence update from test users
nazabucciarelli Mar 11, 2026
81cf18e
fix linter error
nazabucciarelli Mar 11, 2026
391cce0
Merge branch 'develop' into fix/livechat-offline-agent-assignment
nazabucciarelli Mar 11, 2026
cae2152
rollback TEST_MODE conditional
nazabucciarelli Mar 11, 2026
0942d4a
add ws user helpers to fix connectionStatus on tests
nazabucciarelli Mar 12, 2026
9102e16
test on CI without closing ws
nazabucciarelli Mar 12, 2026
395c57b
add use of setUserAwayWS to routing test
nazabucciarelli Mar 12, 2026
6ebec42
enhance ws connection, add ws closures
nazabucciarelli Mar 13, 2026
8b41cd9
change ws port to 3000
nazabucciarelli Mar 13, 2026
6f3c33f
update idle status tests from routing
nazabucciarelli Mar 13, 2026
79d8eb9
fix playwright tests
nazabucciarelli Mar 16, 2026
46d52d0
add ws closure to playwright tests
nazabucciarelli Mar 16, 2026
6c3b010
improve ws cleanup on rooms test, make ws port depend on CI env variable
nazabucciarelli Mar 16, 2026
8137749
fix conflict with develop
nazabucciarelli Mar 17, 2026
7c2f150
remove unintended test lines
nazabucciarelli Mar 17, 2026
fcd37ad
Merge branch 'develop' into fix/livechat-offline-agent-assignment
nazabucciarelli Mar 17, 2026
02239ef
add changeset
nazabucciarelli Mar 17, 2026
ba26d7f
remove unneeded ws closures
nazabucciarelli Mar 17, 2026
e287b5b
handle errors on websockets result
nazabucciarelli Mar 17, 2026
1ae6230
remove accidental timeout
nazabucciarelli Mar 18, 2026
6134a5c
add DDP_LOGIN_PORT env variable for ws connection
nazabucciarelli Mar 19, 2026
f6e4a54
Merge branch 'develop' into fix/livechat-offline-agent-assignment
nazabucciarelli Mar 19, 2026
fca1d9a
Correct phrasing in changeset description
nazabucciarelli Mar 19, 2026
0b3395c
add forgotten CI new env variable
nazabucciarelli Mar 19, 2026
e74d5af
Merge branch 'fix/livechat-offline-agent-assignment' of github.com:Ro…
nazabucciarelli Mar 19, 2026
6d760f1
safe ws closure
nazabucciarelli Mar 19, 2026
7199cc4
enhance overall WS connection logic
nazabucciarelli Mar 20, 2026
d808a27
add onError handler for ws
nazabucciarelli Mar 20, 2026
6f6597e
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into fix/…
nazabucciarelli Apr 7, 2026
b19afad
modify changeset from patch to minor
nazabucciarelli Apr 8, 2026
06820bc
remove sendAction parameter in favor of stringifiedJsonPayload
nazabucciarelli Apr 8, 2026
c75d663
add comment to document auto-assignment bussines rule
nazabucciarelli Apr 8, 2026
402aad3
change websocket variables naming
nazabucciarelli Apr 8, 2026
f4c0f10
Update .changeset/purple-boxes-shout.md
KevLehman Apr 13, 2026
d03991e
Update .changeset/purple-boxes-shout.md
KevLehman Apr 13, 2026
928583f
remove ddpLogin from livechat test
nazabucciarelli Apr 14, 2026
8bfbc05
Merge branch 'fix/livechat-offline-agent-assignment' of github.com:Ro…
nazabucciarelli Apr 14, 2026
a4845d9
update e2e tests to not use ddpLogin
nazabucciarelli Apr 14, 2026
b047bc0
implement omnichannel room forward e2e tests
nazabucciarelli Apr 14, 2026
b35f583
remove converted api tests and ws logic from 00-rooms.ts
nazabucciarelli Apr 14, 2026
99144ed
add e2e test for livechat_enabled_when_agent_idle setting
nazabucciarelli Apr 14, 2026
17ffb08
remove converted tests from 24-routing.ts
nazabucciarelli Apr 14, 2026
8dae157
rollback ws logic
nazabucciarelli Apr 15, 2026
9303454
remove ci-test-e2e.yml added env variable
nazabucciarelli Apr 15, 2026
489e27f
remove .only
nazabucciarelli Apr 15, 2026
484e3c2
fix livechat test by adding agent browser context
nazabucciarelli Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/purple-boxes-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/models': minor
'@rocket.chat/meteor': minor
---

Updates omnichannel routing so agents with `offline` status are always excluded from assignment. The `Livechat_enabled_when_agent_idle` setting now only affects agents with `away` status.
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,25 @@ test.describe('OC - Tags Visibility', () => {
});
});

test.beforeAll('Create conversations', async ({ api }) => {
conversations = await Promise.all([
createConversation(api, { visitorName: visitorA.name, agentId: 'user1', departmentId: departmentA.data._id }),
createConversation(api, { visitorName: visitorB.name, agentId: 'user1', departmentId: departmentB.data._id }),
]);
});

test.beforeEach(async ({ page }) => {
test.beforeEach(async ({ page, api }) => {
poOmnichannel = new HomeOmnichannel(page);
await page.goto('/');
await poOmnichannel.waitForHome();

if (conversations.length === 0) {
conversations = await Promise.all([
createConversation(api, {
visitorName: visitorA.name,
agentId: 'user1',
departmentId: departmentA.data._id,
}),
createConversation(api, {
visitorName: visitorB.name,
agentId: 'user1',
departmentId: departmentB.data._id,
}),
]);
}
});

test.afterAll(async ({ api }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test.describe.serial('OC - Custom fields usage, scope : room and visitor', () =>
test.beforeAll('Set up agent, manager and custom fields', async ({ api }) => {
[agent, manager] = await Promise.all([createAgent(api, 'user1'), createManager(api, 'user1')]);

[roomCustomField, visitorCustomField, conversation] = await Promise.all([
[roomCustomField, visitorCustomField] = await Promise.all([
createCustomField(api, {
field: roomCustomFieldLabel,
label: roomCustomFieldName,
Expand All @@ -45,12 +45,19 @@ test.describe.serial('OC - Custom fields usage, scope : room and visitor', () =>
label: visitorCustomFieldName,
scope: 'visitor',
}),
createConversation(api, {
visitorName: visitor.name,
agentId: 'user1',
visitorToken,
}),
]);
});

test.beforeEach(async ({ page, api }) => {
poHomeChannel = new HomeOmnichannel(page);
await page.goto('/');
await poHomeChannel.waitForHome();

conversation = await createConversation(api, {
visitorName: visitor.name,
agentId: 'user1',
visitorToken,
});

await setVisitorCustomFieldValue(api, {
token: visitorToken,
Expand All @@ -59,12 +66,6 @@ test.describe.serial('OC - Custom fields usage, scope : room and visitor', () =>
});
});

test.beforeEach(async ({ page }) => {
poHomeChannel = new HomeOmnichannel(page);
await page.goto('/');
await poHomeChannel.waitForHome();
});

test.afterAll('Remove agent, manager, custom fields and conversation', async () => {
await Promise.all([agent.delete(), manager.delete(), roomCustomField.delete(), visitorCustomField.delete(), conversation.delete()]);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type { Page } from '@playwright/test';

import { sleep } from '../../../lib/utils/sleep';
import { createFakeVisitor } from '../../mocks/data';
import { IS_EE } from '../config/constants';
import { createAuxContext } from '../fixtures/createAuxContext';
import { Users } from '../fixtures/userStates';
import { OmnichannelLiveChat } from '../page-objects/omnichannel';
import { setSettingValueById } from '../utils';
import { createAgent } from '../utils/omnichannel/agents';
import { addAgentToDepartment, createDepartment } from '../utils/omnichannel/departments';
import { test, expect } from '../utils/test';

test.use({ storageState: Users.user1.state });

test.describe('OC - Routing to Idle Agents', () => {
test.skip(!IS_EE, 'Enterprise Edition Only');

let poLivechat: OmnichannelLiveChat;
let livechatPage: Page;

let agent: Awaited<ReturnType<typeof createAgent>>;

let visitor: { name: string; email: string };

let testDepartment: Awaited<ReturnType<typeof createDepartment>>;

const routingMethods = ['Auto_Selection', 'Load_Balancing', 'Load_Rotation'];

test.beforeAll(async ({ api }) => {
[agent, testDepartment] = await Promise.all([
createAgent(api, 'user1'),
createDepartment(api, { name: 'Idle Routing Dept' }),
setSettingValueById(api, 'Accounts_Default_User_Preferences_idleTimeLimit', 300),
expect((await setSettingValueById(api, 'Omnichannel_enable_department_removal', true)).status()).toBe(200),
]);

await Promise.all([addAgentToDepartment(api, { department: testDepartment.data, agentId: 'user1' })]);
});

test.beforeEach(async ({ page, browser, api }) => {
visitor = createFakeVisitor();

({ page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false));
poLivechat = new OmnichannelLiveChat(livechatPage, api);

await page.goto('/');
await page.locator('#main-content').waitFor();
});

test.afterEach(async ({ api }) => {
if (livechatPage) await livechatPage.context().close();

await setSettingValueById(api, 'Accounts_Default_User_Preferences_idleTimeLimit', 300);
});

test.afterAll(async ({ api }) => {
await Promise.all([
testDepartment.delete(),
agent.delete(),
setSettingValueById(api, 'Livechat_Routing_Method', 'Auto_Selection'),
setSettingValueById(api, 'Livechat_enabled_when_agent_idle', false),
expect((await setSettingValueById(api, 'Omnichannel_enable_department_removal', false)).status()).toBe(200),
]);
});

routingMethods.forEach((routingMethod) => {
test.describe(`Routing method: ${routingMethod}`, () => {
test(`should not route to idle agents`, async ({ api, page }) => {
await test.step(`Setup routing method to ${routingMethod} and ignore idle agents`, async () => {
await setSettingValueById(api, 'Livechat_Routing_Method', routingMethod);
await setSettingValueById(api, 'Livechat_enabled_when_agent_idle', false);
});

await test.step('Visitor tries to initiate a conversation with the away agent', async () => {
await poLivechat.page.reload();
await poLivechat.openAnyLiveChat();
await poLivechat.sendMessage(visitor, false);
await poLivechat.onlineAgentMessage.fill('Hello from visitor');

// Force Agent to become away by idle timeout
await setSettingValueById(api, 'Accounts_Default_User_Preferences_idleTimeLimit', 1);
await page.reload();
await page.locator('#main-content').waitFor();

await sleep(2000); // we give the agent time to go statusConnection: away
await poLivechat.btnSendMessageToOnlineAgent.click();
});

await test.step('Verify visitor is not taken by agent', async () => {
await expect(
poLivechat.alertMessage('Error starting a new conversation: Sorry, no online agents [no-agent-online]'),
).toBeVisible();
});
});

test(`should route to agents even if they are idle when setting is enabled`, async ({ api, page }) => {
await test.step(`Setup routing method to ${routingMethod} and allow idle agents`, async () => {
await setSettingValueById(api, 'Livechat_Routing_Method', routingMethod);
await setSettingValueById(api, 'Livechat_enabled_when_agent_idle', true);
});

await test.step('Force agent to become away by idle timeout', async () => {
await setSettingValueById(api, 'Accounts_Default_User_Preferences_idleTimeLimit', 1);
await page.reload();
await page.locator('#main-content').waitFor();

await sleep(2000); // we give the agent time to go statusConnection: away
});

await test.step('Visitor initiates chat', async () => {
await poLivechat.page.reload();
await poLivechat.openAnyLiveChat();
await poLivechat.sendMessage(visitor, false);
await poLivechat.onlineAgentMessage.fill('test message');
await poLivechat.btnSendMessageToOnlineAgent.click();
});

await test.step('Verify chat is served to an agent', async () => {
await expect(poLivechat.headerTitle).toHaveText(agent.data.username);
});
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,10 @@ test.describe('OC - Livechat - Close chat using widget', () => {
let poLiveChat: OmnichannelLiveChat;
let agent: Awaited<ReturnType<typeof createAgent>>;

test.beforeAll(async ({ api }) => {
test.beforeAll(async ({ api, browser }) => {
agent = await createAgent(api, 'user1');

await createAuxContext(browser, Users.user1, '/', true);
});

test.beforeEach(async ({ page, api }) => {
Expand Down
Loading