Skip to content

Commit

Permalink
CCM-7248: add automated tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bhansell1 committed Nov 15, 2024
1 parent bdb49c8 commit b91fa26
Show file tree
Hide file tree
Showing 22 changed files with 1,906 additions and 365 deletions.
517 changes: 279 additions & 238 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"app:stop": "pm2 kill"
},
"dependencies": {
"next": "14.2.15",
"@aws-amplify/adapter-nextjs": "^1.2.17",
"@aws-amplify/auth": "^6.4.0",
"@aws-amplify/backend": "^1.2.1",
Expand All @@ -26,6 +25,7 @@
"@aws-amplify/ui-react": "^6.3.1",
"aws-amplify": "^6.6.0",
"aws-jwt-verify": "^4.0.1",
"next": "14.2.15",
"nhsuk-frontend": "^8.3.0",
"nhsuk-react-components": "^4.1.3",
"react": "^18",
Expand Down
28 changes: 17 additions & 11 deletions src/__tests__/components/Redirect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@ jest.mock('next/navigation', () => ({
const redirectMock = jest.mocked(redirect);
const useSearchParamsMock = jest.mocked(useSearchParams);

test.each(['/redirect-path', 'redirect-path'])('Redirect - URL provided %p', (redirect) => {
const mockSearchParams = new ReadonlyURLSearchParams({
redirect,
});

useSearchParamsMock.mockReturnValue(mockSearchParams);

render(<Redirect />);

expect(redirectMock).toHaveBeenCalledWith(`/redirect/redirect-path`, 'push');
});
test.each(['/redirect-path', 'redirect-path'])(
'Redirect - URL provided %p',
(redirectPath) => {
const mockSearchParams = new ReadonlyURLSearchParams({
redirect: redirectPath,
});

useSearchParamsMock.mockReturnValue(mockSearchParams);

render(<Redirect />);

expect(redirectMock).toHaveBeenCalledWith(
`/redirect/redirect-path`,
'push'
);
}
);

test('Redirect - URL not provided', () => {
const mockSearchParams = new ReadonlyURLSearchParams({});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ exports[`LoginStatus - authenticated 1`] = `
<a
class="nhsuk-account__login--link"
href="/auth/signout"
id="login-link"
>
Log out
</a>
Expand All @@ -16,6 +17,7 @@ exports[`LoginStatus - configuring 1`] = `
<a
class="nhsuk-account__login--link"
href="/auth"
id="logout-link"
>
Log in
</a>
Expand All @@ -27,6 +29,7 @@ exports[`LoginStatus - unauthenticated 1`] = `
<a
class="nhsuk-account__login--link"
href="/auth"
id="logout-link"
>
Log in
</a>
Expand Down
12 changes: 6 additions & 6 deletions src/components/molecules/ClientLayout/ClientLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { Amplify } from 'aws-amplify';
import { Authenticator } from '@aws-amplify/ui-react';
import content from '@/src/content/content';
import { NHSNotifyContainer } from '@/src/components/layouts/container/container';
import { BASE_PATH } from '@/src/utils/constants';
import { NHSNotifyHeader } from '../Header/Header';
import { NHSNotifyFooter } from '../Footer/Footer';
import { BASE_PATH } from '@/src/utils/constants';

/* eslint-disable @typescript-eslint/no-require-imports, unicorn/prefer-module */
Amplify.configure(require('@/amplify_outputs.json'), { ssr: true });
Expand Down Expand Up @@ -66,11 +66,11 @@ export const ClientLayout = ({ children }: { children: React.ReactNode }) => (
src={`${BASE_PATH}/lib/nhs-frontend-js-check.js`}
defer
/>
<Authenticator.Provider>
<NHSNotifyHeader />
<NHSNotifyContainer>{children}</NHSNotifyContainer>
<NHSNotifyFooter />
</Authenticator.Provider>
<Authenticator.Provider>
<NHSNotifyHeader />
<NHSNotifyContainer>{children}</NHSNotifyContainer>
<NHSNotifyFooter />
</Authenticator.Provider>
</body>
</html>
);
3 changes: 2 additions & 1 deletion src/components/molecules/LoginStatus/LoginStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const LoginStatus = () => {
if (authStatus === 'authenticated') {
return (
<a
id='login-link'
className='nhsuk-account__login--link'
href={`${BASE_PATH}/signout`}
>
Expand All @@ -19,7 +20,7 @@ export const LoginStatus = () => {
}

return (
<a className='nhsuk-account__login--link' href={BASE_PATH}>
<a id='logout-link' className='nhsuk-account__login--link' href={BASE_PATH}>
Log in
</a>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/molecules/Redirect/Redirect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { useSearchParams, redirect, RedirectType } from 'next/navigation';
import path from 'path';
import path from 'node:path';

export const Redirect = () => {
const searchParams = useSearchParams();
Expand Down
74 changes: 74 additions & 0 deletions tests/test-team/component-tests/signin.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { test, expect } from '@playwright/test';
import { CognitoUserHelper, User } from '../helpers/cognito-user-helper';
import { IamWebAuthSignInPage } from '../pages/iam-webauth-signin-page';

test.describe('SignIn', () => {
let user: User;

const cognitoHelper = new CognitoUserHelper();

test.beforeAll(async () => {
user = await cognitoHelper.createUser('playwright-signIn');
});

test.afterAll(async () => {
await cognitoHelper.deleteUser(user.userId);
});

test('should sign user in, then redirect user to redirect path', async ({
page,
baseURL,
}) => {
const signInPage = new IamWebAuthSignInPage(page);

await signInPage.loadPage({
redirectPath: '/templates/create-and-submit-templates',
});

await signInPage.cognitoSignIn(user.email);

await expect(page).toHaveURL(
`${baseURL}/templates/create-and-submit-templates`
);
});

test.describe('Error handling', () => {
test('should not log user in, when email is invalid', async ({ page }) => {
const signInPage = new IamWebAuthSignInPage(page);

await signInPage.loadPage({
redirectPath: '/templates/create-and-submit-templates',
});

await signInPage.emailInput.fill('[email protected]');

await signInPage.passwordInput.fill(process.env.TEMPORARY_USER_PASSWORD);

await signInPage.clickSubmitButton();

await expect(signInPage.errorMessage).toHaveText(
'Incorrect username or password.'
);
});

test('should not log user in, when password is invalid', async ({
page,
}) => {
const signInPage = new IamWebAuthSignInPage(page);

await signInPage.loadPage({
redirectPath: '/templates/create-and-submit-templates',
});

await signInPage.emailInput.fill(user.email);

await signInPage.passwordInput.fill('invalid-password');

await signInPage.clickSubmitButton();

await expect(signInPage.errorMessage).toHaveText(
'Incorrect username or password.'
);
});
});
});
48 changes: 48 additions & 0 deletions tests/test-team/component-tests/signout.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { test, expect } from '@playwright/test';
import { CognitoUserHelper, User } from '../helpers/cognito-user-helper';
import { IamWebAuthSignInPage } from '../pages/iam-webauth-signin-page';

test.describe('SignIn', () => {
let user: User;

const cognitoHelper = new CognitoUserHelper();

test.beforeAll(async () => {
user = await cognitoHelper.createUser('playwright-signout');
});

test.afterAll(async () => {
await cognitoHelper.deleteUser(user.userId);
});

test('should sign user out, then redirect user to redirect path', async ({
page,
baseURL,
}) => {
const signInPage = new IamWebAuthSignInPage(page);

await signInPage.loadPage({
redirectPath: '/templates/create-and-submit-templates',
});

await signInPage.cognitoSignIn(user.email);

await expect(page).toHaveURL(
`${baseURL}/templates/create-and-submit-templates`
);

await page.goto(
`${baseURL}/auth/signout?redirect=${encodeURIComponent('/templates/create-and-submit-templates')}`
);

await expect(page).toHaveURL(
`${baseURL}/templates/create-and-submit-templates`
);

await signInPage.loadPage({
redirectPath: '/templates/create-and-submit-templates',
});

await expect(signInPage.emailInput).toBeVisible();
});
});
25 changes: 25 additions & 0 deletions tests/test-team/config/global.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { FullConfig } from '@playwright/test';
import { AmplifyConfigurationHelper } from '../helpers/amplify-configuration-helper';
import generate from 'generate-password';
import { v4 as uuidv4 } from 'uuid';

async function globalSetup(config: FullConfig) {
const configHelper = new AmplifyConfigurationHelper();

const [temporary, password] = generate.generateMultiple(2, {
length: 12,
numbers: true,
uppercase: true,
symbols: true,
strict: true,
});

process.env.AWS_COGNITO_USER_POOL_ID = configHelper.getUserPoolId();
process.env.TEMPORARY_USER_PASSWORD = temporary;
process.env.USER_PASSWORD = password;
process.env.USER_EMAIL_PREFIX = uuidv4().slice(0, 5);

return config;
}

export default globalSetup;
36 changes: 36 additions & 0 deletions tests/test-team/config/local.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { defineConfig, devices } from '@playwright/test';
import baseConfig from './playwright.config';

export default defineConfig({
...baseConfig,

timeout: 10000_000,

projects: [
{
name: 'component',
testMatch: '*.component.ts',
use: {
screenshot: 'only-on-failure',
baseURL: 'http://localhost:3000',
...devices['Desktop Chrome'],
headless: false,
},
},
{
name: 'e2e-local',
testMatch: '*.e2e.spec.ts',
use: {
baseURL: 'http://localhost:3000',
...devices['Desktop Chrome'],
},
},
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'npm run test:start-local-app',
url: 'http://localhost:3000/auth',
reuseExistingServer: !process.env.CI,
stderr: 'pipe',
},
});
28 changes: 28 additions & 0 deletions tests/test-team/config/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
testDir: '../',
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
retries: 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 4 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['line'],
[
'html',
{
outputFolder: '../playwright-report',
open: process.env.CI ? 'never' : 'on-failure',
},
],
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
globalSetup: './global.setup.ts',
});
12 changes: 12 additions & 0 deletions tests/test-team/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
TEMPORARY_USER_PASSWORD: string;
USER_PASSWORD: string;
AWS_COGNITO_USER_POOL_ID: string;
USER_EMAIL_PREFIX: string;
}
}
}

export {};
23 changes: 23 additions & 0 deletions tests/test-team/helpers/amplify-configuration-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as fs from 'node:fs';

type AmplifyOutput = {
auth: {
user_pool_id: string;
user_pool_client_id: string;
identity_pool_id: string;
};
};

export class AmplifyConfigurationHelper {
private readonly _configuration: AmplifyOutput;

constructor() {
this._configuration = JSON.parse(
fs.readFileSync('../../amplify_outputs.json', 'utf8')
);
}

getUserPoolId() {
return this._configuration.auth.user_pool_id;
}
}
Loading

0 comments on commit b91fa26

Please sign in to comment.