Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
dc009dd
WIP add config and basic SMS test methods
stephenhand Jul 15, 2026
396ff36
Potential fix for pull request finding
stephenhand Jul 15, 2026
88a9075
Potential fix for pull request finding
stephenhand Jul 15, 2026
30f7c09
fix: use chat api for e2e channel cleanup lookup
Copilot Jul 15, 2026
859eca5
Initial plan
Copilot Jul 15, 2026
a3f6730
feat: add SMS E2E test mirroring webchat test with shared ChatStateme…
Copilot Jul 15, 2026
5b6c919
Licence headers
stephenhand Jul 15, 2026
c0ee276
Merge branch 'CHI-3711-sms_e2e_test' into copilot/wip-add-config-and-…
stephenhand Jul 15, 2026
6cfb45f
Merge branch 'master' into copilot/wip-add-config-and-basic-sms-test-…
stephenhand Jul 16, 2026
bde9a95
Merge branch 'master' into copilot/wip-add-config-and-basic-sms-test-…
stephenhand Jul 17, 2026
64dd61e
feat: add SMS channel for E2E development environment
Copilot Jul 17, 2026
e036e2c
fix: use consistent SMS capitalization in friendly_name
Copilot Jul 17, 2026
8d7e138
Merge branch 'master' into CHI-3711-sms_e2e_test
stephenhand Jul 20, 2026
36c8d90
Merge branch 'CHI-3711-sms_e2e_test' into copilot/wip-add-config-and-…
stephenhand Jul 20, 2026
2d3f805
refactor: hardcode E2E SMS channel phone number +12607821891
Copilot Jul 20, 2026
0d150f9
refactor: remove redundant additional.configure.tf for SMS address
Copilot Jul 20, 2026
77c4c7e
feat: add voice channel for E2E development environment
Copilot Jul 22, 2026
cc25f5d
WIP voice testing
stephenhand Jul 22, 2026
d594f50
Merge branch 'copilot/wip-add-config-and-basic-sms-test-methods' into…
stephenhand Jul 22, 2026
9a0cbe8
First passing voice E2E test
stephenhand Jul 22, 2026
9c75bcc
Licence
stephenhand Jul 22, 2026
cfdb3b2
WIP add config and basic SMS test methods
stephenhand Jul 15, 2026
c7e8ebd
Potential fix for pull request finding
stephenhand Jul 15, 2026
78fe83e
Potential fix for pull request finding
stephenhand Jul 15, 2026
aacc4b9
fix: use chat api for e2e channel cleanup lookup
Copilot Jul 15, 2026
62bc1fe
Initial plan
Copilot Jul 15, 2026
dcba977
feat: add SMS E2E test mirroring webchat test with shared ChatStateme…
Copilot Jul 15, 2026
dece3bc
Licence headers
stephenhand Jul 15, 2026
fbbc441
feat: add SMS channel for E2E development environment
Copilot Jul 17, 2026
49b86ec
fix: use consistent SMS capitalization in friendly_name
Copilot Jul 17, 2026
d65a26d
refactor: hardcode E2E SMS channel phone number +12607821891
Copilot Jul 20, 2026
5e70458
refactor: remove redundant additional.configure.tf for SMS address
Copilot Jul 20, 2026
fc9a0b4
WIP voice testing
stephenhand Jul 22, 2026
188b5d6
feat: add voice channel for E2E development environment
Copilot Jul 22, 2026
f1144a1
First passing voice E2E test
stephenhand Jul 22, 2026
64730de
Licence
stephenhand Jul 22, 2026
4547284
Fix offlie contact e2e test
stephenhand Jul 22, 2026
6c571c7
Merge remote-tracking branch 'origin/CHI-3711-voice_test_poc' into CH…
stephenhand Jul 22, 2026
465730d
Fix offlie contact e2e test
stephenhand Jul 22, 2026
cf32d57
Linter
stephenhand Jul 22, 2026
1a324b0
Merge branch 'master' into copilot/wip-add-config-and-basic-sms-test-…
stephenhand Jul 22, 2026
d4c6192
Extra assertion in E2E tests
stephenhand Jul 23, 2026
8cc9d5e
Fake sound input for all E2E browsers, not just those running in a la…
stephenhand Jul 23, 2026
e37b2a8
Revert local change
stephenhand Jul 23, 2026
42daf15
Linter
stephenhand Jul 23, 2026
5b95060
Merge branch 'copilot/wip-add-config-and-basic-sms-test-methods' into…
stephenhand Jul 23, 2026
5fb2a6f
Apply suggestions from code review
stephenhand Jul 23, 2026
02ebb61
Remove E2E voice queue
stephenhand Jul 23, 2026
32a32d2
Merge remote-tracking branch 'origin/CHI-3711-voice_test_poc' into CH…
stephenhand Jul 23, 2026
8920634
Merge branch 'master' into CHI-3711-voice_test_poc
stephenhand Jul 23, 2026
8924aba
Revert unused update
stephenhand Jul 23, 2026
b6fd887
Merge branch 'master' into CHI-3711-voice_test_poc
stephenhand Jul 23, 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
62 changes: 62 additions & 0 deletions e2e-tests/chatScripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,65 @@ export const getWebchatScript = (): ChatStatement[] => {

return defaultScript;
};

