Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
201a39b
CCM-14061: Finish routing e2e tests
chris-elliott-nhsd Feb 17, 2026
c1843fd
CCM-14061: Fix selector
chris-elliott-nhsd Feb 17, 2026
db595fd
CCM-14061: Allow more time for routing config test
chris-elliott-nhsd Feb 17, 2026
74a8a68
CCM-14061: Debug
chris-elliott-nhsd Feb 17, 2026
0463778
CCM-14061: Wait for js-enabled text to load
chris-elliott-nhsd Feb 17, 2026
ba95b20
CCM-14061: debug
chris-elliott-nhsd Feb 17, 2026
5951822
CCM-14061: Increase viewport size
chris-elliott-nhsd Feb 17, 2026
2fad8af
CCM-14061: Increase viewport size
chris-elliott-nhsd Feb 17, 2026
2135dea
CCM-14061: Increase timeouts
chris-elliott-nhsd Feb 17, 2026
aee3eff
CCM-14061: Use pressSequentially instead of fill
chris-elliott-nhsd Feb 18, 2026
8d21122
CCM-14061: Increase timeout on CIS2 security test
chris-elliott-nhsd Feb 18, 2026
a442aae
CCM-14061: Use pressSequentially instead of fill
chris-elliott-nhsd Feb 18, 2026
c856ca3
CCM-14061: Add extra wait immediately before submit
chris-elliott-nhsd Feb 18, 2026
5e425d6
CCM-14061: Add waitForLoadState
chris-elliott-nhsd Feb 18, 2026
f2a3504
CCM-14061: Add waitForLoadState
chris-elliott-nhsd Feb 18, 2026
4c38ec8
CCM-14061: Debug
chris-elliott-nhsd Feb 18, 2026
7874de3
CCM-14061: Debug
chris-elliott-nhsd Feb 18, 2026
4e67a32
CCM-14061: Fix selector
chris-elliott-nhsd Feb 18, 2026
2a17868
CCM-14061: Increase timeout
chris-elliott-nhsd Feb 18, 2026
462ba89
CCM-14061: Increase timeout on copy test
chris-elliott-nhsd Feb 18, 2026
b8c2866
CCM-14061: debug
chris-elliott-nhsd Feb 18, 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
4 changes: 4 additions & 0 deletions tests/security/config/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export default defineConfig({
slowMo: 0,
},
video: 'on',
viewport: {
height: 1200,
width: 1600,
},
headless: true,
},
},
Expand Down
23 changes: 15 additions & 8 deletions tests/security/functions/common-steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,14 @@ export async function createEmailTemplate(
'/templates/create-email-template'
);

await page.waitForLoadState('load');
await expect(page.getByTestId('navigation-links')).toBeVisible();

await page.getByLabel('Template name').fill(name);
await page.getByLabel('Template name').pressSequentially(name);

await page.getByLabel('Subject line').fill('E2E subject');
await page.getByLabel('Subject line').pressSequentially('E2E subject');

await page.getByLabel('Message').fill('E2E Message');
await page.getByLabel('Message').pressSequentially('E2E Message');

