Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/actions/test-types.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"accessibility",
"api",
"event",
"ui-accessibility",
"ui-component",
"ui-routing-component",
"ui-e2e",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable jsx-a11y/alt-text */
/* eslint-disable @next/next/no-img-element */
'use client';

import { useActionState, useState } from 'react';
Expand Down
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions scripts/tests/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ test-coverage: # Evaluate code coverage from scripts/test/coverage @Testing
test-accessibility: # Run tests from scripts/tests/accessibility.sh @Testing
make _test name="accessibility"

test-ui-accessibility: # Run tests from scripts/tests/ui-accessibility.sh @Testing
make _test name="ui-accessibility"

test-ui-routing-component: # Run tests from scripts/tests/ui-routing-component.sh @Testing
make _test name="ui-routing-component"

Expand Down Expand Up @@ -50,6 +53,7 @@ test: # Run all the test tasks @Testing
test-lint \
test-typecheck \
test-coverage \
test-ui-accessibility \
test-ui-component \
test-ui-e2e \
test-api \
Expand All @@ -73,6 +77,7 @@ ${VERBOSE}.SILENT: \
test-coverage \
test-lint \
test-typecheck \
test-ui-accessibility \
test-ui-component \
test-api \
test-ui-e2e \
Expand Down
15 changes: 15 additions & 0 deletions scripts/tests/ui-accessibility.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
npx playwright install --with-deps > /dev/null
cd tests/test-team
TEST_EXIT_CODE=0
npm run test:accessibility || TEST_EXIT_CODE=$?
echo "TEST_EXIT_CODE=$TEST_EXIT_CODE"

mkdir -p ../acceptance-test-report
cp -r ./playwright-report ../acceptance-test-report
[[ -e test-results ]] && cp -r ./test-results ../acceptance-test-report

exit $TEST_EXIT_CODE
56 changes: 56 additions & 0 deletions tests/test-team/config/accessibility/accessibility.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import path from 'node:path';
import { defineConfig, devices } from '@playwright/test';
import baseConfig from '../playwright.config';

const buildCommand = [
'INCLUDE_AUTH_PAGES=true',
'npm run build && npm run start',
].join(' ');

export default defineConfig({
...baseConfig,
fullyParallel: true,
timeout: 60_000, // 30 seconds in the playwright default
expect: {
timeout: 10_000, // default is 5 seconds. After creating and previewing sometimes the load is slow on a cold start
},
projects: [
{
name: 'accessibility:setup',
testMatch: 'ui.setup.ts',
use: {
baseURL: 'http://localhost:3000',
...devices['Desktop Chrome'],
headless: true,
screenshot: 'only-on-failure',
},
},
{
name: 'accessibility',
testMatch: '*.accessibility.spec.ts',
use: {
screenshot: 'only-on-failure',
baseURL: 'http://localhost:3000',
...devices['Desktop Chrome'],
headless: true,
storageState: path.resolve(__dirname, '../.auth/user.json'),
},
dependencies: ['accessibility:setup'],
teardown: 'accessibility:teardown',
},
{
name: 'accessibility:teardown',
testMatch: 'ui.teardown.ts',
},
],
/* Run your local dev server before starting the tests */
webServer: {
timeout: 4 * 60 * 1000, // 4 minutes
command: buildCommand,
cwd: path.resolve(__dirname, '../../../..'),
url: 'http://localhost:3000/templates/create-and-submit-templates',
reuseExistingServer: !process.env.CI,
stderr: 'pipe',
stdout: 'pipe',
},
});
56 changes: 56 additions & 0 deletions tests/test-team/fixtures/accessibility-analyze.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { test as base, Page } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
import { TemplateMgmtBasePage } from 'pages/template-mgmt-base-page';
import { expect } from '@playwright/test';

type Analyze = <T extends TemplateMgmtBasePage>(
page: T,
opts?: {
id?: string;
beforeAnalyze?: (page: T) => Promise<void>;
}
) => Promise<void>;

type AccessibilityFixture = {
analyze: Analyze;
};

const DISABLED_RULES = [
/* We don't have control over NHS colours.
* Axe decides the page is 5.75 ratio and wcag2aaa expects 7:1
*/
'color-contrast-enhanced',
];

const makeAxeBuilder = (page: Page) =>
new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag2aaa'])
.disableRules(DISABLED_RULES);