/**
* SMS scripts are structurally the same as webchat scripts (using the same ChatStatement format),
* but must start with a CALLER statement since the client must initiate an SMS conversation.
* BOT messages are expected to arrive after the client sends its first message.
*/
export const defaultSmsScript: ChatStatement[] = [
callerStatement('hi'),
botStatement('Welcome to the helpline. Please answer the following questions.'),
callerStatement('yes'),
botStatement('How old are you?'),
callerStatement('10'),
botStatement('What is your gender?'),
callerStatement('girl'),
botStatement('We will transfer you now. Please hold for a counsellor.'),
counselorAutoStatement('Hi, this is the counsellor. How can I help you?'),
callerStatement('CALLER TEST SMS MESSAGE'),
counselorStatement('COUNSELLOR TEST SMS MESSAGE'),
];

export const smsCommonScripts: Record<string, ChatStatement[]> = {
ca: [
callerStatement('CALLER TEST SMS MESSAGE'),
counselorAutoStatement("Hi, you've reached a counsellor. What would you like to talk about?"),
counselorStatement('COUNSELLOR TEST SMS MESSAGE'),
],
};

export const smsEnvScripts: Record<string, Record<string, ChatStatement[]>> = {
development: {
as: [
callerStatement('hi'),
botStatement("Sorry, I didn't understand that. Please try again."),
callerStatement('hi'),
botStatement('Are you calling about yourself? Please answer Yes or No.'),
callerStatement('yes'),
botStatement('How old are you?'),
callerStatement('10'),
botStatement('What is your gender?'),
callerStatement('girl'),
botStatement("We'll transfer you now. Please hold for a counsellor."),
counselorAutoStatement('Hi, this is the counsellor. How can I help you?'),
callerStatement('CALLER TEST SMS MESSAGE'),
counselorStatement('COUNSELLOR TEST SMS MESSAGE'),
],
},
};

export const getSmsScript = (): ChatStatement[] => {
const helplineShortCode = getConfigValue('helplineShortCode') as string;
const helplineEnv = getConfigValue('helplineEnv') as string;

if (smsEnvScripts[helplineEnv]?.[helplineShortCode]) {
return smsEnvScripts[helplineEnv][helplineShortCode];
}

if (smsCommonScripts[helplineShortCode]) {
return smsCommonScripts[helplineShortCode];
}

return defaultSmsScript;
};
41 changes: 40 additions & 1 deletion e2e-tests/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ export type Config = {
};

const helplineShortCode = process.env.HL?.toLocaleLowerCase() || 'e2e';
// Account to initiate calls into the helpline under test from, for testing voice & SMS
const helplineEnv = process.env.HL_ENV?.toLocaleLowerCase() || 'local';
const clientHelplineShortCode = process.env.CLIENT_HL?.toLocaleLowerCase() || 'as';
const clientHelplineEnv = process.env.CLIENT_HL_ENV?.toLocaleLowerCase() || 'development';
const shouldLoadFromSsm = process.env.LOAD_SSM_CONFIG && process.env.LOAD_SSM_CONFIG !== 'false';

