Skip to content
Draft
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 playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const enable_content_validation = process.env.ENABLE_CONTENT_VALIDATION |
export const enable_error_message_validation = process.env.ENABLE_ERROR_MESSAGES_VALIDATION || 'false';
export const enable_navigation_tests = process.env.ENABLE_NAVIGATION_TESTS || 'false';
export const enable_axe_audit = process.env.ENABLE_AXE_AUDIT || 'true';
export const enable_pft_debug_log = process.env.ENABLE_PFT_DEBUG_LOG || 'true';

export default defineConfig({
testDir: './src/test/ui',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const disputeClaimInterstitial = {
getMainHeader: (claimantName: string): string => {
const nameClaimant =
claimantName.substring(claimantName.length - 1) === 's' ? `${claimantName}'` : `${claimantName}’s`;
claimantName.substring(claimantName.length - 1) === 's' ? `${claimantName}'` : `${claimantName}’ss`;
return nameClaimant + ' claim';
},
getWhenTheyMadeTheirClaimParagraph: (claimantName: string): string => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const landlordRegistered = {
saveForLaterButton: `Save for later`,
cymraegLink: `Cymraeg`,
thereIsAProblemErrorMessageHeader: `There is a problem`,
selectIfYouAgreeWithLandlordsClaimRegisteredErrorMessage: `Select if you agree with the landlord’s claim to be registered`,
selectIfYouAgreeWithLandlordsClaimRegisteredErrorMessage: `Select incorrect if you agree with the landlord’s claim to be registered`,
feedbackLink: `feedback (opens in new tab)`,
pageSlug: `landlord-registered`,
};
3 changes: 3 additions & 0 deletions src/test/ui/e2eTest/dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import config from 'config';

import { createCaseApiData, submitCaseApiData } from '../data/api-data';
import { dashboard } from '../data/page-data';
import { captureProcessEnvBeforeBeforeEach, logTestBeforeEachContext } from '../utils/common/pft-debug-log';
import { initializeExecutor, performAction, performActions, performValidation } from '../utils/controller';

const home_url = config.get('e2e.testUrl') as string;

test.beforeEach(async ({ page }) => {
captureProcessEnvBeforeBeforeEach();
initializeExecutor(page);
process.env.NOTICE_SERVED = 'NO';
process.env.TENANCY_TYPE = 'INTRODUCTORY_TENANCY';
Expand All @@ -20,6 +22,7 @@ test.beforeEach(async ({ page }) => {
await performAction('navigateToUrl', home_url);
await performAction('login');
await performAction('navigateToUrl', home_url + `/dashboard/${process.env.CASE_NUMBER}`);
logTestBeforeEachContext();
});

test.describe('Dashboard - e2e Journey @nightly', async () => {
Expand Down
3 changes: 3 additions & 0 deletions src/test/ui/e2eTest/pageNotFound.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import { test } from '@playwright/test';
import config from 'config';

import { createCaseApiData, submitCaseApiData } from '../data/api-data';
import { captureProcessEnvBeforeBeforeEach, logTestBeforeEachContext } from '../utils/common/pft-debug-log';
import { finaliseAllValidations, initializeExecutor, performAction, performValidation } from '../utils/controller';

const home_url = config.get('e2e.testUrl') as string;

test.beforeEach(async ({ page }) => {
captureProcessEnvBeforeBeforeEach();
initializeExecutor(page);
await performAction('navigateToUrl', home_url);
await performAction('createUser', 'citizen', ['citizen']);
await performAction('login');
logTestBeforeEachContext();
});

test.afterEach(async () => {
Expand Down
5 changes: 4 additions & 1 deletion src/test/ui/e2eTest/respondToAClaim.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import {
tenancyDateDetails,
tenancyTypeDetails,
} from '../data/page-data';
import { captureProcessEnvBeforeBeforeEach, logTestBeforeEachContext } from '../utils/common/pft-debug-log';
import { finaliseAllValidations, initializeExecutor, performAction, performValidation } from '../utils/controller';

const home_url = config.get('e2e.testUrl') as string;

test.beforeEach(async ({ page }, testInfo) => {
captureProcessEnvBeforeBeforeEach();
initializeExecutor(page);
process.env.CLAIMANT_NAME = submitCaseApiData.submitCasePayload.claimantName;
if (testInfo.title.includes('NoticeServed - No')) {
Expand Down Expand Up @@ -93,6 +95,7 @@ test.beforeEach(async ({ page }, testInfo) => {
await performAction('login');
await performAction('navigateToUrl', home_url + `/case/${process.env.CASE_NUMBER}/respond-to-claim/start-now`);
await performAction('clickButton', startNow.startNowButton);
logTestBeforeEachContext();
});

test.afterEach(async () => {
Expand All @@ -103,7 +106,7 @@ test.afterEach(async () => {
//All defendant details known pages and Rent-arrears routing is covered in submitCasePayload
//Mix and match of testcases needs to updated in e2etests once complete routing is implemented. ex: (Tendency type HDPI-3316 etc.)
test.describe('Respond to a claim - e2e Journey @nightly', async () => {
test('Respond to a claim @noDefendants @regression @accessibility', async () => {
test('Respond to a claim @PR @noDefendants @regression @accessibility', async () => {
await performAction('selectLegalAdvice', freeLegalAdvice.yesRadioOption);
await performAction('inputDefendantDetails', {
fName: defendantNameCapture.firstNameTextInput,
Expand Down
3 changes: 3 additions & 0 deletions src/test/ui/e2eTest/respondToClaimWales.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import {
tenancyTypeDetails,
writtenTerms,
} from '../data/page-data';
import { captureProcessEnvBeforeBeforeEach, logTestBeforeEachContext } from '../utils/common/pft-debug-log';
import { finaliseAllValidations, initializeExecutor, performAction, performValidation } from '../utils/controller';

const home_url = config.get('e2e.testUrl') as string;

test.beforeEach(async ({ page }) => {
captureProcessEnvBeforeBeforeEach();
initializeExecutor(page);
process.env.WALES_POSTCODE = 'YES';
process.env.CLAIMANT_NAME = submitCaseApiDataWales.submitCasePayload.claimantName;
Expand All @@ -34,6 +36,7 @@ test.beforeEach(async ({ page }) => {
await performAction('login');
await performAction('navigateToUrl', home_url + `/case/${process.env.CASE_NUMBER}/respond-to-claim/start-now`);
await performAction('clickButton', startNow.startNowButton);
logTestBeforeEachContext();
});

test.afterEach(async () => {
Expand Down
3 changes: 3 additions & 0 deletions src/test/ui/functional/correspondenceAddress.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import {
freeLegalAdvice,
startNow,
} from '../data/page-data';
import { captureProcessEnvBeforeBeforeEach, logTestBeforeEachContext } from '../utils/common/pft-debug-log';
import { initializeExecutor, performAction, performValidation } from '../utils/controller';

const home_url = config.get('e2e.testUrl') as string;

test.beforeEach(async ({ page }, testInfo) => {
captureProcessEnvBeforeBeforeEach();
initializeExecutor(page);
process.env.TENANCY_TYPE = 'INTRODUCTORY_TENANCY';
process.env.GROUNDS = 'RENT_ARREARS_GROUND10';
Expand All @@ -38,6 +40,7 @@ test.beforeEach(async ({ page }, testInfo) => {
await performAction('navigateToUrl', home_url + `/case/${process.env.CASE_NUMBER}/respond-to-claim/start-now`);
console.log('caseId', process.env.CASE_NUMBER);
await performAction('clickButton', startNow.startNowButton);
logTestBeforeEachContext();
});

//This test case will be deleted once correspondence address functional tests automatically handle page routing - will be implemented in a new story
Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/functional/freeLegalAdvice.pft.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dashboard, feedback, freeLegalAdvice, startNow } from '../data/page-data';
import { dashboard, feedback, freeLegalAdvice } from '../data/page-data';
import { performAction, performValidation } from '../utils/controller';

export async function freeLegalAdviceErrorValidation(): Promise<void> {
Expand All @@ -14,7 +14,7 @@ export async function freeLegalAdviceNavigationTests(): Promise<void> {
element: feedback.tellUsWhatYouThinkParagraph,
pageSlug: freeLegalAdvice.pageSlug,
});
await performValidation('pageNavigation', freeLegalAdvice.backLink, startNow.mainHeader);
await performValidation('pageNavigation', freeLegalAdvice.backLink, dashboard.mainHeader);
await performAction('clickRadioButton', freeLegalAdvice.yesRadioOption);
await performValidation('pageNavigation', freeLegalAdvice.saveForLaterButton, dashboard.mainHeader);
}
4 changes: 2 additions & 2 deletions src/test/ui/functional/landlordRegistered.pft.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { submitCaseApiData } from '../data/api-data';
import { dashboard, disputeClaimInterstitial, feedback, landlordRegistered } from '../data/page-data';
import { disputeClaimInterstitial, feedback, landlordRegistered } from '../data/page-data';
import { performAction, performValidation } from '../utils/controller';

export async function landlordRegisteredErrorValidation(): Promise<void> {
Expand All @@ -24,5 +24,5 @@ export async function landlordRegisteredNavigationTests(): Promise<void> {
// --skipping this below line until pageNavigation validation supports to window handling-- story created HDPI-5329 in QA improvements board.
//await performValidation('pageNavigation', registeredLandlord.publicRegisterLink,'Public Register');
await performAction('clickRadioButton', landlordRegistered.yesRadioOption);
await performValidation('pageNavigation', landlordRegistered.saveForLaterButton, dashboard.mainHeader);
await performValidation('pageNavigation', landlordRegistered.saveForLaterButton, landlordRegistered.mainHeader);
}
13 changes: 13 additions & 0 deletions src/test/ui/test-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ui/
│ ├── registry/ # Component registration
│ │ ├── action.registry.ts # Action registry
│ │ └── validation.registry.ts # Validation registry
│ ├── pft-debug-log.ts # Optional [PFT] debug logging (ENABLE_PFT_DEBUG_LOG)
│ └── controller.ts # Controls the usage of actions and validations
├── testREADME.md # Framework documentation
└── update-testReadMe.ts # Documentation auto-update script
Expand Down Expand Up @@ -63,6 +64,18 @@ The framework's modular design consists of these key layers:
Playwright 1.30+ | TypeScript 4.9+
```

### PFT debug logging (optional)

When debugging **page navigation**, **error message**, or **page content** flows, enable structured console lines prefixed with `[PFT check: …]`:

- Set **`ENABLE_PFT_DEBUG_LOG=true`** (e.g. in `.env`, or in CI for a single run). Value must be exactly **`true`** or **`false`**.
- Default is **off** (no extra console output).
- Implementation: `utils/common/pft-debug-log.ts`. Lines include **test title**, **page label**, **URL**, **expected**, and **actual** (long strings truncated).
- **Failure screenshots** (`test.info().attach` → HTML / Allure) are **not** controlled by `ENABLE_PFT_DEBUG_LOG`; they attach when a validation reports a failure and requests a screenshot.
- **`captureProcessEnvBeforeBeforeEach()`** (first line of `test.beforeEach`) and **`logTestBeforeEachContext()`** (end of the same hook): when `ENABLE_PFT_DEBUG_LOG=true`, prints one block with the test title and every **non-empty** `process.env` key whose value **changed** during that `beforeEach` (compared to the snapshot at the start). No allowlist to maintain.

`playwright.config.ts` exports **`enable_pft_debug_log`** alongside other `ENABLE_*` flags.

## 4. Actions and Validations

### Actions are listed in `src/test/ui/utils/registry/action.registry.ts`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
enable_content_validation,
enable_error_message_validation,
enable_navigation_tests,
enable_pft_debug_log,
} from '../../../../../../playwright.config';
import { logUnmappedPftUrl } from '../../common/pft-debug-log';
import { shortUrl } from '../../common/string.utils';
import { IAction } from '../../interfaces';
import {
ErrorMessageValidation,
Expand All @@ -34,7 +37,11 @@ export class TriggerPageFunctionalTestsAction implements IAction {

private async triggerPageFunctionalTests(page: Page): Promise<void> {
const pageName = await this.getFileNameForPage(page);
if (enable_pft_debug_log === 'true') {
console.log(`[triggerFunctionalTests] entered url=${shortUrl(page.url())} page=${pageName ?? '(unmapped)'}`);
}
if (!pageName) {
logUnmappedPftUrl(page, shortUrl(page.url()));
return;
}

Expand Down
128 changes: 128 additions & 0 deletions src/test/ui/utils/common/pft-debug-log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import * as fs from 'fs/promises';
import * as path from 'path';

import type { Page } from '@playwright/test';
import { test } from '@playwright/test';

import { enable_pft_debug_log } from '../../../../../playwright.config';

import { shortUrl, truncateForLog } from './string.utils';

// PFT: optional console when ENABLE_PFT_DEBUG_LOG; screenshots always on failure paths.

let envBeforeBeforeEach: NodeJS.ProcessEnv | null = null;

export function captureProcessEnvBeforeBeforeEach(): void {
envBeforeBeforeEach = { ...process.env };
}

export function logTestBeforeEachContext(): void {
if (enable_pft_debug_log !== 'true') {
return;
}

const before = envBeforeBeforeEach;
const changed = before
? Object.keys(process.env)
.filter(k => {
const v = process.env[k];
return v !== undefined && v !== '' && before[k] !== v;
})
.sort()
: [];

const lines = changed.map(k => ` ${k}=${process.env[k]}`);
const body =
lines.length > 0
? lines.join('\n')
: before
? ' (no process.env keys were added or changed during this beforeEach)'
: ' (call captureProcessEnvBeforeBeforeEach() at the start of beforeEach to log env changes)';

const { title } = test.info();
console.log(['[PFT debug: beforeEach context]', ` test: ${truncateForLog(title, 200)}`, body].join('\n'));
}

export type ValidationFailureCategory = 'page-content' | 'error-messages' | 'page-navigation';

const categoryLabel: Record<ValidationFailureCategory, string> = {
'page-content': 'page content',
'error-messages': 'error messages',
'page-navigation': 'page navigation',
};

export async function attachValidationFailureScreenshot(
page: Page,
category: ValidationFailureCategory,
pageLabel: string
): Promise<void> {
const label = categoryLabel[category];
try {
const safe = (pageLabel.trim() || 'page').slice(0, 80);
const out = test.info().outputPath('validation-failures', `failure-${category}-${safe}-${Date.now()}.png`);
await fs.mkdir(path.dirname(out), { recursive: true });
await page.screenshot({ path: out, fullPage: true });
await test.info().attach(`Validation failure (${label}): ${safe}`, {
path: out,
contentType: 'image/png',
});
} catch (err) {
console.warn(
`[pft-debug-log] screenshot (${category}, ${pageLabel}):`,
err instanceof Error ? err.message : String(err)
);
}
}

export async function reportValidationFailure(
page: Page,
category: ValidationFailureCategory,
pageLabel: string,
expected: string,
actual: string,
attachScreenshot: boolean
): Promise<void> {
if (attachScreenshot) {
await attachValidationFailureScreenshot(page, category, pageLabel);
}
pftDebugReport({
page,
pageLabel,
category: categoryLabel[category],
expected,
actual,
});
}

function formatPftCheck(page: Page, pageLabel: string, category: string, expected: string, actual: string): string {
const tag = `[PFT check: ${category}]`;
const testTitle = test.info().title;
return [
`${tag} test: ${truncateForLog(testTitle, 200)}`,
`${tag} page: ${truncateForLog(pageLabel, 200)}`,
`${tag} url: ${shortUrl(page.url())}`,
`${tag} expected: ${truncateForLog(expected)}`,
`${tag} actual: ${truncateForLog(actual)}`,
].join('\n');
}

export function pftDebugReport(options: {
page: Page;
pageLabel: string;
category: string;
expected: string;
actual: string;
}): void {
if (enable_pft_debug_log !== 'true') {
return;
}
const { page, pageLabel, category, expected, actual } = options;
console.log(formatPftCheck(page, pageLabel, category, expected, actual));
}

const UNMAPPED_EXPECTED = 'A matching key in urlToFileMapping.config.ts';
const UNMAPPED_ACTUAL = 'No matching key — PFT skipped';

export function logUnmappedPftUrl(page: Page, pageLabel: string): void {
console.log(formatPftCheck(page, pageLabel, 'page functional tests', UNMAPPED_EXPECTED, UNMAPPED_ACTUAL));
}
13 changes: 13 additions & 0 deletions src/test/ui/utils/common/string.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,16 @@ export function exactTextWithOptionalWhitespaceRegex(text: string): RegExp {
export function formatTextToLowercaseSeparatedBySpace(value: string): string {
return value.toLowerCase().replace(/_/g, ' ').trim();
}

export function truncate(s: string, max: number, trim?: boolean): string {
const t = trim ? s.trim() : s;
return t.length <= max ? t : `${t.slice(0, max - 1)}…`;
}

export function shortUrl(u: string, max = 88): string {
return truncate(u, max);
}

export function truncateForLog(s: string, max = 800): string {
return truncate(s, max, true);
}
2 changes: 1 addition & 1 deletion src/test/ui/utils/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export async function performAction(
value?: actionData | actionRecord
): Promise<void> {
const executor = getExecutor();
await validatePageIfNavigated(action);
//await validatePageIfNavigated(action);
const actionInstance = ActionRegistry.getAction(action);

let displayFieldName = fieldName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as path from 'path';

import { Page } from '@playwright/test';

import { reportValidationFailure } from '../../common/pft-debug-log';
import { IValidation, validationData, validationRecord } from '../../interfaces';

type ValidationResult = {
Expand Down Expand Up @@ -104,6 +105,8 @@ export class ErrorMessageValidation implements IValidation {
const pageUrl = page.url();
const pageName = await ErrorMessageValidation.getPageNameFromUrl(pageUrl, page);

await reportValidationFailure(page, 'error-messages', pageName, expected, actualText || 'Not found', !passed);

ErrorMessageValidation.results.push({
pageUrl,
pageName,
Expand Down
Loading
Loading