Skip to content

Commit 5235568

Browse files
bhansell1harrim91
andauthored
CCM-12666: playwright accessibility tests (#755)
Co-authored-by: Michael Harrison <[email protected]>
1 parent 94f9c46 commit 5235568

File tree

10 files changed

+303
-0
lines changed

10 files changed

+303
-0
lines changed

.github/actions/test-types.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"accessibility",
33
"api",
44
"event",
5+
"ui-accessibility",
56
"ui-component",
67
"ui-routing-component",
78
"ui-e2e",

frontend/src/components/forms/ChooseMessageOrder/ChooseMessageOrder.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint-disable jsx-a11y/alt-text */
2+
/* eslint-disable @next/next/no-img-element */
13
'use client';
24

35
import { useActionState, useState } from 'react';

package-lock.json

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/tests/test.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ test-coverage: # Evaluate code coverage from scripts/test/coverage @Testing
2323
test-accessibility: # Run tests from scripts/tests/accessibility.sh @Testing
2424
make _test name="accessibility"
2525

26+
test-ui-accessibility: # Run tests from scripts/tests/ui-accessibility.sh @Testing
27+
make _test name="ui-accessibility"
28+
2629
test-ui-routing-component: # Run tests from scripts/tests/ui-routing-component.sh @Testing
2730
make _test name="ui-routing-component"
2831

@@ -50,6 +53,7 @@ test: # Run all the test tasks @Testing
5053
test-lint \
5154
test-typecheck \
5255
test-coverage \
56+
test-ui-accessibility \
5357
test-ui-component \
5458
test-ui-e2e \
5559
test-api \
@@ -73,6 +77,7 @@ ${VERBOSE}.SILENT: \
7377
test-coverage \
7478
test-lint \
7579
test-typecheck \
80+
test-ui-accessibility \
7681
test-ui-component \
7782
test-api \
7883
test-ui-e2e \

scripts/tests/ui-accessibility.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
cd "$(git rev-parse --show-toplevel)"
5+
npx playwright install --with-deps > /dev/null
6+
cd tests/test-team
7+
TEST_EXIT_CODE=0
8+
npm run test:accessibility || TEST_EXIT_CODE=$?
9+
echo "TEST_EXIT_CODE=$TEST_EXIT_CODE"
10+
11+
mkdir -p ../acceptance-test-report
12+
cp -r ./playwright-report ../acceptance-test-report
13+
[[ -e test-results ]] && cp -r ./test-results ../acceptance-test-report
14+
15+
exit $TEST_EXIT_CODE
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import path from 'node:path';
2+
import { defineConfig, devices } from '@playwright/test';
3+
import baseConfig from '../playwright.config';
4+
5+
const buildCommand = [
6+
'INCLUDE_AUTH_PAGES=true',
7+
'npm run build && npm run start',
8+
].join(' ');
9+
10+
export default defineConfig({
11+
...baseConfig,
12+
fullyParallel: true,
13+
timeout: 60_000, // 30 seconds in the playwright default
14+
expect: {
15+
timeout: 10_000, // default is 5 seconds. After creating and previewing sometimes the load is slow on a cold start
16+
},
17+
projects: [
18+
{
19+
name: 'accessibility:setup',
20+
testMatch: 'ui.setup.ts',
21+
use: {
22+
baseURL: 'http://localhost:3000',
23+
...devices['Desktop Chrome'],
24+
headless: true,
25+
screenshot: 'only-on-failure',
26+
},
27+
},
28+
{
29+
name: 'accessibility',
30+
testMatch: '*.accessibility.spec.ts',
31+
use: {
32+
screenshot: 'only-on-failure',
33+
baseURL: 'http://localhost:3000',
34+
...devices['Desktop Chrome'],
35+
headless: true,
36+
storageState: path.resolve(__dirname, '../.auth/user.json'),
37+
},
38+
dependencies: ['accessibility:setup'],
39+
teardown: 'accessibility:teardown',
40+
},
41+
{
42+
name: 'accessibility:teardown',
43+
testMatch: 'ui.teardown.ts',
44+
},
45+
],
46+
/* Run your local dev server before starting the tests */
47+
webServer: {
48+
timeout: 4 * 60 * 1000, // 4 minutes
49+
command: buildCommand,
50+
cwd: path.resolve(__dirname, '../../../..'),
51+
url: 'http://localhost:3000/templates/create-and-submit-templates',
52+
reuseExistingServer: !process.env.CI,
53+
stderr: 'pipe',
54+
stdout: 'pipe',
55+
},
56+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { test as base, Page } from '@playwright/test';
2+
import AxeBuilder from '@axe-core/playwright';
3+
import { TemplateMgmtBasePage } from 'pages/template-mgmt-base-page';
4+
import { expect } from '@playwright/test';
5+
6+
type Analyze = <T extends TemplateMgmtBasePage>(
7+
page: T,
8+
opts?: {
9+
id?: string;
10+
beforeAnalyze?: (page: T) => Promise<void>;
11+
}
12+
) => Promise<void>;
13+
14+
type AccessibilityFixture = {
15+
analyze: Analyze;
16+
};
17+
18+
const DISABLED_RULES = [
19+
/* We don't have control over NHS colours.
20+
* Axe decides the page is 5.75 ratio and wcag2aaa expects 7:1
21+
*/
22+
'color-contrast-enhanced',
23+
];
24+
25+
const makeAxeBuilder = (page: Page) =>
26+
new AxeBuilder({ page })
27+
.withTags(['wcag2a', 'wcag2aa', 'wcag2aaa'])
28+
.disableRules(DISABLED_RULES);
29+
30+
export const test = base.extend<AccessibilityFixture>({
31+
analyze: async ({ baseURL, page }, use) => {
32+
const analyze: Analyze = async (pageUnderTest, opts) => {
33+
const { id, beforeAnalyze } = opts ?? {};
34+
35+
await pageUnderTest.loadPage(id);
36+
37+
if (beforeAnalyze) {
38+
await beforeAnalyze(pageUnderTest);
39+
}
40+
41+
const pageUrlSegment = (
42+
pageUnderTest.constructor as typeof TemplateMgmtBasePage
43+
).pageUrlSegment;
44+
45+
await expect(page).toHaveURL(
46+
new RegExp(`${baseURL}/templates/${pageUrlSegment}(.*)`) // eslint-disable-line security/detect-non-literal-regexp
47+
);
48+
49+
const results = await makeAxeBuilder(page).analyze();
50+
51+
expect(results.violations).toEqual([]);
52+
};
53+
54+
await use(analyze);
55+
},
56+
});

tests/test-team/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"@faker-js/faker": "^9.9.0",
1212
"@nhsdigital/nhs-notify-event-schemas-template-management": "*",
1313
"@playwright/test": "^1.51.1",
14+
"@axe-core/playwright": "^4.11.0",
15+
"axe-core": "^4.11.0",
1416
"async-mutex": "^0.5.0",
1517
"aws-amplify": "^6.13.6",
1618
"date-fns": "^4.1.0",
@@ -30,6 +32,7 @@
3032
"scripts": {
3133
"lint": "eslint .",
3234
"lint:fix": "npm run lint -- --fix",
35+
"test:accessibility": "playwright test --project accessibility -c config/accessibility/accessibility.config.ts",
3336
"test:api": "playwright test --project api -c config/api/api.config.ts",
3437
"test:e2e": "playwright test --project e2e -c config/e2e/e2e.config.ts",
3538
"test:event": "playwright test -c config/event/event.config.ts",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from './campaign-id-required-page';
2+
export * from './choose-message-order-page';
3+
export * from './choose-templates-page';
4+
export * from './create-message-plan-page';
5+
export * from './message-plans-page';
6+
export * from './invalid-message-plan-page';
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import {
2+
createAuthHelper,
3+
TestUser,
4+
testUsers,
5+
} from 'helpers/auth/cognito-auth-helper';
6+
import { RoutingConfigFactory } from 'helpers/factories/routing-config-factory';
7+
import { RoutingConfigStorageHelper } from 'helpers/db/routing-config-storage-helper';
8+
import { test } from 'fixtures/accessibility-analyze';
9+
import {
10+
RoutingChooseMessageOrderPage,
11+
RoutingCreateMessagePlanPage,
12+
RoutingChooseTemplatesPage,
13+
RoutingInvalidMessagePlanPage,
14+
RoutingMessagePlanCampaignIdRequiredPage,
15+
RoutingMessagePlansPage,
16+
} from 'pages/routing';
17+
import { loginAsUser } from 'helpers/auth/login-as-user';
18+
import { randomUUID } from 'node:crypto';
19+
import { TemplateFactory } from 'helpers/factories/template-factory';
20+
import { TemplateStorageHelper } from 'helpers/db/template-storage-helper';
21+
22+
let userWithMultipleCampaigns: TestUser;
23+
const routingStorageHelper = new RoutingConfigStorageHelper();
24+
const templateStorageHelper = new TemplateStorageHelper();
25+
const validRoutingConfigId = randomUUID();
26+
const messageOrder = 'NHSAPP,EMAIL,SMS,LETTER';
27+
28+
test.describe('Routing - Accessibility', () => {
29+
test.beforeAll(async () => {
30+
const authHelper = createAuthHelper();
31+
32+
const user = await authHelper.getTestUser(testUsers.User1.userId);
33+
34+
userWithMultipleCampaigns = await authHelper.getTestUser(
35+
testUsers.UserWithMultipleCampaigns.userId
36+
);
37+
38+
const templateIds = {
39+
NHSAPP: randomUUID(),
40+
SMS: randomUUID(),
41+
LETTER: randomUUID(),
42+
};
43+
44+
const routingConfig = RoutingConfigFactory.createForMessageOrder(
45+
user,
46+
messageOrder,
47+
{
48+
id: validRoutingConfigId,
49+
name: 'Test plan with some templates',
50+
}
51+
)
52+
.addTemplate('NHSAPP', templateIds.NHSAPP)
53+
.addTemplate('SMS', templateIds.SMS)
54+
.addTemplate('LETTER', templateIds.LETTER).dbEntry;
55+
56+
const templates = [
57+
TemplateFactory.createNhsAppTemplate(
58+
templateIds.NHSAPP,
59+
user,
60+
'Test NHS App template'
61+
),
62+
TemplateFactory.createSmsTemplate(
63+
templateIds.SMS,
64+
user,
65+
'Test SMS template'
66+
),
67+
TemplateFactory.uploadLetterTemplate(
68+
templateIds.LETTER,
69+
user,
70+
'Test Letter template'
71+
),
72+
];
73+
74+
await routingStorageHelper.seed([routingConfig]);
75+
await templateStorageHelper.seedTemplateData(templates);
76+
});
77+
78+
test.afterAll(async () => {
79+
await routingStorageHelper.deleteSeeded();
80+
await templateStorageHelper.deleteSeededTemplates();
81+
});
82+
83+
test('Message plans', async ({ page, analyze }) =>
84+
analyze(new RoutingMessagePlansPage(page)));
85+
86+
test('Campaign required', async ({ page, analyze }) =>
87+
analyze(new RoutingMessagePlanCampaignIdRequiredPage(page)));
88+
89+
test('Invalid message plans', async ({ page, analyze }) =>
90+
analyze(new RoutingInvalidMessagePlanPage(page)));
91+
92+
test('Choose message order', async ({ page, analyze }) =>
93+
analyze(new RoutingChooseMessageOrderPage(page)));
94+
95+
test('Choose template', async ({ page, analyze }) =>
96+
analyze(new RoutingChooseTemplatesPage(page), {
97+
id: validRoutingConfigId,
98+
}));
99+
100+
test('Choose message order - error', async ({ page, analyze }) =>
101+
analyze(new RoutingChooseMessageOrderPage(page), {
102+
beforeAnalyze: (p) => p.clickContinueButton(),
103+
}));
104+
105+
test.describe('client has multiple campaigns', () => {
106+
test.use({ storageState: { cookies: [], origins: [] } });
107+
108+
test.beforeEach(async ({ page }) => {
109+
await loginAsUser(userWithMultipleCampaigns, page);
110+
});
111+
112+
test('Create message plan', async ({ page, analyze }) =>
113+
analyze(
114+
new RoutingCreateMessagePlanPage(page, {
115+
messageOrder,
116+
})
117+
));
118+
119+
test('Create message plan - error', async ({ page, analyze }) =>
120+
analyze(
121+
new RoutingCreateMessagePlanPage(page, {
122+
messageOrder,
123+
}),
124+
{ beforeAnalyze: (p) => p.clickSubmit() }
125+
));
126+
});
127+
});

0 commit comments

Comments
 (0)