// These are environments where we want to avoid tests or steps that update HRM data
Expand Down Expand Up @@ -135,6 +138,18 @@ const configOptions: ConfigOptions = {
ssmPath: () => `/${localOverrideEnv}/twilio/${getConfigValue('twilioAccountSid')}/auth_token`,
},

// The twilio account sid and auth token are used to target a flex account
clientTwilioAccountSid: {
envKey: 'CLIENT_TWILIO_ACCOUNT_SID',
ssmPath: `/${clientHelplineEnv}/twilio/${clientHelplineShortCode.toUpperCase()}/account_sid`,
},
clientTwilioAuthToken: {
envKey: 'CLIENT_TWILIO_AUTH_TOKEN',
// Order is important here. We use a function so that we can reference the clientTwilioAccountSid config value above.
ssmPath: () =>
`/${clientHelplineEnv}/twilio/${getConfigValue('clientTwilioAccountSid')}/auth_token`,
},

// Turn on debug mode. Possibly unused.
debug: {
envKey: 'DEBUG',
Expand Down Expand Up @@ -180,6 +195,30 @@ const configOptions: ConfigOptions = {
default: `https://assets-${localOverrideEnv}.tl.techmatters.org/aselo-webchat-react-app/${helplineShortCode}/?e2eTestMode=true`,
},

// This should match the number set up for the Voice studio flow on the helpline under test
voicePhoneNumber: {
envKey: 'VOICE_PHONE_NUMBER',
default: '+12607821891',
},

// This should match the number set up on the clientTwilioAccountSid that can make outgoing calls
clientVoicePhoneNumber: {
envKey: 'CLIENT_VOICE_PHONE_NUMBER',
default: '+12064083885',
},

// This should match the number set up for the SMS studio flow on the helpline under test
smsPhoneNumber: {
envKey: 'SMS_PHONE_NUMBER',
default: () => getConfigValue('voicePhoneNumber'),
},

// This should match the number set up on the clientTwilioAccountSid that can send outgoing SMS messages
clientSmsPhoneNumber: {
envKey: 'CLIENT_SMS_PHONE_NUMBER',
default: () => getConfigValue('clientVoicePhoneNumber'),
},

// inLambda is used to determine if we are running in a lambda or not and set other config values accordingly
inLambda: {
envKey: 'TEST_IN_LAMBDA',
Expand Down Expand Up @@ -256,7 +295,7 @@ const setConfigValueFromSsm = async (key: string) => {
throw err;
}

console.log(`Failed to load config value from SSM at ${option.ssmPath}. Using default value`);
console.warn(`Failed to load config value from SSM at ${option.ssmPath}. Using default value`);

setConfigValue(key, typeof option.default === 'function' ? option.default() : option.default);
}
Expand Down
27 changes: 25 additions & 2 deletions e2e-tests/contactForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function contactForm(page: Page) {
}
}

return {
const formApi = {
selectChildCallType: async () => {
const childCallTypeButton = selectors.childCallTypeButton();
const responsePromise = page.waitForResponse('**/contacts/**');
Expand All @@ -100,6 +100,28 @@ export function contactForm(page: Page) {
await tab.fill(tab);
}
},
fillWithContent: async (formContent: any) => {
await formApi.fill([
<ContactFormTab>{
id: 'childInformation',
label: 'TabbedForms-AddChildInfoTab',
fill: formApi.fillStandardTab,
items: formContent.childInformation,
},
<ContactFormTab<Categories>>{
id: 'categories',
label: 'TabbedForms-CategoriesTab',
fill: formApi.fillCategoriesTab,
items: formContent.categories,
},
<ContactFormTab>{
id: 'caseInformation',
label: 'TabbedForms-AddCaseInfoTab',
fill: formApi.fillStandardTab,
items: formContent.caseInformation,
},
]);
},
save: async ({ saveAndAddToCase }: { saveAndAddToCase?: boolean } = {}) => {
const tab = {
id: 'caseInformation',
Expand All @@ -124,5 +146,6 @@ export function contactForm(page: Page) {
},
fillCategoriesTab,
fillStandardTab,
};
} as const;
return formApi;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@
* send new messages from the e2e test user.
*/

import { deleteChatChannels } from './twilio/channels';
import { deleteChatConversations, deleteSmsConversations } from './twilio/channels';
import { initConfig } from './config';

const main = async () => {
await initConfig();
await deleteChatChannels();
await deleteChatConversations();
await deleteSmsConversations();
};

main();
18 changes: 17 additions & 1 deletion e2e-tests/formContentsByHelpline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const formContentsByHelpline = {
Accessibility: ['Education'],
},
caseInformation: {
callSummary: 'E2E TEST CALL',
callSummary: 'E2E TEST PREPOPULATED FORM',
},
},
ca: {
Expand All @@ -54,3 +54,19 @@ export const formContentsByHelpline = {
},
},
};