export const test = base.extend<AccessibilityFixture>({
analyze: async ({ baseURL, page }, use) => {
const analyze: Analyze = async (pageUnderTest, opts) => {
const { id, beforeAnalyze } = opts ?? {};

await pageUnderTest.loadPage(id);

if (beforeAnalyze) {
await beforeAnalyze(pageUnderTest);
}

const pageUrlSegment = (
pageUnderTest.constructor as typeof TemplateMgmtBasePage
).pageUrlSegment;

await expect(page).toHaveURL(
new RegExp(`${baseURL}/templates/${pageUrlSegment}(.*)`) // eslint-disable-line security/detect-non-literal-regexp
);

const results = await makeAxeBuilder(page).analyze();

expect(results.violations).toEqual([]);
};

await use(analyze);
},
});
3 changes: 3 additions & 0 deletions tests/test-team/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"@faker-js/faker": "^9.9.0",
"@nhsdigital/nhs-notify-event-schemas-template-management": "*",
"@playwright/test": "^1.51.1",
"@axe-core/playwright": "^4.11.0",
"axe-core": "^4.11.0",
"async-mutex": "^0.5.0",
"aws-amplify": "^6.13.6",
"date-fns": "^4.1.0",
Expand All @@ -30,6 +32,7 @@
"scripts": {
"lint": "eslint .",
"lint:fix": "npm run lint -- --fix",
"test:accessibility": "playwright test --project accessibility -c config/accessibility/accessibility.config.ts",
"test:api": "playwright test --project api -c config/api/api.config.ts",
"test:e2e": "playwright test --project e2e -c config/e2e/e2e.config.ts",
"test:event": "playwright test -c config/event/event.config.ts",
Expand Down
6 changes: 6 additions & 0 deletions tests/test-team/pages/routing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './campaign-id-required-page';
export * from './choose-message-order-page';
export * from './choose-templates-page';
export * from './create-message-plan-page';
export * from './message-plans-page';
export * from './invalid-message-plan-page';
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {
createAuthHelper,
TestUser,
testUsers,
} from 'helpers/auth/cognito-auth-helper';
import { RoutingConfigFactory } from 'helpers/factories/routing-config-factory';
import { RoutingConfigStorageHelper } from 'helpers/db/routing-config-storage-helper';
import { test } from 'fixtures/accessibility-analyze';
import {
RoutingChooseMessageOrderPage,
RoutingCreateMessagePlanPage,
RoutingChooseTemplatesPage,
RoutingInvalidMessagePlanPage,
RoutingMessagePlanCampaignIdRequiredPage,
RoutingMessagePlansPage,
} from 'pages/routing';
import { loginAsUser } from 'helpers/auth/login-as-user';
import { randomUUID } from 'node:crypto';
import { TemplateFactory } from 'helpers/factories/template-factory';
import { TemplateStorageHelper } from 'helpers/db/template-storage-helper';

let userWithMultipleCampaigns: TestUser;
const routingStorageHelper = new RoutingConfigStorageHelper();
const templateStorageHelper = new TemplateStorageHelper();
const validRoutingConfigId = randomUUID();
const messageOrder = 'NHSAPP,EMAIL,SMS,LETTER';

test.describe('Routing - Accessibility', () => {
test.beforeAll(async () => {
const authHelper = createAuthHelper();

const user = await authHelper.getTestUser(testUsers.User1.userId);

userWithMultipleCampaigns = await authHelper.getTestUser(
testUsers.UserWithMultipleCampaigns.userId
);

const templateIds = {
NHSAPP: randomUUID(),
SMS: randomUUID(),
LETTER: randomUUID(),
};

const routingConfig = RoutingConfigFactory.createForMessageOrder(
user,
messageOrder,
{
id: validRoutingConfigId,
name: 'Test plan with some templates',
}
)
.addTemplate('NHSAPP', templateIds.NHSAPP)
.addTemplate('SMS', templateIds.SMS)
.addTemplate('LETTER', templateIds.LETTER).dbEntry;

const templates = [
TemplateFactory.createNhsAppTemplate(
templateIds.NHSAPP,
user,
'Test NHS App template'
),
TemplateFactory.createSmsTemplate(
templateIds.SMS,
user,
'Test SMS template'
),
TemplateFactory.uploadLetterTemplate(
templateIds.LETTER,
user,
'Test Letter template'
),
];

await routingStorageHelper.seed([routingConfig]);
await templateStorageHelper.seedTemplateData(templates);
});

test.afterAll(async () => {
await routingStorageHelper.deleteSeeded();
await templateStorageHelper.deleteSeededTemplates();
});

test('Message plans', async ({ page, analyze }) =>
analyze(new RoutingMessagePlansPage(page)));

test('Campaign required', async ({ page, analyze }) =>
analyze(new RoutingMessagePlanCampaignIdRequiredPage(page)));

test('Invalid message plans', async ({ page, analyze }) =>
analyze(new RoutingInvalidMessagePlanPage(page)));

test('Choose message order', async ({ page, analyze }) =>
analyze(new RoutingChooseMessageOrderPage(page)));

test('Choose template', async ({ page, analyze }) =>
analyze(new RoutingChooseTemplatesPage(page), {
id: validRoutingConfigId,
}));

test('Choose message order - error', async ({ page, analyze }) =>
analyze(new RoutingChooseMessageOrderPage(page), {
beforeAnalyze: (p) => p.clickContinueButton(),
}));

test.describe('client has multiple campaigns', () => {
test.use({ storageState: { cookies: [], origins: [] } });

test.beforeEach(async ({ page }) => {
await loginAsUser(userWithMultipleCampaigns, page);
});

test('Create message plan', async ({ page, analyze }) =>
analyze(
new RoutingCreateMessagePlanPage(page, {
messageOrder,
})
));

test('Create message plan - error', async ({ page, analyze }) =>
analyze(
new RoutingCreateMessagePlanPage(page, {
messageOrder,
}),
{ beforeAnalyze: (p) => p.clickSubmit() }
));
});
});
Loading