await page.getByText('Save and preview').click({
position: {
Expand All @@ -103,12 +104,15 @@ export async function createSmsTemplate(
'/templates/create-text-message-template'
);

await page.waitForLoadState('load');
await expect(page.getByTestId('navigation-links')).toBeVisible();
await expect(page.getByTestId('character-message-count')).toBeVisible();

await page.getByLabel('Template name').fill(name);
await page.getByLabel('Template name').pressSequentially(name);

await page.getByLabel('Message').fill('E2E Message');
await page.getByLabel('Message').pressSequentially('E2E Message');

await expect(page.getByTestId('character-message-count')).toBeVisible();
await page.getByText('Save and preview').click({
position: {
x: 50,
Expand All @@ -125,12 +129,15 @@ export async function createNhsAppTemplate(
'/templates/create-nhs-app-template'
);

await page.waitForLoadState('load');
await expect(page.getByTestId('navigation-links')).toBeVisible();
await expect(page.getByTestId('character-count')).toBeVisible();

await page.getByLabel('Template name').fill(name);
await page.getByLabel('Template name').pressSequentially(name);

await page.getByLabel('Message').fill('E2E Message');
await page.getByLabel('Message').pressSequentially('E2E Message');

await expect(page.getByTestId('character-count')).toBeVisible();
await page.getByText('Save and preview').click({
position: {
x: 50,
Expand Down Expand Up @@ -274,7 +281,7 @@ export function copyTemplate(
await basePage.clickButtonByName('Save and preview');
await expect(basePage.pageHeader).toHaveText(editedTemplateName);
await basePage.clickBackLink();
await basePage.page.waitForSelector('text=Message templates', { timeout: 10_000 });
await basePage.page.waitForSelector('text=Message templates');
await basePage.waitForLoad();
await expect(basePage.templateEdited(editedTemplateName)).toBeVisible();

Expand Down
90 changes: 43 additions & 47 deletions tests/security/functions/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,63 @@ import {
getCis2CredentialProvider,
} from "nhs-notify-system-tests-shared";

async function enterCis2TotpCode(
async function enterVerificationCode(
page: Page,
cis2CredentialProvider: Cis2CredentialProvider,
targetHeadingText: string
) {
await page.getByLabel('Enter verification code').fill(cis2CredentialProvider.totp());
await page.getByText(/\W+Submit\W+/).click();

const happyPathSelector = page.getByText(targetHeadingText);
const reVerificationSelector = page.locator(`//button[text()=' Re-enter verification code ']`);
await page.getByText('Submit').click();

await happyPathSelector.or(reVerificationSelector).waitFor({ timeout: 60_000 });
if (await happyPathSelector.isVisible()) {
return true;
await expect(
page.getByText('Message templates')
.or(
page.getByText('Re-enter verification code')
)
.or(
page.getByText('Sign in using an NHS account')
)).toBeVisible({ timeout: 90_000 });

if (await page.getByText('Message templates').isVisible()) {
return;
}
return false;
}

async function enterCis2TotpCodeWithRetry(
page: Page,
cis2CredentialProvider: Cis2CredentialProvider,
targetHeadingText: string
) {
for (var i=0; i<3; i++) {
const success = await enterCis2TotpCode(page, cis2CredentialProvider, targetHeadingText);
if (success) {
return;
}
await page.getByText(/\W+Re-enter verification code\W+/).click();
if (await page.getByText('Re-enter verification code').isVisible()) {
await page.getByText('Re-enter verification code').click();
enterVerificationCode(page, cis2CredentialProvider);

return;
}

if (await page.getByText('Sign in using an NHS account').isVisible()) {
await cis2Login(page);

return;
}

throw new Error('Unexpected outcome');
}

async function loginWithCis2(
export async function cis2Login(
page: Page,
targetHeadingText: string
) {
const cis2CredentialProvider = await getCis2CredentialProvider();

try {
// Notify WebUI - Click the CIS2 login button
const cis2Button = page.getByText("Log in with my Care Identity")
await page.waitForLoadState('networkidle')
await cis2Button.click();

// CIS2 - Select credentials type
await page.getByLabel("Authenticator app").click();
await page.getByText(/\W+Continue\W+/).click();
await page.waitForSelector(`//input[@name='password']`);

// CIS2 - Username/password form
await page.fill('input[name="email"]', cis2CredentialProvider.username);
await page.fill('input[name="password"]', cis2CredentialProvider.password);
await page.getByText(/\W+Continue\W+/).click();
await expect(page.getByText('Enter verification code')).toBeVisible();

// CIS2 - TOTP form
await enterCis2TotpCodeWithRetry(page, cis2CredentialProvider, targetHeadingText);
} catch (error) {
console.error("Error during login:", error);
throw error;
}
await page.waitForLoadState('load');

await page.getByText("Log in with my Care Identity").click();

await page.getByLabel("Authenticator app").click();

await page.getByText('Continue').click();

await page.getByLabel('What is your email address?').fill(cis2CredentialProvider.username);

await page.getByLabel('What is your password?').fill(cis2CredentialProvider.password);

await page.getByText('Continue').click();

await enterVerificationCode(page, cis2CredentialProvider);
}

async function logOut(page: TemplateMgmtBasePage) {
Expand All @@ -77,4 +73,4 @@ async function logOut(page: TemplateMgmtBasePage) {
await expect(page.pageHeader).toHaveText('Sign in');
}

export { loginWithCis2, expect, logOut };
export { expect, logOut };
2 changes: 1 addition & 1 deletion tests/security/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"test:security:playwright": "playwright test --project security -c config/playwright.config.ts",
"test:security": "./run.sh",
"test:security": "./run.sh main 3",
"test:security:local": "$(git rev-parse --show-toplevel)/scripts/tests/local-lifecycle.sh $(pwd) test:security auth",
"typecheck": "tsc --noEmit"
},
Expand Down
9 changes: 8 additions & 1 deletion tests/security/tests/routing-config.security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const previewAndSelectTemplate = async (
}

test(`User creates a multi-channel routing config`, async ({ page }, { config: { configFile } }) => {
test.setTimeout(180_000);

const templates = await getSeededTemplateConfig(configFile);
await page.goto('/templates/message-plans');
Expand Down Expand Up @@ -111,5 +112,11 @@ test(`User creates a multi-channel routing config`, async ({ page }, { config: {

await expect(page).toHaveURL(new RegExp(`/templates/message-plans/review-and-move-to-production/${routingConfigId}(.*)`));

// remaining pages not ready yet
await page.getByText('Move to production').click();

await expect(page).toHaveURL('/templates/message-plans');

await page.getByText(/Production \(\d+\)/).click();

await expect(page.getByText(routingConfigId)).toBeVisible();
});
10 changes: 5 additions & 5 deletions tests/security/tests/template-mgmt-cis2-login.security.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable security/detect-non-literal-regexp */

import { loginWithCis2, logOut } from '../functions/login';
import { cis2Login, logOut } from '../functions/login';
import { TemplateMgmtBasePage } from '../pages/template-mgmt-base-page';
import {
chooseTemplate,
Expand All @@ -13,7 +13,7 @@ import test from 'playwright/test';
import { TemplateMgmtLetterPage } from '../pages/template-mgmt-letter-page';

test.use({ storageState: { cookies: [], origins: [] } });
test.setTimeout(180_000);
test.setTimeout(300_000);

/**
* Intention is to cover:
Expand All @@ -40,14 +40,14 @@ test('User logs in via CIS2, saves data in templates, logs out and logs back in
const name = 'CIS2 login test';

await startPage({ basePage });
await loginWithCis2(basePage.page, 'Message templates');
await cis2Login(basePage.page);
await startNewTemplate(props);
await chooseTemplate(props, channel);
await createEmailTemplate(page, name);
await previewPage(props, channelPath, name);
await context.storageState({ path: 'login-state/cis2.json' });
await logOut(basePage);
await page.waitForLoadState('networkidle');
await page.waitForLoadState('load');
await startPage({ basePage });
await loginWithCis2(basePage.page, 'Message templates');
await cis2Login(basePage.page);
});
4 changes: 4 additions & 0 deletions tests/test-team/config/dev.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export default defineConfig({
slowMo: 0,
},
video: 'on',
viewport: {
height: 1200,
width: 1600,
},
headless: true
},
},
Expand Down
102 changes: 45 additions & 57 deletions tests/test-team/functions/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,63 @@ import {
getCis2CredentialProvider,
} from 'nhs-notify-system-tests-shared';

async function enterCis2TotpCode(
async function enterVerificationCode(
page: Page,
cis2CredentialProvider: Cis2CredentialProvider,
targetHeadingText: string
) {
await page.getByLabel('Enter verification code').fill(cis2CredentialProvider.totp());
await page.getByText(/\W+Submit\W+/).click();

const happyPathSelector = page.getByText(targetHeadingText);
const reVerificationSelector = page.locator(
`//button[text()=' Re-enter verification code ']`
);

await happyPathSelector
.or(reVerificationSelector)
.waitFor({ timeout: 30_000 });
if (await happyPathSelector.isVisible()) {
return true;

await page.getByText('Submit').click();

await expect(
page.getByText('Message templates')
.or(
page.getByText('Re-enter verification code')
)
.or(
page.getByText('Sign in using an NHS account')
)).toBeVisible({ timeout: 90_000 });

if (await page.getByText('Message templates').isVisible()) {
return;
}
return false;
}

async function enterCis2TotpCodeWithRetry(
page: Page,
cis2CredentialProvider: Cis2CredentialProvider,
targetHeadingText: string
) {
for (var i = 0; i < 3; i++) {
const success = await enterCis2TotpCode(
page,
cis2CredentialProvider,
targetHeadingText
);
if (success) {
return;
}
await page.getByText(/\W+Re-enter verification code\W+/).click();
if (await page.getByText('Re-enter verification code').isVisible()) {
await page.getByText('Re-enter verification code').click();
enterVerificationCode(page, cis2CredentialProvider);

return;
}

if (await page.getByText('Sign in using an NHS account').isVisible()) {
await cis2Login(page);

return;
}

throw new Error('Unexpected outcome');
}

async function loginWithCis2(
export async function cis2Login(
page: Page,
targetHeadingText: string
) {
const cis2CredentialProvider = await getCis2CredentialProvider();

try {
// Notify WebUI - Click the CIS2 login button
const cis2Button = page.getByText('Log in with my Care Identity');
await page.waitForLoadState('networkidle');
await cis2Button.click();

// CIS2 - Select credentials type
await page.getByLabel('Authenticator app').click();
await page.getByText(/\W+Continue\W+/).click();
await page.waitForSelector(`//input[@name='password']`);

// CIS2 - Username/password form
await page.fill('input[name="email"]', cis2CredentialProvider.username);
await page.fill('input[name="password"]', cis2CredentialProvider.password);
await page.getByText(/\W+Continue\W+/).click();
await expect(page.getByText('Enter verification code')).toBeVisible();

// CIS2 - TOTP form
await enterCis2TotpCodeWithRetry(page, cis2CredentialProvider, targetHeadingText);
} catch (error) {
console.error('Error during login:', error);
throw error;
}
await page.waitForLoadState('load');

await page.getByText("Log in with my Care Identity").click();

await page.getByLabel("Authenticator app").click();

await page.getByText('Continue').click();

await page.getByLabel('What is your email address?').fill(cis2CredentialProvider.username);

await page.getByLabel('What is your password?').fill(cis2CredentialProvider.password);

await page.getByText('Continue').click();

await enterVerificationCode(page, cis2CredentialProvider);
}

async function logOut(page: TemplateMgmtBasePage) {
Expand All @@ -82,4 +70,4 @@ async function logOut(page: TemplateMgmtBasePage) {
await expect(page.pageHeader).toHaveText('Sign in');
}

export { loginWithCis2, expect, logOut };
export { expect, logOut };
Loading
Loading