export const formContentsByHelplineForEmptyForm = {
...formContentsByHelpline,
e2e: {
...formContentsByHelpline.e2e,
childInformation: {
...formContentsByHelpline.e2e.childInformation,

gender: 'Unknown',
age: 'Unknown',
},
caseInformation: {
callSummary: 'E2E TEST EMPTY FORM',
},
},
};
10 changes: 5 additions & 5 deletions e2e-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
"main": "index.js",
"scripts": {
"postinstall": "npx playwright install chromium",
"deleteChatChannels": "tsx deleteChatChannels.ts",
"deleteChatChannels": "tsx deleteConversations.ts",
"test": "npx playwright test --workers 1 ",
"test:ui": "npx playwright test --workers 1 --config ui-tests/playwright.ui-test.config.ts",
"test:local": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true npm run test",
"test:local": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true npm run test -- --retries 0",
"test:local-aselo-webchat": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true ASELO_WEBCHAT_URL=http://localhost:3001 npm run test -- --headed --debug --retries 0 aseloWebchat",
"test:local:debug": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true npm run test -- --headed --debug --retries 0 offline",
"test:local:debug": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true npm run test -- --headed --debug --retries 0",
"test:development:as": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true HL_ENV=development HL=as SKIP_DATA_UPDATE=true npm run test",
"test:development:as:debug": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true HL_ENV=development HL=as SKIP_DATA_UPDATE=true npm run test -- --headed --retries 0",
"test:development:e2e": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true HL_ENV=development HL=e2e npm run test",
"test:development:e2e:local": "cross-env LOAD_SSM_CONFIG=true HL_ENV=development HL=e2e npm run test -- --headed",
"test:development:e2e:debug": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true HL_ENV=development HL=e2e npm run test -- --headed --debug --retries 0 login",
"test:development:e2e:debug": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true HL_ENV=development HL=e2e npm run test -- --headed --debug --retries 0",
"test:staging:ca": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true HL_ENV=staging HL=ca npm run test",
"test:staging:ca:headed": "cross-env LOAD_SSM_CONFIG=true HL_ENV=staging HL=ca npm test -- --headed --debug --retries 0 aseloWebchat",
"test:staging:ca:headed": "cross-env LOAD_SSM_CONFIG=true HL_ENV=staging HL=ca npm test -- --headed --debug --retries 0",
"test:production:ca": "cross-env DEBUG=pw:api LOAD_SSM_CONFIG=true HL_ENV=production HL=ca npm run test",
"lint": "eslint --ext ts .",
"lint:fix": "npm run lint -- --fix .",
Expand Down
9 changes: 6 additions & 3 deletions e2e-tests/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { getConfigValue } from './config';

const inLambda = getConfigValue('inLambda') as boolean;

const browserArgs = ['--use-fake-ui-for-media-stream', '--use-fake-device-for-media-stream'];

const playwrightConfig: PlaywrightTestConfig = {
globalSetup: require.resolve('./global-setup'),
use: {
Expand All @@ -40,6 +42,7 @@ const playwrightConfig: PlaywrightTestConfig = {
* of chromium/Playwright. We use the `TEST_NAME` environment variable to set
* a unique target for each test that runs in lambdas to avoid this issue.
*/
...browserArgs,
'--single-process',
'--autoplay-policy=user-gesture-required',
'--disable-background-networking',
Expand Down Expand Up @@ -81,11 +84,11 @@ const playwrightConfig: PlaywrightTestConfig = {
'--disable-gpu',
'--use-gl=swiftshader',
'--autoplay-policy=no-user-gesture-required',
'--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream',
],
}
: {},
: {
args: browserArgs,
},
},
testDir: './tests',
retries: inLambda ? 0 : 1,
Expand Down
23 changes: 2 additions & 21 deletions e2e-tests/tests/aseloWebchat.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { getWebchatScript } from '../chatScripts';
import { flexChat } from '../flexChat';
import { skipTestIfNotTargeted } from '../skipTest';
import { tasks } from '../tasks';
import { Categories, contactForm, ContactFormTab } from '../contactForm';
import { contactForm } from '../contactForm';
import { deleteAllTasksInQueue } from '../twilio/tasks';
import { notificationBar } from '../notificationBar';
import { clickThroughTwilioPasteModals } from '../agent-desktop';
Expand Down Expand Up @@ -102,26 +102,7 @@ test.describe.serial('Aselo web chat caller', () => {
throw new Error(`No form contents configured for helplineShortCode="${String(helpline)}"`);
}
const form = contactForm(pluginPage);
await form.fill([
<ContactFormTab>{
id: 'childInformation',
label: 'TabbedForms-AddChildInfoTab',
fill: form.fillStandardTab,
items: formContent.childInformation,
},
<ContactFormTab<Categories>>{
id: 'categories',
label: 'TabbedForms-CategoriesTab',
fill: form.fillCategoriesTab,
items: formContent.categories,
},
<ContactFormTab>{
id: 'caseInformation',
label: 'TabbedForms-AddCaseInfoTab',
fill: form.fillStandardTab,
items: formContent.caseInformation,
},
]);
await form.fillWithContent(formContent);

console.info('Saving form');
await form.save();
Expand Down
41 changes: 9 additions & 32 deletions e2e-tests/tests/offlineContact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@
*/

import { expect, Page, request, test } from '@playwright/test';
import { Categories, contactForm, ContactFormTab } from '../contactForm';
import { contactForm, ContactFormTab } from '../contactForm';
import { caseHome } from '../case';
import { agentDesktop, navigateToAgentDesktop } from '../agent-desktop';
import { skipTestIfDataUpdateDisabled, skipTestIfNotTargeted } from '../skipTest';
import { notificationBar } from '../notificationBar';
import { closePage, setupContextAndPage } from '../browser';
import { apiHrmRequest } from '../hrm/hrmRequest';
import { clearOfflineTask } from '../hrm/clearOfflineTask';
import { getConfigValue } from '../config';
import {
formContentsByHelpline,
formContentsByHelplineForEmptyForm,
} from '../formContentsByHelpline';

test.describe.serial('Offline Contact (with Case)', () => {
skipTestIfNotTargeted();
Expand Down Expand Up @@ -56,6 +61,8 @@ test.describe.serial('Offline Contact (with Case)', () => {
await agentDesktopPage.addOfflineContact();

console.log('Starting filling form');
const helpline = getConfigValue('helplineShortCode') as keyof typeof formContentsByHelpline;
const formContent = formContentsByHelplineForEmptyForm[helpline];

Comment on lines +64 to 66
const form = contactForm(pluginPage);
await form.selectChildCallType();
Expand All @@ -70,38 +77,8 @@ test.describe.serial('Offline Contact (with Case)', () => {
helpline: 'Childline',
},
},
<ContactFormTab>{
id: 'childInformation',
label: 'TabbedForms-AddChildInfoTab',
fill: form.fillStandardTab,
items: {
firstName: 'E2E',
lastName: 'OFFLINE CONTACT',
gender: 'Unknown',
age: 'Unknown',
phone1: '1234512345',
province: 'Northern',
district: 'District A',
},
},
<ContactFormTab<Categories>>{
id: 'categories',
label: 'TabbedForms-CategoriesTab',
fill: form.fillCategoriesTab,
items: {
Accessibility: ['Education'],
},
},
<ContactFormTab>{
id: 'caseInformation',
label: 'TabbedForms-AddCaseInfoTab',
fill: form.fillStandardTab,
items: {
callSummary: 'E2E OFFLINE CONTACT',
},
},
]);

await form.fillWithContent(formContent);
const beforeDate = new Date(); // Capture date here since we'll create case inmediately after saving contact

// if (getConfigValue('skipDataUpdate') as boolean) {
Expand Down
Loading
Loading