From 94f4f8bcf07ba5a3e29fb6323c4f952ac3384562 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 17:59:30 +0100 Subject: [PATCH 01/12] Added screenshots to allure report in case of validation issues and intentionally failing tests on free legal page --- playwright.config.ts | 1 + .../page-data/freeLegalAdvice.page.data.ts | 2 +- .../ui/data/page-data/startNow.page.data.ts | 2 +- src/test/ui/e2eTest/respondToAClaim.spec.ts | 2 +- src/test/ui/test-README.md | 11 +++ .../triggerPageFunctionalTests.action.ts | 23 +++++ src/test/ui/utils/common/pft-debug-log.ts | 98 +++++++++++++++++++ src/test/ui/utils/controller.ts | 4 +- .../error-message.validation.ts | 3 + .../pageContent.validation.ts | 26 ++++- .../pageNavigation.validation.ts | 37 +++++++ 11 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 src/test/ui/utils/common/pft-debug-log.ts diff --git a/playwright.config.ts b/playwright.config.ts index da025f06b..942144463 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -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 || 'false'; export default defineConfig({ testDir: './src/test/ui', diff --git a/src/test/ui/data/page-data/freeLegalAdvice.page.data.ts b/src/test/ui/data/page-data/freeLegalAdvice.page.data.ts index 6064d76a2..efa6dbafe 100644 --- a/src/test/ui/data/page-data/freeLegalAdvice.page.data.ts +++ b/src/test/ui/data/page-data/freeLegalAdvice.page.data.ts @@ -23,6 +23,6 @@ export const freeLegalAdvice = { saveForLaterButton: `Save for later`, cymraegLink: `Cymraeg`, backLink: `Back`, - youMustSayAboutFreeLegalAdviceErrorMessage: `You must say if you've had any free legal advice`, + youMustSayAboutFreeLegalAdviceErrorMessage: `You must say if you've had any incorrect free legal advice`, thereIsAProblemErrorMessageHeader: `There is a problem`, }; diff --git a/src/test/ui/data/page-data/startNow.page.data.ts b/src/test/ui/data/page-data/startNow.page.data.ts index db5d20b66..5d83915ee 100644 --- a/src/test/ui/data/page-data/startNow.page.data.ts +++ b/src/test/ui/data/page-data/startNow.page.data.ts @@ -1,5 +1,5 @@ export const startNow = { - mainHeader: `Respond to a property possession claim online`, + mainHeader: `Respond to a incorrect property possession claim online`, respondWithinParagraph: `There’s no fee to respond to a claim. You must respond within 14 days of receiving the claim pack in the post.`, thisServiceIsAlsoAvailableParagraph: `This service is also available `, inWelshLink: `in Welsh (Cymraeg)`, diff --git a/src/test/ui/e2eTest/respondToAClaim.spec.ts b/src/test/ui/e2eTest/respondToAClaim.spec.ts index f3a5adfe9..07211b96c 100644 --- a/src/test/ui/e2eTest/respondToAClaim.spec.ts +++ b/src/test/ui/e2eTest/respondToAClaim.spec.ts @@ -103,7 +103,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, diff --git a/src/test/ui/test-README.md b/src/test/ui/test-README.md index 377e68f57..a79fd5f43 100644 --- a/src/test/ui/test-README.md +++ b/src/test/ui/test-README.md @@ -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 @@ -63,6 +64,16 @@ 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]`: + +- Set **`ENABLE_PFT_DEBUG_LOG=true`** or **`PFT_DEBUG_LOG=true`** (e.g. in `.env`, or in CI for a single run). +- Default is **off** (no extra output). +- Implementation: `utils/pft-debug-log.ts`. Logs include the current **URL**, **Playwright test title**, **action/validation type**, and safe field summaries. Keys matching password/token patterns are **redacted**; long strings are **truncated**. + +`playwright.config.ts` exports **`enable_pft_debug_log`** for consistency with other `ENABLE_*` flags. + ## 4. Actions and Validations ### Actions are listed in `src/test/ui/utils/registry/action.registry.ts` diff --git a/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts b/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts index 4b28c4e24..1d7eb06eb 100644 --- a/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts +++ b/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts @@ -7,7 +7,9 @@ import { enable_content_validation, enable_error_message_validation, enable_navigation_tests, + enable_pft_debug_log, } from '../../../../../../playwright.config'; +import { pftDebugReport, shortUrl } from '../../common/pft-debug-log'; import { IAction } from '../../interfaces'; import { ErrorMessageValidation, @@ -34,7 +36,19 @@ export class TriggerPageFunctionalTestsAction implements IAction { private async triggerPageFunctionalTests(page: Page): Promise { 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) { + if (enable_content_validation === 'true') { + pftDebugReport({ + page, + pageLabel: shortUrl(page.url()), + category: 'page content', + expected: 'URL segment or page heading maps to a page name in urlToFileMapping.config.ts', + actual: 'Could not resolve page from URL mapping', + }); + } return; } @@ -56,6 +70,15 @@ export class TriggerPageFunctionalTestsAction implements IAction { await this.runPageContentValidation(page, pageName); } else { PageContentValidation.trackMissingDataFile(pageName); + pftDebugReport({ + page, + pageLabel: pageName, + category: 'page content', + expected: `Page data file ${pageName}.page.data.ts exists under data/page-data/`, + actual: + `URL maps to "${pageName}" in urlToFileMapping.config.ts, but there is no matching page data file. ` + + `Add src/test/ui/data/page-data/${pageName}.page.data.ts (page content / design source for validation).`, + }); } } diff --git a/src/test/ui/utils/common/pft-debug-log.ts b/src/test/ui/utils/common/pft-debug-log.ts new file mode 100644 index 000000000..3ca070910 --- /dev/null +++ b/src/test/ui/utils/common/pft-debug-log.ts @@ -0,0 +1,98 @@ +import * as fs from 'fs/promises'; +import * as path from 'path'; + +import type { Page } from '@playwright/test'; +import { test } from '@playwright/test'; + +import { + enable_content_validation, + enable_error_message_validation, + enable_navigation_tests, + enable_pft_debug_log, +} from '../../../../../playwright.config'; + +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 const shortUrl = (u: string, max = 88) => truncate(u, max); +export const truncateForLog = (s: string, max = 800) => truncate(s, max, true); + +export type ValidationFailureCategory = 'page-content' | 'error-messages' | 'page-navigation'; + +const validationEnv: Record = { + 'page-content': enable_content_validation, + 'error-messages': enable_error_message_validation, + 'page-navigation': enable_navigation_tests, +}; + +const validationLabel: Record = { + 'page-content': 'page content', + 'error-messages': 'error messages', + 'page-navigation': 'page navigation', +}; + +async function attachValidationFailureScreenshot( + page: Page, + category: ValidationFailureCategory, + pageLabel: string, + debugLabel: string +): Promise { + if (validationEnv[category] !== 'true') { + return; + } + try { + const safe = (pageLabel.trim() || 'page').slice(0, 80); + const fileName = `failure-${category}-${safe}-${Date.now()}.png`; + const out = test.info().outputPath('validation-failures', fileName); + await fs.mkdir(path.dirname(out), { recursive: true }); + await page.screenshot({ path: out, fullPage: true }); + await test.info().attach(`Validation failure (${debugLabel}): ${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 { + const label = validationLabel[category]; + if (attachScreenshot) { + await attachValidationFailureScreenshot(page, category, pageLabel, label); + } + pftDebugReport({ page, pageLabel, category: label, expected, actual }); +} + +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; + const tag = `[PFT check: ${category}]`; + console.log( + [ + `${tag} page: ${truncateForLog(pageLabel, 200)}`, + `${tag} url: ${shortUrl(page.url())}`, + `${tag} expected: ${truncateForLog(expected)}`, + `${tag} actual: ${truncateForLog(actual)}`, + ].join('\n') + ); +} diff --git a/src/test/ui/utils/controller.ts b/src/test/ui/utils/controller.ts index d77bc965d..1014bcca7 100644 --- a/src/test/ui/utils/controller.ts +++ b/src/test/ui/utils/controller.ts @@ -41,7 +41,7 @@ async function detectPageNavigation(): Promise { if (!startAxeAudit && executor.page.url().includes('start-now')) { startAxeAudit = true; } - if (!startFunctionalTests && executor.page.url().includes('free-legal-advice')) { + if (!startFunctionalTests && executor.page.url().includes('start-now')) { startFunctionalTests = true; } const pageNavigated = currentUrl !== previousUrl; @@ -90,7 +90,7 @@ export async function performAction( value?: actionData | actionRecord ): Promise { const executor = getExecutor(); - await validatePageIfNavigated(action); + //await validatePageIfNavigated(action); const actionInstance = ActionRegistry.getAction(action); let displayFieldName = fieldName; diff --git a/src/test/ui/utils/validations/custom-validations/error-message.validation.ts b/src/test/ui/utils/validations/custom-validations/error-message.validation.ts index 3c09c9564..e651aa9ee 100644 --- a/src/test/ui/utils/validations/custom-validations/error-message.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/error-message.validation.ts @@ -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 = { @@ -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, diff --git a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts index cb01d0d93..ba497ec47 100644 --- a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import { Page } from '@playwright/test'; +import { reportValidationFailure, truncateForLog } from '../../common/pft-debug-log'; import { escapeForRegex, exactTextWithOptionalWhitespaceRegex } from '../../common/string.utils'; import { IValidation } from '../../interfaces'; @@ -139,6 +140,14 @@ export class PageContentValidation implements IValidation { const pageData = await this.getPageData(pageName); if (!pageData) { + await reportValidationFailure( + page, + 'page-content', + pageName, + `Page data file exists for "${pageName}" (data/page-data/${pageName}.page.data.ts)`, + 'No page data file found', + false + ); return; } @@ -161,6 +170,21 @@ export class PageContentValidation implements IValidation { } PageContentValidation.validationResults.set(pageUrl, pageResults); + + const failed = pageResults.filter(r => r.status === 'fail'); + const total = pageResults.length; + const expectedSummary = + total === 0 + ? `No content fields to check for "${pageName}"` + : `All ${total} content field(s) from page data should be visible on "${pageName}"`; + const actualSummary = + total === 0 + ? 'Nothing to validate' + : failed.length === 0 + ? `All ${total} matched (visible)` + : `${failed.length} not visible: ${failed.map(f => `${f.element} → "${truncateForLog(f.expected, 120)}"`).join('; ')}`; + + await reportValidationFailure(page, 'page-content', pageName, expectedSummary, actualSummary, failed.length > 0); } private async getPageData(pageName: string): Promise { @@ -283,7 +307,7 @@ export class PageContentValidation implements IValidation { } if (failedPages.size > 0) { - console.log('\n❌ VALIDATION FAILED:'); + console.log('\n❌ FAILED PAGE CONTENT VALIDATION:'); for (const [pageName, pageFailures] of failedPages) { console.log(` Page: ${pageName}`); let pageFailureCount = 0; diff --git a/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts b/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts index b3c84128f..7b6f8467b 100644 --- a/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import { Page, expect } from '@playwright/test'; +import { reportValidationFailure } from '../../common/pft-debug-log'; import { performAction } from '../../controller'; import { IValidation } from '../../interfaces'; @@ -90,6 +91,15 @@ export class PageNavigationValidation implements IValidation { if (hasPFTFile) { PageNavigationValidation.pagesPassed.add(pageName); } + + await reportValidationFailure( + page, + 'page-navigation', + pageName, + `h1 text: "${expectedHeader}"`, + `h1 text: "${expectedHeader}"`, + false + ); } catch (error) { const actualText = await page .locator('h1') @@ -111,6 +121,15 @@ export class PageNavigationValidation implements IValidation { error: error instanceof Error ? error.message.split('\n')[0] : String(error), hasPFTFile, }); + + await reportValidationFailure( + page, + 'page-navigation', + pageName, + `h1 text: "${expectedHeader}"`, + `h1 text: "${(actualText || 'Not found').trim()}"`, + true + ); } } @@ -137,6 +156,15 @@ export class PageNavigationValidation implements IValidation { if (hasPFTFile) { PageNavigationValidation.pagesPassed.add(pageName); } + + await reportValidationFailure( + page, + 'page-navigation', + pageName, + `h1 after navigation: "${expectedHeader}"`, + `h1: "${(actualText || '').trim()}"`, + false + ); } catch (error) { const pageName = await PageNavigationValidation.getPageNameFromUrl(page.url(), page); const actualText = await page @@ -157,6 +185,15 @@ export class PageNavigationValidation implements IValidation { error: error instanceof Error ? error.message.split('\n')[0] : String(error), hasPFTFile, }); + + await reportValidationFailure( + page, + 'page-navigation', + pageName, + `h1 after navigation: "${expectedHeader}"`, + `h1: "${(actualText || 'Not found').trim()}"`, + true + ); } } From c78bd581a3c28111e27ce99779fe2229e43b8141 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 18:11:49 +0100 Subject: [PATCH 02/12] Added screenshots to allure report in case of validation issues and intentionally failing tests on free legal page --- .../pageContent.validation.ts | 2 +- .../pageNavigation.validation.ts | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts index 239ef57ce..508b34198 100644 --- a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts @@ -146,7 +146,7 @@ export class PageContentValidation implements IValidation { pageName, `Page data file exists for "${pageName}" (data/page-data/${pageName}.page.data.ts)`, 'No page data file found', - false + true ); return; } diff --git a/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts b/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts index 43957807c..d2de7984f 100644 --- a/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts @@ -191,13 +191,23 @@ export class PageNavigationValidation implements IValidation { } } + const expectedNavLog = [ + expectedElementText && `element: "${expectedElementText}"`, + expectedUrlPattern && `url: "${expectedUrlPattern}"`, + ] + .filter(Boolean) + .join('; '); + const actualNavLog = [actualElementText && `element: "${actualElementText}"`, `url: "${actualUrl || page.url()}"`] + .filter(Boolean) + .join('; '); + await reportValidationFailure( page, 'page-navigation', pageName, - `h1 after navigation: "${expectedHeader}"`, - `h1: "${(actualText || '').trim()}"`, - false + expectedNavLog || 'page navigation validation', + actualNavLog, + !overallPassed ); } catch (error) { const pageName = await PageNavigationValidation.getPageNameFromUrl(page.url(), page); @@ -239,7 +249,7 @@ export class PageNavigationValidation implements IValidation { page, 'page-navigation', pageName, - `h1 after navigation: "${expectedHeader}"`, + `h1 after navigation: "${expectedValue}"`, `h1: "${(actualText || 'Not found').trim()}"`, true ); From 6e6e2654d1a8123145344c4a96ec9385cd338ea3 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 18:47:34 +0100 Subject: [PATCH 03/12] Added screenshots to allure report in case of validation issues and intentionally failing tests on free legal page --- playwright.config.ts | 2 ++ src/test/ui/test-README.md | 11 ++++++----- src/test/ui/utils/common/pft-debug-log.ts | 24 +++++++++-------------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 942144463..931b7aef6 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -19,6 +19,8 @@ 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'; + +/** `ENABLE_PFT_DEBUG_LOG`: optional `[PFT check: …]` console lines (expected/actual, URL, test title). Use exactly `true` or `false`. Does not control failure PNG attachments. */ export const enable_pft_debug_log = process.env.ENABLE_PFT_DEBUG_LOG || 'false'; export default defineConfig({ diff --git a/src/test/ui/test-README.md b/src/test/ui/test-README.md index a79fd5f43..6ef5cf7d7 100644 --- a/src/test/ui/test-README.md +++ b/src/test/ui/test-README.md @@ -66,13 +66,14 @@ 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]`: +When debugging **page navigation**, **error message**, or **page content** flows, enable structured console lines prefixed with `[PFT check: …]`: -- Set **`ENABLE_PFT_DEBUG_LOG=true`** or **`PFT_DEBUG_LOG=true`** (e.g. in `.env`, or in CI for a single run). -- Default is **off** (no extra output). -- Implementation: `utils/pft-debug-log.ts`. Logs include the current **URL**, **Playwright test title**, **action/validation type**, and safe field summaries. Keys matching password/token patterns are **redacted**; long strings are **truncated**. +- 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. -`playwright.config.ts` exports **`enable_pft_debug_log`** for consistency with other `ENABLE_*` flags. +`playwright.config.ts` exports **`enable_pft_debug_log`** alongside other `ENABLE_*` flags. ## 4. Actions and Validations diff --git a/src/test/ui/utils/common/pft-debug-log.ts b/src/test/ui/utils/common/pft-debug-log.ts index 3ca070910..e1a1c6aa3 100644 --- a/src/test/ui/utils/common/pft-debug-log.ts +++ b/src/test/ui/utils/common/pft-debug-log.ts @@ -4,12 +4,13 @@ import * as path from 'path'; import type { Page } from '@playwright/test'; import { test } from '@playwright/test'; -import { - enable_content_validation, - enable_error_message_validation, - enable_navigation_tests, - enable_pft_debug_log, -} from '../../../../../playwright.config'; +import { enable_pft_debug_log } from '../../../../../playwright.config'; + +/** + * Console: `[PFT check: …]` lines only when `ENABLE_PFT_DEBUG_LOG=true` (`enable_pft_debug_log`). + * Failure PNGs: `test.info().attach` whenever `reportValidationFailure(..., attachScreenshot: true)` runs — + * not gated by `ENABLE_PFT_DEBUG_LOG` (Allure/HTML report pick up attachments from test results). + */ function truncate(s: string, max: number, trim?: boolean): string { const t = trim ? s.trim() : s; @@ -21,12 +22,6 @@ export const truncateForLog = (s: string, max = 800) => truncate(s, max, true); export type ValidationFailureCategory = 'page-content' | 'error-messages' | 'page-navigation'; -const validationEnv: Record = { - 'page-content': enable_content_validation, - 'error-messages': enable_error_message_validation, - 'page-navigation': enable_navigation_tests, -}; - const validationLabel: Record = { 'page-content': 'page content', 'error-messages': 'error messages', @@ -39,9 +34,6 @@ async function attachValidationFailureScreenshot( pageLabel: string, debugLabel: string ): Promise { - if (validationEnv[category] !== 'true') { - return; - } try { const safe = (pageLabel.trim() || 'page').slice(0, 80); const fileName = `failure-${category}-${safe}-${Date.now()}.png`; @@ -87,8 +79,10 @@ export function pftDebugReport(options: { } const { page, pageLabel, category, expected, actual } = options; const tag = `[PFT check: ${category}]`; + const testTitle = test.info().title; console.log( [ + `${tag} test: ${truncateForLog(testTitle, 200)}`, `${tag} page: ${truncateForLog(pageLabel, 200)}`, `${tag} url: ${shortUrl(page.url())}`, `${tag} expected: ${truncateForLog(expected)}`, From 0d033abe425a4793df81f608838b8291350a88b9 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 18:49:29 +0100 Subject: [PATCH 04/12] Added screenshots to allure report in case of validation issues and intentionally failing tests on free legal page --- src/test/ui/data/page-data/freeLegalAdvice.page.data.ts | 2 +- src/test/ui/data/page-data/startNow.page.data.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/ui/data/page-data/freeLegalAdvice.page.data.ts b/src/test/ui/data/page-data/freeLegalAdvice.page.data.ts index 3ed9fe06c..404ef4c58 100644 --- a/src/test/ui/data/page-data/freeLegalAdvice.page.data.ts +++ b/src/test/ui/data/page-data/freeLegalAdvice.page.data.ts @@ -23,7 +23,7 @@ export const freeLegalAdvice = { saveForLaterButton: `Save for later`, cymraegLink: `Cymraeg`, backLink: `Back`, - youMustSayAboutFreeLegalAdviceErrorMessage: `You must say if you've had any incorrect free legal advice`, + youMustSayAboutFreeLegalAdviceErrorMessage: `You must say if you've had any free legal advice`, thereIsAProblemErrorMessageHeader: `There is a problem`, feedbackLink: `feedback (opens in new tab)`, pageSlug: `free-legal-advice`, diff --git a/src/test/ui/data/page-data/startNow.page.data.ts b/src/test/ui/data/page-data/startNow.page.data.ts index d76ed5123..c28f6411c 100644 --- a/src/test/ui/data/page-data/startNow.page.data.ts +++ b/src/test/ui/data/page-data/startNow.page.data.ts @@ -1,5 +1,5 @@ export const startNow = { - mainHeader: `Respond to a incorrect property possession claim online`, + mainHeader: `Respond to a property possession claim online`, respondWithinParagraph: `There’s no fee to respond to a claim. You must respond within 14 days of receiving the claim pack in the post.`, thisServiceIsAlsoAvailableParagraph: `This service is also available `, inWelshLink: `in Welsh (Cymraeg)`, From d70ba739aabc7ab38398dcbf8da35110c674e2e5 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 19:57:18 +0100 Subject: [PATCH 05/12] Added screenshots to allure report in case of validation issues and intentionally failing tests on free legal page --- playwright.config.ts | 4 +-- src/test/ui/e2eTest/dashboard.spec.ts | 2 ++ src/test/ui/e2eTest/pageNotFound.spec.ts | 2 ++ src/test/ui/e2eTest/respondToAClaim.spec.ts | 2 ++ .../ui/e2eTest/respondToClaimWales.spec.ts | 2 ++ .../functional/correspondenceAddress.spec.ts | 2 ++ src/test/ui/functional/freeLegalAdvice.pft.ts | 4 +-- src/test/ui/test-README.md | 1 + src/test/ui/utils/common/pft-debug-log.ts | 31 +++++++++++++++++++ 9 files changed, 45 insertions(+), 5 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 931b7aef6..63cb90f70 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -19,9 +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'; - -/** `ENABLE_PFT_DEBUG_LOG`: optional `[PFT check: …]` console lines (expected/actual, URL, test title). Use exactly `true` or `false`. Does not control failure PNG attachments. */ -export const enable_pft_debug_log = process.env.ENABLE_PFT_DEBUG_LOG || 'false'; +export const enable_pft_debug_log = process.env.ENABLE_PFT_DEBUG_LOG || 'true'; export default defineConfig({ testDir: './src/test/ui', diff --git a/src/test/ui/e2eTest/dashboard.spec.ts b/src/test/ui/e2eTest/dashboard.spec.ts index 82cbf9402..7a2fa70e1 100644 --- a/src/test/ui/e2eTest/dashboard.spec.ts +++ b/src/test/ui/e2eTest/dashboard.spec.ts @@ -3,6 +3,7 @@ import config from 'config'; import { createCaseApiData, submitCaseApiData } from '../data/api-data'; import { dashboard } from '../data/page-data'; +import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; import { initializeExecutor, performAction, performActions, performValidation } from '../utils/controller'; const home_url = config.get('e2e.testUrl') as string; @@ -20,6 +21,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 () => { diff --git a/src/test/ui/e2eTest/pageNotFound.spec.ts b/src/test/ui/e2eTest/pageNotFound.spec.ts index c7ceb836f..cc3c59020 100644 --- a/src/test/ui/e2eTest/pageNotFound.spec.ts +++ b/src/test/ui/e2eTest/pageNotFound.spec.ts @@ -2,6 +2,7 @@ import { test } from '@playwright/test'; import config from 'config'; import { createCaseApiData, submitCaseApiData } from '../data/api-data'; +import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; import { finaliseAllValidations, initializeExecutor, performAction, performValidation } from '../utils/controller'; const home_url = config.get('e2e.testUrl') as string; @@ -11,6 +12,7 @@ test.beforeEach(async ({ page }) => { await performAction('navigateToUrl', home_url); await performAction('createUser', 'citizen', ['citizen']); await performAction('login'); + logTestBeforeEachContext(); }); test.afterEach(async () => { diff --git a/src/test/ui/e2eTest/respondToAClaim.spec.ts b/src/test/ui/e2eTest/respondToAClaim.spec.ts index 07211b96c..218254ebe 100644 --- a/src/test/ui/e2eTest/respondToAClaim.spec.ts +++ b/src/test/ui/e2eTest/respondToAClaim.spec.ts @@ -21,6 +21,7 @@ import { tenancyDateDetails, tenancyTypeDetails, } from '../data/page-data'; +import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; import { finaliseAllValidations, initializeExecutor, performAction, performValidation } from '../utils/controller'; const home_url = config.get('e2e.testUrl') as string; @@ -93,6 +94,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 () => { diff --git a/src/test/ui/e2eTest/respondToClaimWales.spec.ts b/src/test/ui/e2eTest/respondToClaimWales.spec.ts index 3342e9b61..5f8e7456d 100644 --- a/src/test/ui/e2eTest/respondToClaimWales.spec.ts +++ b/src/test/ui/e2eTest/respondToClaimWales.spec.ts @@ -17,6 +17,7 @@ import { tenancyTypeDetails, writtenTerms, } from '../data/page-data'; +import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; import { finaliseAllValidations, initializeExecutor, performAction, performValidation } from '../utils/controller'; const home_url = config.get('e2e.testUrl') as string; @@ -34,6 +35,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 () => { diff --git a/src/test/ui/functional/correspondenceAddress.spec.ts b/src/test/ui/functional/correspondenceAddress.spec.ts index 6b84d8b74..b278592da 100644 --- a/src/test/ui/functional/correspondenceAddress.spec.ts +++ b/src/test/ui/functional/correspondenceAddress.spec.ts @@ -10,6 +10,7 @@ import { freeLegalAdvice, startNow, } from '../data/page-data'; +import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; import { initializeExecutor, performAction, performValidation } from '../utils/controller'; const home_url = config.get('e2e.testUrl') as string; @@ -38,6 +39,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 diff --git a/src/test/ui/functional/freeLegalAdvice.pft.ts b/src/test/ui/functional/freeLegalAdvice.pft.ts index 4df11f34d..75a90aefe 100644 --- a/src/test/ui/functional/freeLegalAdvice.pft.ts +++ b/src/test/ui/functional/freeLegalAdvice.pft.ts @@ -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 { @@ -14,7 +14,7 @@ export async function freeLegalAdviceNavigationTests(): Promise { 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); } diff --git a/src/test/ui/test-README.md b/src/test/ui/test-README.md index 6ef5cf7d7..f9f215e5a 100644 --- a/src/test/ui/test-README.md +++ b/src/test/ui/test-README.md @@ -72,6 +72,7 @@ When debugging **page navigation**, **error message**, or **page content** flows - 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. +- **`logTestBeforeEachContext()`** (end of each UI spec `test.beforeEach`): when `ENABLE_PFT_DEBUG_LOG=true`, prints one block with the test title and **only** case/journey env keys that are **set** at that moment (`CLAIMANT_NAME`, `NOTICE_SERVED`, `TENANCY_TYPE`, `CASE_NUMBER`, …). Unset keys are omitted. `playwright.config.ts` exports **`enable_pft_debug_log`** alongside other `ENABLE_*` flags. diff --git a/src/test/ui/utils/common/pft-debug-log.ts b/src/test/ui/utils/common/pft-debug-log.ts index e1a1c6aa3..dbfd86ae0 100644 --- a/src/test/ui/utils/common/pft-debug-log.ts +++ b/src/test/ui/utils/common/pft-debug-log.ts @@ -12,6 +12,37 @@ import { enable_pft_debug_log } from '../../../../../playwright.config'; * not gated by `ENABLE_PFT_DEBUG_LOG` (Allure/HTML report pick up attachments from test results). */ +/** Env keys that UI spec `beforeEach` hooks assign (case / journey). Only these are considered for logging. */ +const BEFORE_EACH_ASSIGNED_ENV_KEYS: readonly string[] = [ + 'CLAIMANT_NAME', + 'NOTICE_SERVED', + 'TENANCY_TYPE', + 'GROUNDS', + 'CLAIMANT_NAME_OVERRIDDEN', + 'CORRESPONDENCE_ADDRESS', + 'WALES_POSTCODE', + 'CASE_NUMBER', +]; + +/** + * Call at the **end** of `test.beforeEach` (after `process.env` / case creation is done). + * Prints one block only when `ENABLE_PFT_DEBUG_LOG=true`. + * Logs **only** keys above that are **currently set** in `process.env` (skips unset keys). + */ +export function logTestBeforeEachContext(): void { + if (enable_pft_debug_log !== 'true') { + return; + } + const { title } = test.info(); + const lines = BEFORE_EACH_ASSIGNED_ENV_KEYS.filter(key => { + const v = process.env[key]; + return v !== undefined && v !== ''; + }).map(key => ` ${key}=${process.env[key]}`); + const body = + lines.length > 0 ? lines.join('\n') : ' (none of the tracked beforeEach env keys are set at this point)'; + console.log(['[PFT debug: beforeEach context]', ` test: ${truncateForLog(title, 200)}`, body].join('\n')); +} + 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)}…`; From 02d5c178927b92af58e770e03eb2997f562ba285 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 20:01:21 +0100 Subject: [PATCH 06/12] Added few failures --- .../ui/data/page-data/disputeClaimInterstitial.page.data.ts | 2 +- src/test/ui/data/page-data/landlordRegistered.page.data.ts | 2 +- src/test/ui/functional/landlordRegistered.pft.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/ui/data/page-data/disputeClaimInterstitial.page.data.ts b/src/test/ui/data/page-data/disputeClaimInterstitial.page.data.ts index 45098e782..556856643 100644 --- a/src/test/ui/data/page-data/disputeClaimInterstitial.page.data.ts +++ b/src/test/ui/data/page-data/disputeClaimInterstitial.page.data.ts @@ -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 => { diff --git a/src/test/ui/data/page-data/landlordRegistered.page.data.ts b/src/test/ui/data/page-data/landlordRegistered.page.data.ts index 6627e5831..5eea2c58c 100644 --- a/src/test/ui/data/page-data/landlordRegistered.page.data.ts +++ b/src/test/ui/data/page-data/landlordRegistered.page.data.ts @@ -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`, }; diff --git a/src/test/ui/functional/landlordRegistered.pft.ts b/src/test/ui/functional/landlordRegistered.pft.ts index 8d6f177b8..270b8a667 100644 --- a/src/test/ui/functional/landlordRegistered.pft.ts +++ b/src/test/ui/functional/landlordRegistered.pft.ts @@ -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 { @@ -24,5 +24,5 @@ export async function landlordRegisteredNavigationTests(): Promise { // --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); } From 159587e242cb7ff460a97e7dd3920dc605c87899 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 22:58:11 +0100 Subject: [PATCH 07/12] Added few failures --- src/test/ui/e2eTest/dashboard.spec.ts | 3 +- src/test/ui/e2eTest/pageNotFound.spec.ts | 3 +- src/test/ui/e2eTest/respondToAClaim.spec.ts | 3 +- .../ui/e2eTest/respondToClaimWales.spec.ts | 3 +- .../functional/correspondenceAddress.spec.ts | 3 +- src/test/ui/test-README.md | 34 +++---- .../triggerPageFunctionalTests.action.ts | 32 ++----- src/test/ui/utils/common/pft-debug-log.ts | 90 +++++++++++++------ .../pageContent.validation.ts | 74 ++++++++------- 9 files changed, 136 insertions(+), 109 deletions(-) diff --git a/src/test/ui/e2eTest/dashboard.spec.ts b/src/test/ui/e2eTest/dashboard.spec.ts index 7a2fa70e1..11daa4f40 100644 --- a/src/test/ui/e2eTest/dashboard.spec.ts +++ b/src/test/ui/e2eTest/dashboard.spec.ts @@ -3,12 +3,13 @@ import config from 'config'; import { createCaseApiData, submitCaseApiData } from '../data/api-data'; import { dashboard } from '../data/page-data'; -import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; +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'; diff --git a/src/test/ui/e2eTest/pageNotFound.spec.ts b/src/test/ui/e2eTest/pageNotFound.spec.ts index cc3c59020..8d5dd6daa 100644 --- a/src/test/ui/e2eTest/pageNotFound.spec.ts +++ b/src/test/ui/e2eTest/pageNotFound.spec.ts @@ -2,12 +2,13 @@ import { test } from '@playwright/test'; import config from 'config'; import { createCaseApiData, submitCaseApiData } from '../data/api-data'; -import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; +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']); diff --git a/src/test/ui/e2eTest/respondToAClaim.spec.ts b/src/test/ui/e2eTest/respondToAClaim.spec.ts index 218254ebe..329fc74bb 100644 --- a/src/test/ui/e2eTest/respondToAClaim.spec.ts +++ b/src/test/ui/e2eTest/respondToAClaim.spec.ts @@ -21,12 +21,13 @@ import { tenancyDateDetails, tenancyTypeDetails, } from '../data/page-data'; -import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; +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')) { diff --git a/src/test/ui/e2eTest/respondToClaimWales.spec.ts b/src/test/ui/e2eTest/respondToClaimWales.spec.ts index 5f8e7456d..486c4ebf1 100644 --- a/src/test/ui/e2eTest/respondToClaimWales.spec.ts +++ b/src/test/ui/e2eTest/respondToClaimWales.spec.ts @@ -17,12 +17,13 @@ import { tenancyTypeDetails, writtenTerms, } from '../data/page-data'; -import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; +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; diff --git a/src/test/ui/functional/correspondenceAddress.spec.ts b/src/test/ui/functional/correspondenceAddress.spec.ts index b278592da..dec1d55da 100644 --- a/src/test/ui/functional/correspondenceAddress.spec.ts +++ b/src/test/ui/functional/correspondenceAddress.spec.ts @@ -10,12 +10,13 @@ import { freeLegalAdvice, startNow, } from '../data/page-data'; -import { logTestBeforeEachContext } from '../utils/common/pft-debug-log'; +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'; diff --git a/src/test/ui/test-README.md b/src/test/ui/test-README.md index f9f215e5a..d6a0e1629 100644 --- a/src/test/ui/test-README.md +++ b/src/test/ui/test-README.md @@ -30,7 +30,8 @@ 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) +│ ├── common/ +│ │ └── pft-debug-log.ts # PFT debug logging, failure screenshots (see §3) │ └── controller.ts # Controls the usage of actions and validations ├── testREADME.md # Framework documentation └── update-testReadMe.ts # Documentation auto-update script @@ -64,17 +65,19 @@ The framework's modular design consists of these key layers: Playwright 1.30+ | TypeScript 4.9+ ``` -### PFT debug logging (optional) +### PFT debug logging (`ENABLE_PFT_DEBUG_LOG`) -When debugging **page navigation**, **error message**, or **page content** flows, enable structured console lines prefixed with `[PFT check: …]`: +Implementation: **`utils/common/pft-debug-log.ts`**. `playwright.config.ts` exports **`enable_pft_debug_log`** (reads `process.env.ENABLE_PFT_DEBUG_LOG`; must be exactly **`true`** or **`false`** when set). If the variable is **unset**, the config defaults to **`true`** (see `playwright.config.ts`). Set **`ENABLE_PFT_DEBUG_LOG=false`** to silence optional console output. -- 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. -- **`logTestBeforeEachContext()`** (end of each UI spec `test.beforeEach`): when `ENABLE_PFT_DEBUG_LOG=true`, prints one block with the test title and **only** case/journey env keys that are **set** at that moment (`CLAIMANT_NAME`, `NOTICE_SERVED`, `TENANCY_TYPE`, `CASE_NUMBER`, …). Unset keys are omitted. +**Console output (flag-gated — only when `enable_pft_debug_log === 'true'`):** -`playwright.config.ts` exports **`enable_pft_debug_log`** alongside other `ENABLE_*` flags. +- **`pftDebugLog(...)`** — used for end-of-test **summaries** (page content, error-message, and navigation `finaliseTest()` output) and the **`[triggerFunctionalTests] entered …`** line in `triggerPageFunctionalTests`. +- **`pftDebugReport` / `[PFT check: …]`** — structured lines from `reportValidationFailure` and unmapped-URL handling in the trigger: **test title**, **page label**, **URL**, **expected**, **actual** (long strings truncated). +- **`logTestBeforeEachContext()`** — requires **`captureProcessEnvBeforeBeforeEach()`** as the **first** line of the same `test.beforeEach`. Prints one block with the test title and every **non-empty** `process.env` key whose value **changed** during that `beforeEach` (diff from the snapshot at the start of the hook). + +**Failure screenshots (not gated by the flag):** + +- Full-page PNGs via **`test.info().attach`** (HTML / Allure) when a validation attaches on failure — e.g. **`attachValidationFailureScreenshot`**, **`reportValidationFailure(..., attachScreenshot: true)`** (page content mismatch, load/parse errors, error-message failures, navigation failures). If screenshot capture fails, a **`console.warn`** may still appear so the problem is visible even when debug logging is off. ## 4. Actions and Validations @@ -176,15 +179,16 @@ Smart Mapping: Automatically maps URLs to page data files, including numeric URL Comprehensive: Validates buttons, headers, links, paragraphs, and other UI elements -Validation Summary - -After each test, you'll see a detailed report in the respective test stdout: +Validation summary (stdout) - + +The emoji-style **PAGE CONTENT / ERROR MESSAGE / NAVIGATION** summary blocks are emitted via **`pftDebugLog`** and only appear when **`ENABLE_PFT_DEBUG_LOG=true`**. Failures still **throw** and **failure screenshots** still attach to the report when validations fail, independent of that flag. + +Example (when debug logging is enabled): ``` 📊 PAGE CONTENT VALIDATION SUMMARY (Test #1): -Total pages validated: 3 -Pages passed: 2 -Pages failed: 1 -Missing elements: Submit button, Continue link + Total pages validated: 3 + ... ``` ## 9. Functional test automation for pcs-frontend diff --git a/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts b/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts index 1d7eb06eb..cfe270ef7 100644 --- a/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts +++ b/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts @@ -7,9 +7,8 @@ import { enable_content_validation, enable_error_message_validation, enable_navigation_tests, - enable_pft_debug_log, } from '../../../../../../playwright.config'; -import { pftDebugReport, shortUrl } from '../../common/pft-debug-log'; +import { pftDebugLog, pftDebugReport, shortUrl } from '../../common/pft-debug-log'; import { IAction } from '../../interfaces'; import { ErrorMessageValidation, @@ -36,19 +35,15 @@ export class TriggerPageFunctionalTestsAction implements IAction { private async triggerPageFunctionalTests(page: Page): Promise { const pageName = await this.getFileNameForPage(page); - if (enable_pft_debug_log === 'true') { - console.log(`[triggerFunctionalTests] entered url=${shortUrl(page.url())} page=${pageName ?? '(unmapped)'}`); - } + pftDebugLog(`[triggerFunctionalTests] entered url=${shortUrl(page.url())} page=${pageName ?? '(unmapped)'}`); if (!pageName) { - if (enable_content_validation === 'true') { - pftDebugReport({ - page, - pageLabel: shortUrl(page.url()), - category: 'page content', - expected: 'URL segment or page heading maps to a page name in urlToFileMapping.config.ts', - actual: 'Could not resolve page from URL mapping', - }); - } + pftDebugReport({ + page, + pageLabel: shortUrl(page.url()), + category: 'page functional tests', + expected: 'A matching key in urlToFileMapping.config.ts', + actual: 'No matching key — PFT skipped', + }); return; } @@ -70,15 +65,6 @@ export class TriggerPageFunctionalTestsAction implements IAction { await this.runPageContentValidation(page, pageName); } else { PageContentValidation.trackMissingDataFile(pageName); - pftDebugReport({ - page, - pageLabel: pageName, - category: 'page content', - expected: `Page data file ${pageName}.page.data.ts exists under data/page-data/`, - actual: - `URL maps to "${pageName}" in urlToFileMapping.config.ts, but there is no matching page data file. ` + - `Add src/test/ui/data/page-data/${pageName}.page.data.ts (page content / design source for validation).`, - }); } } diff --git a/src/test/ui/utils/common/pft-debug-log.ts b/src/test/ui/utils/common/pft-debug-log.ts index dbfd86ae0..3d501b38d 100644 --- a/src/test/ui/utils/common/pft-debug-log.ts +++ b/src/test/ui/utils/common/pft-debug-log.ts @@ -6,41 +6,72 @@ import { test } from '@playwright/test'; import { enable_pft_debug_log } from '../../../../../playwright.config'; +/** Summary / diagnostic `console.log` lines — only when `ENABLE_PFT_DEBUG_LOG=true`. */ +export function pftDebugLog(...args: unknown[]): void { + if (enable_pft_debug_log !== 'true') { + return; + } + console.log(...args); +} + /** - * Console: `[PFT check: …]` lines only when `ENABLE_PFT_DEBUG_LOG=true` (`enable_pft_debug_log`). - * Failure PNGs: `test.info().attach` whenever `reportValidationFailure(..., attachScreenshot: true)` runs — - * not gated by `ENABLE_PFT_DEBUG_LOG` (Allure/HTML report pick up attachments from test results). + * Console output: + * - **`pftDebugLog` / `logTestBeforeEachContext`**: only when `ENABLE_PFT_DEBUG_LOG=true`. + * - **`pftDebugReport`** (`[PFT check: …]`): only when `ENABLE_PFT_DEBUG_LOG=true`. + * Failure PNGs: `test.info().attach` from `attachValidationFailureScreenshot` and from + * `reportValidationFailure(..., attachScreenshot: true)` — **not** gated by the flag + * (Allure / HTML report attachments). */ -/** Env keys that UI spec `beforeEach` hooks assign (case / journey). Only these are considered for logging. */ -const BEFORE_EACH_ASSIGNED_ENV_KEYS: readonly string[] = [ - 'CLAIMANT_NAME', - 'NOTICE_SERVED', - 'TENANCY_TYPE', - 'GROUNDS', - 'CLAIMANT_NAME_OVERRIDDEN', - 'CORRESPONDENCE_ADDRESS', - 'WALES_POSTCODE', - 'CASE_NUMBER', -]; +/** + * Shallow snapshot of `process.env` at the **start** of `test.beforeEach` (before any assignments). + * Used by `logTestBeforeEachContext()` to log only keys **added or changed** during that hook. + */ +let envSnapshotBeforeBeforeEach: NodeJS.ProcessEnv | null = null; + +/** + * Call as the **first** line of `test.beforeEach` (before `initializeExecutor` / any `process.env` writes). + * Pairs with `logTestBeforeEachContext()` at the end of the same hook. + */ +export function captureProcessEnvBeforeBeforeEach(): void { + envSnapshotBeforeBeforeEach = { ...process.env }; +} + +function envKeysChangedDuringBeforeEach(): string[] { + const before = envSnapshotBeforeBeforeEach; + if (!before) { + return []; + } + const keys: string[] = []; + for (const k of Object.keys(process.env)) { + const v = process.env[k]; + if (v === undefined || v === '') { + continue; + } + if (before[k] !== v) { + keys.push(k); + } + } + return keys.sort(); +} /** * Call at the **end** of `test.beforeEach` (after `process.env` / case creation is done). * Prints one block only when `ENABLE_PFT_DEBUG_LOG=true`. - * Logs **only** keys above that are **currently set** in `process.env` (skips unset keys). + * Logs every **non-empty** `process.env` key whose value **differs** from the snapshot taken by + * `captureProcessEnvBeforeBeforeEach()` at the start of this `beforeEach`. */ export function logTestBeforeEachContext(): void { - if (enable_pft_debug_log !== 'true') { - return; - } const { title } = test.info(); - const lines = BEFORE_EACH_ASSIGNED_ENV_KEYS.filter(key => { - const v = process.env[key]; - return v !== undefined && v !== ''; - }).map(key => ` ${key}=${process.env[key]}`); + const changedKeys = envKeysChangedDuringBeforeEach(); + const lines = changedKeys.map(key => ` ${key}=${process.env[key]}`); const body = - lines.length > 0 ? lines.join('\n') : ' (none of the tracked beforeEach env keys are set at this point)'; - console.log(['[PFT debug: beforeEach context]', ` test: ${truncateForLog(title, 200)}`, body].join('\n')); + lines.length > 0 + ? lines.join('\n') + : envSnapshotBeforeBeforeEach + ? ' (no process.env keys were added or changed during this beforeEach)' + : ' (call captureProcessEnvBeforeBeforeEach() at the start of beforeEach to log env changes)'; + pftDebugLog(['[PFT debug: beforeEach context]', ` test: ${truncateForLog(title, 200)}`, body].join('\n')); } function truncate(s: string, max: number, trim?: boolean): string { @@ -59,12 +90,15 @@ const validationLabel: Record = { 'page-navigation': 'page navigation', }; -async function attachValidationFailureScreenshot( +/** + * Full-page screenshot attached to the test report (Allure / HTML). Not gated by `ENABLE_PFT_DEBUG_LOG`. + */ +export async function attachValidationFailureScreenshot( page: Page, category: ValidationFailureCategory, - pageLabel: string, - debugLabel: string + pageLabel: string ): Promise { + const debugLabel = validationLabel[category]; try { const safe = (pageLabel.trim() || 'page').slice(0, 80); const fileName = `failure-${category}-${safe}-${Date.now()}.png`; @@ -93,7 +127,7 @@ export async function reportValidationFailure( ): Promise { const label = validationLabel[category]; if (attachScreenshot) { - await attachValidationFailureScreenshot(page, category, pageLabel, label); + await attachValidationFailureScreenshot(page, category, pageLabel); } pftDebugReport({ page, pageLabel, category: label, expected, actual }); } diff --git a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts index 508b34198..4c85d6848 100644 --- a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts @@ -3,7 +3,11 @@ import * as path from 'path'; import { Page } from '@playwright/test'; -import { reportValidationFailure, truncateForLog } from '../../common/pft-debug-log'; +import { + attachValidationFailureScreenshot, + pftDebugLog, + reportValidationFailure, +} from '../../common/pft-debug-log'; import { escapeForRegex, exactTextWithOptionalWhitespaceRegex } from '../../common/string.utils'; import { IValidation } from '../../interfaces'; @@ -140,14 +144,19 @@ export class PageContentValidation implements IValidation { const pageData = await this.getPageData(pageName); if (!pageData) { - await reportValidationFailure( - page, - 'page-content', - pageName, - `Page data file exists for "${pageName}" (data/page-data/${pageName}.page.data.ts)`, - 'No page data file found', - true - ); + // Missing file is already handled in triggerPageFunctionalTests (trackMissingDataFile; no runPageContentValidation). + // Here we only surface load/parse/export failures when the file exists. + const pageDataPath = path.join(__dirname, '../../../data/page-data', `${pageName}.page.data.ts`); + if (fs.existsSync(pageDataPath)) { + await reportValidationFailure( + page, + 'page-content', + pageName, + `Page data file should export default or a named export (data/page-data/${pageName}.page.data.ts)`, + 'Failed to load or parse the file (syntax error or missing export)', + true + ); + } return; } @@ -172,20 +181,9 @@ export class PageContentValidation implements IValidation { PageContentValidation.validationResults.set(pageUrl, pageResults); - const failed = pageResults.filter(r => r.status === 'fail'); - const total = pageResults.length; - const expectedSummary = - total === 0 - ? `No content fields to check for "${pageName}"` - : `All ${total} content field(s) from page data should be visible on "${pageName}"`; - const actualSummary = - total === 0 - ? 'Nothing to validate' - : failed.length === 0 - ? `All ${total} matched (visible)` - : `${failed.length} not visible: ${failed.map(f => `${f.element} → "${truncateForLog(f.expected, 120)}"`).join('; ')}`; - - await reportValidationFailure(page, 'page-content', pageName, expectedSummary, actualSummary, failed.length > 0); + if (pageResults.some(r => r.status === 'fail')) { + await attachValidationFailureScreenshot(page, 'page-content', pageName); + } } private async getPageData(pageName: string): Promise { @@ -289,40 +287,40 @@ export class PageContentValidation implements IValidation { const failedCount = failedPages.size; const missingFilesCount = PageContentValidation.missingDataFiles.size; - console.log(`\n📊 PAGE CONTENT VALIDATION SUMMARY (Test #${PageContentValidation.testCounter}):`); - console.log(` Total pages validated: ${totalValidated}`); - console.log(` Number of pages passed: ${passedCount}`); - console.log(` Number of pages failed: ${failedCount}`); - console.log(` Missing data files: ${missingFilesCount}`); + pftDebugLog(`\n📊 PAGE CONTENT VALIDATION SUMMARY (Test #${PageContentValidation.testCounter}):`); + pftDebugLog(` Total pages validated: ${totalValidated}`); + pftDebugLog(` Number of pages passed: ${passedCount}`); + pftDebugLog(` Number of pages failed: ${failedCount}`); + pftDebugLog(` Missing data files: ${missingFilesCount}`); if (passedCount > 0) { - console.log(` Passed pages: ${Array.from(passedPages).join(', ')}`); + pftDebugLog(` Passed pages: ${Array.from(passedPages).join(', ')}`); } if (failedCount > 0) { - console.log(` Failed pages: ${Array.from(failedPages.keys()).join(', ')}`); + pftDebugLog(` Failed pages: ${Array.from(failedPages.keys()).join(', ')}`); } if (missingFilesCount > 0) { - console.log(` Page files not found: ${Array.from(PageContentValidation.missingDataFiles).join(', ')}`); + pftDebugLog(` Page files not found: ${Array.from(PageContentValidation.missingDataFiles).join(', ')}`); } if (failedPages.size > 0) { - console.log('\n❌ FAILED PAGE CONTENT VALIDATION:'); + pftDebugLog('\n❌ FAILED PAGE CONTENT VALIDATION:'); for (const [pageName, pageFailures] of failedPages) { - console.log(` Page: ${pageName}`); + pftDebugLog(` Page: ${pageName}`); let pageFailureCount = 0; for (const [elementType, elements] of pageFailures) { pageFailureCount += elements.length; - console.log(` ${elementType} elements (${elements.length}):`); - elements.forEach(element => console.log(` - ${element}`)); + pftDebugLog(` ${elementType} elements (${elements.length}):`); + elements.forEach(element => pftDebugLog(` - ${element}`)); } - console.log(` Total missing on this page: ${pageFailureCount}\n`); + pftDebugLog(` Total missing on this page: ${pageFailureCount}\n`); } } else if (totalValidated > 0) { - console.log('\n✅ VALIDATION PASSED: All intended pages validated successfully!\n'); + pftDebugLog('\n✅ VALIDATION PASSED: All intended pages validated successfully!\n'); } else if (missingFilesCount > 0) { - console.log('\n⚠️ NO VALIDATION: Missing data files for all pages\n'); + pftDebugLog('\n⚠️ NO VALIDATION: Missing data files for all pages\n'); } PageContentValidation.clearValidationResults(); From 7b3a6b49378953205fc541c539ff115979630f10 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 22:59:41 +0100 Subject: [PATCH 08/12] Added few failures --- src/test/ui/test-README.md | 2 +- .../error-message.validation.ts | 38 ++++++++-------- .../pageContent.validation.ts | 6 +-- .../pageNavigation.validation.ts | 44 +++++++++---------- 4 files changed, 43 insertions(+), 47 deletions(-) diff --git a/src/test/ui/test-README.md b/src/test/ui/test-README.md index d6a0e1629..073b8d34c 100644 --- a/src/test/ui/test-README.md +++ b/src/test/ui/test-README.md @@ -31,7 +31,7 @@ ui/ │ │ ├── action.registry.ts # Action registry │ │ └── validation.registry.ts # Validation registry │ ├── common/ -│ │ └── pft-debug-log.ts # PFT debug logging, failure screenshots (see §3) +│ │ └── pft-debug-log.ts # PFT debug logging, failure screenshots (see section 3) │ └── controller.ts # Controls the usage of actions and validations ├── testREADME.md # Framework documentation └── update-testReadMe.ts # Documentation auto-update script diff --git a/src/test/ui/utils/validations/custom-validations/error-message.validation.ts b/src/test/ui/utils/validations/custom-validations/error-message.validation.ts index e651aa9ee..ab8f065ac 100644 --- a/src/test/ui/utils/validations/custom-validations/error-message.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/error-message.validation.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { Page } from '@playwright/test'; -import { reportValidationFailure } from '../../common/pft-debug-log'; +import { pftDebugLog, reportValidationFailure } from '../../common/pft-debug-log'; import { IValidation, validationData, validationRecord } from '../../interfaces'; type ValidationResult = { @@ -218,8 +218,8 @@ export class ErrorMessageValidation implements IValidation { const totalPages = ErrorMessageValidation.pagesWithEMV.size + ErrorMessageValidation.missingEMVFiles.size; if (totalPages === 0) { - console.log(`\n📊 ERROR MESSAGE VALIDATION (Test #${ErrorMessageValidation.testCounter}):`); - console.log(' No pages checked for error message validation'); + pftDebugLog(`\n📊 ERROR MESSAGE VALIDATION (Test #${ErrorMessageValidation.testCounter}):`); + pftDebugLog(' No pages checked for error message validation'); return; } @@ -247,45 +247,45 @@ export class ErrorMessageValidation implements IValidation { passedPages.delete(pageName); } - console.log(`\n📊 ERROR MESSAGE VALIDATION SUMMARY (Test #${ErrorMessageValidation.testCounter}):`); - console.log(` Total pages validated for error messages: ${totalPages}`); - console.log(` Number of pages passed: ${passedPages.size}`); - console.log(` Number of pages failed: ${failedPages.size}`); - console.log(` Number of missing EMV methods: ${ErrorMessageValidation.missingEMVFiles.size}`); + pftDebugLog(`\n📊 ERROR MESSAGE VALIDATION SUMMARY (Test #${ErrorMessageValidation.testCounter}):`); + pftDebugLog(` Total pages validated for error messages: ${totalPages}`); + pftDebugLog(` Number of pages passed: ${passedPages.size}`); + pftDebugLog(` Number of pages failed: ${failedPages.size}`); + pftDebugLog(` Number of missing EMV methods: ${ErrorMessageValidation.missingEMVFiles.size}`); if (passedPages.size > 0) { - console.log(` Passed pages: ${Array.from(passedPages).join(', ')}`); + pftDebugLog(` Passed pages: ${Array.from(passedPages).join(', ')}`); } if (failedPages.size > 0) { - console.log(` Failed pages: ${Array.from(failedPages).join(', ')}`); + pftDebugLog(` Failed pages: ${Array.from(failedPages).join(', ')}`); } if (ErrorMessageValidation.missingEMVFiles.size > 0) { - console.log(` EMV methods not found: ${Array.from(ErrorMessageValidation.missingEMVFiles).join(', ')}`); + pftDebugLog(` EMV methods not found: ${Array.from(ErrorMessageValidation.missingEMVFiles).join(', ')}`); } // Show failure details if (failedPages.size > 0) { - console.log('\n❌ FAILED ERROR MESSAGE VALIDATIONS:'); + pftDebugLog('\n❌ FAILED ERROR MESSAGE VALIDATIONS:'); for (const pageName of Array.from(failedPages).sort()) { const details = failureDetails.get(pageName); - console.log(` Page: ${pageName}`); + pftDebugLog(` Page: ${pageName}`); if (details) { - console.log(` Expected: ${details.expected}`); - console.log(` Actual: ${details.actual}`); + pftDebugLog(` Expected: ${details.expected}`); + pftDebugLog(` Actual: ${details.actual}`); } - console.log(''); + pftDebugLog(''); } } if (failedPages.size > 0) { - console.log('❌ ERROR MESSAGE VALIDATIONS FAILED\n'); + pftDebugLog('❌ ERROR MESSAGE VALIDATIONS FAILED\n'); } else if (passedPages.size > 0) { - console.log('\n✅ ALL ERROR MESSAGE VALIDATIONS PASSED\n'); + pftDebugLog('\n✅ ALL ERROR MESSAGE VALIDATIONS PASSED\n'); } else if (ErrorMessageValidation.pagesWithEMV.size > 0) { - console.log('\n⚠️ EMV methods found but no validations performed\n'); + pftDebugLog('\n⚠️ EMV methods found but no validations performed\n'); } ErrorMessageValidation.clearResults(); diff --git a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts index 4c85d6848..f7f499700 100644 --- a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts @@ -3,11 +3,7 @@ import * as path from 'path'; import { Page } from '@playwright/test'; -import { - attachValidationFailureScreenshot, - pftDebugLog, - reportValidationFailure, -} from '../../common/pft-debug-log'; +import { attachValidationFailureScreenshot, pftDebugLog, reportValidationFailure } from '../../common/pft-debug-log'; import { escapeForRegex, exactTextWithOptionalWhitespaceRegex } from '../../common/string.utils'; import { IValidation } from '../../interfaces'; diff --git a/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts b/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts index d2de7984f..e931bb162 100644 --- a/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { Page, expect } from '@playwright/test'; -import { reportValidationFailure } from '../../common/pft-debug-log'; +import { pftDebugLog, reportValidationFailure } from '../../common/pft-debug-log'; import { performAction } from '../../controller'; import { IValidation, validationRecord } from '../../interfaces'; @@ -387,8 +387,8 @@ export class PageNavigationValidation implements IValidation { PageNavigationValidation.missingNavigationFiles.size; if (totalPages === 0) { - console.log(`\n📊 NAVIGATION TESTS (Test #${PageNavigationValidation.testCounter}):`); - console.log(' No pages checked for navigation tests'); + pftDebugLog(`\n📊 NAVIGATION TESTS (Test #${PageNavigationValidation.testCounter}):`); + pftDebugLog(' No pages checked for navigation tests'); return; } @@ -417,7 +417,7 @@ export class PageNavigationValidation implements IValidation { }); } } else { - console.log(` ⚠️ Unattributed failure on ${result.pageName}: ${result.error}`); + pftDebugLog(` ⚠️ Unattributed failure on ${result.pageName}: ${result.error}`); } } } @@ -439,57 +439,57 @@ export class PageNavigationValidation implements IValidation { } } - console.log(`\n📊 NAVIGATION TESTS SUMMARY (Test #${PageNavigationValidation.testCounter}):`); - console.log(` Total pages with navigation tests: ${totalPages}`); - console.log(` Number of pages passed: ${passedPages.size}`); - console.log(` Number of pages failed: ${failedPages.size}`); - console.log( + pftDebugLog(`\n📊 NAVIGATION TESTS SUMMARY (Test #${PageNavigationValidation.testCounter}):`); + pftDebugLog(` Total pages with navigation tests: ${totalPages}`); + pftDebugLog(` Number of pages passed: ${passedPages.size}`); + pftDebugLog(` Number of pages failed: ${failedPages.size}`); + pftDebugLog( ` Missing navigation methods: ${PageNavigationValidation.missingNavigationMethods.size + PageNavigationValidation.missingNavigationFiles.size}` ); if (passedPages.size > 0) { - console.log(` Passed pages: ${Array.from(passedPages).join(', ')}`); + pftDebugLog(` Passed pages: ${Array.from(passedPages).join(', ')}`); } if (failedPages.size > 0) { - console.log(` Failed pages: ${Array.from(failedPages).join(', ')}`); + pftDebugLog(` Failed pages: ${Array.from(failedPages).join(', ')}`); } if (PageNavigationValidation.missingNavigationMethods.size > 0) { - console.log( + pftDebugLog( ` Navigation methods not found: ${Array.from(PageNavigationValidation.missingNavigationMethods).join(', ')}` ); } if (PageNavigationValidation.missingNavigationFiles.size > 0) { - console.log( + pftDebugLog( ` Navigation files not found: ${Array.from(PageNavigationValidation.missingNavigationFiles).join(', ')}` ); } if (failedPages.size > 0) { - console.log('\n❌ FAILED NAVIGATION TESTS:'); + pftDebugLog('\n❌ FAILED NAVIGATION TESTS:'); for (const pageName of Array.from(failedPages).sort()) { const details = failureDetails.get(pageName); - console.log(` Page: ${pageName}`); + pftDebugLog(` Page: ${pageName}`); if (details) { - console.log(` Expected: ${details.expected}`); - console.log(` Actual: ${details.actual}`); + pftDebugLog(` Expected: ${details.expected}`); + pftDebugLog(` Actual: ${details.actual}`); if (details.validationType === 'url') { - console.log(` Note: Page slug URL validation failed`); + pftDebugLog(` Note: Page slug URL validation failed`); } } - console.log(''); + pftDebugLog(''); } } if (failedPages.size > 0) { - console.log('❌ NAVIGATION TESTS FAILED\n'); + pftDebugLog('❌ NAVIGATION TESTS FAILED\n'); } else if (passedPages.size > 0) { - console.log('\n✅ ALL NAVIGATION TESTS PASSED\n'); + pftDebugLog('\n✅ ALL NAVIGATION TESTS PASSED\n'); } else if (pagesWithNavigationMethods.size > 0) { - console.log('\n⚠️ Navigation files found but no tests performed\n'); + pftDebugLog('\n⚠️ Navigation files found but no tests performed\n'); } PageNavigationValidation.clearResults(); From 6b5d4c1491ef819ce5cdb5c99b360cc80e1d2238 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 23:04:00 +0100 Subject: [PATCH 09/12] Added few failures --- src/test/ui/test-README.md | 34 +++++++------- .../triggerPageFunctionalTests.action.ts | 7 ++- src/test/ui/utils/common/pft-debug-log.ts | 21 +++------ .../error-message.validation.ts | 38 ++++++++-------- .../pageContent.validation.ts | 32 +++++++------- .../pageNavigation.validation.ts | 44 +++++++++---------- 6 files changed, 84 insertions(+), 92 deletions(-) diff --git a/src/test/ui/test-README.md b/src/test/ui/test-README.md index 073b8d34c..f70b7b6d0 100644 --- a/src/test/ui/test-README.md +++ b/src/test/ui/test-README.md @@ -30,8 +30,7 @@ ui/ │ ├── registry/ # Component registration │ │ ├── action.registry.ts # Action registry │ │ └── validation.registry.ts # Validation registry -│ ├── common/ -│ │ └── pft-debug-log.ts # PFT debug logging, failure screenshots (see section 3) +│ ├── 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 @@ -65,19 +64,17 @@ The framework's modular design consists of these key layers: Playwright 1.30+ | TypeScript 4.9+ ``` -### PFT debug logging (`ENABLE_PFT_DEBUG_LOG`) +### PFT debug logging (optional) -Implementation: **`utils/common/pft-debug-log.ts`**. `playwright.config.ts` exports **`enable_pft_debug_log`** (reads `process.env.ENABLE_PFT_DEBUG_LOG`; must be exactly **`true`** or **`false`** when set). If the variable is **unset**, the config defaults to **`true`** (see `playwright.config.ts`). Set **`ENABLE_PFT_DEBUG_LOG=false`** to silence optional console output. +When debugging **page navigation**, **error message**, or **page content** flows, enable structured console lines prefixed with `[PFT check: …]`: -**Console output (flag-gated — only when `enable_pft_debug_log === 'true'`):** +- 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. -- **`pftDebugLog(...)`** — used for end-of-test **summaries** (page content, error-message, and navigation `finaliseTest()` output) and the **`[triggerFunctionalTests] entered …`** line in `triggerPageFunctionalTests`. -- **`pftDebugReport` / `[PFT check: …]`** — structured lines from `reportValidationFailure` and unmapped-URL handling in the trigger: **test title**, **page label**, **URL**, **expected**, **actual** (long strings truncated). -- **`logTestBeforeEachContext()`** — requires **`captureProcessEnvBeforeBeforeEach()`** as the **first** line of the same `test.beforeEach`. Prints one block with the test title and every **non-empty** `process.env` key whose value **changed** during that `beforeEach` (diff from the snapshot at the start of the hook). - -**Failure screenshots (not gated by the flag):** - -- Full-page PNGs via **`test.info().attach`** (HTML / Allure) when a validation attaches on failure — e.g. **`attachValidationFailureScreenshot`**, **`reportValidationFailure(..., attachScreenshot: true)`** (page content mismatch, load/parse errors, error-message failures, navigation failures). If screenshot capture fails, a **`console.warn`** may still appear so the problem is visible even when debug logging is off. +`playwright.config.ts` exports **`enable_pft_debug_log`** alongside other `ENABLE_*` flags. ## 4. Actions and Validations @@ -179,16 +176,15 @@ Smart Mapping: Automatically maps URLs to page data files, including numeric URL Comprehensive: Validates buttons, headers, links, paragraphs, and other UI elements -Validation summary (stdout) - - -The emoji-style **PAGE CONTENT / ERROR MESSAGE / NAVIGATION** summary blocks are emitted via **`pftDebugLog`** and only appear when **`ENABLE_PFT_DEBUG_LOG=true`**. Failures still **throw** and **failure screenshots** still attach to the report when validations fail, independent of that flag. - -Example (when debug logging is enabled): +Validation Summary - +After each test, you'll see a detailed report in the respective test stdout: ``` 📊 PAGE CONTENT VALIDATION SUMMARY (Test #1): - Total pages validated: 3 - ... +Total pages validated: 3 +Pages passed: 2 +Pages failed: 1 +Missing elements: Submit button, Continue link ``` ## 9. Functional test automation for pcs-frontend diff --git a/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts b/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts index cfe270ef7..190727681 100644 --- a/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts +++ b/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts @@ -7,8 +7,9 @@ import { enable_content_validation, enable_error_message_validation, enable_navigation_tests, + enable_pft_debug_log, } from '../../../../../../playwright.config'; -import { pftDebugLog, pftDebugReport, shortUrl } from '../../common/pft-debug-log'; +import { pftDebugReport, shortUrl } from '../../common/pft-debug-log'; import { IAction } from '../../interfaces'; import { ErrorMessageValidation, @@ -35,7 +36,9 @@ export class TriggerPageFunctionalTestsAction implements IAction { private async triggerPageFunctionalTests(page: Page): Promise { const pageName = await this.getFileNameForPage(page); - pftDebugLog(`[triggerFunctionalTests] entered url=${shortUrl(page.url())} page=${pageName ?? '(unmapped)'}`); + if (enable_pft_debug_log === 'true') { + console.log(`[triggerFunctionalTests] entered url=${shortUrl(page.url())} page=${pageName ?? '(unmapped)'}`); + } if (!pageName) { pftDebugReport({ page, diff --git a/src/test/ui/utils/common/pft-debug-log.ts b/src/test/ui/utils/common/pft-debug-log.ts index 3d501b38d..9707bd576 100644 --- a/src/test/ui/utils/common/pft-debug-log.ts +++ b/src/test/ui/utils/common/pft-debug-log.ts @@ -6,21 +6,11 @@ import { test } from '@playwright/test'; import { enable_pft_debug_log } from '../../../../../playwright.config'; -/** Summary / diagnostic `console.log` lines — only when `ENABLE_PFT_DEBUG_LOG=true`. */ -export function pftDebugLog(...args: unknown[]): void { - if (enable_pft_debug_log !== 'true') { - return; - } - console.log(...args); -} - /** - * Console output: - * - **`pftDebugLog` / `logTestBeforeEachContext`**: only when `ENABLE_PFT_DEBUG_LOG=true`. - * - **`pftDebugReport`** (`[PFT check: …]`): only when `ENABLE_PFT_DEBUG_LOG=true`. + * Console: `[PFT check: …]` lines only when `ENABLE_PFT_DEBUG_LOG=true` (`enable_pft_debug_log`). * Failure PNGs: `test.info().attach` from `attachValidationFailureScreenshot` and from - * `reportValidationFailure(..., attachScreenshot: true)` — **not** gated by the flag - * (Allure / HTML report attachments). + * `reportValidationFailure(..., attachScreenshot: true)` — not gated by `ENABLE_PFT_DEBUG_LOG` + * (Allure/HTML report pick up attachments from test results). */ /** @@ -62,6 +52,9 @@ function envKeysChangedDuringBeforeEach(): string[] { * `captureProcessEnvBeforeBeforeEach()` at the start of this `beforeEach`. */ export function logTestBeforeEachContext(): void { + if (enable_pft_debug_log !== 'true') { + return; + } const { title } = test.info(); const changedKeys = envKeysChangedDuringBeforeEach(); const lines = changedKeys.map(key => ` ${key}=${process.env[key]}`); @@ -71,7 +64,7 @@ export function logTestBeforeEachContext(): void { : envSnapshotBeforeBeforeEach ? ' (no process.env keys were added or changed during this beforeEach)' : ' (call captureProcessEnvBeforeBeforeEach() at the start of beforeEach to log env changes)'; - pftDebugLog(['[PFT debug: beforeEach context]', ` test: ${truncateForLog(title, 200)}`, body].join('\n')); + console.log(['[PFT debug: beforeEach context]', ` test: ${truncateForLog(title, 200)}`, body].join('\n')); } function truncate(s: string, max: number, trim?: boolean): string { diff --git a/src/test/ui/utils/validations/custom-validations/error-message.validation.ts b/src/test/ui/utils/validations/custom-validations/error-message.validation.ts index ab8f065ac..e651aa9ee 100644 --- a/src/test/ui/utils/validations/custom-validations/error-message.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/error-message.validation.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { Page } from '@playwright/test'; -import { pftDebugLog, reportValidationFailure } from '../../common/pft-debug-log'; +import { reportValidationFailure } from '../../common/pft-debug-log'; import { IValidation, validationData, validationRecord } from '../../interfaces'; type ValidationResult = { @@ -218,8 +218,8 @@ export class ErrorMessageValidation implements IValidation { const totalPages = ErrorMessageValidation.pagesWithEMV.size + ErrorMessageValidation.missingEMVFiles.size; if (totalPages === 0) { - pftDebugLog(`\n📊 ERROR MESSAGE VALIDATION (Test #${ErrorMessageValidation.testCounter}):`); - pftDebugLog(' No pages checked for error message validation'); + console.log(`\n📊 ERROR MESSAGE VALIDATION (Test #${ErrorMessageValidation.testCounter}):`); + console.log(' No pages checked for error message validation'); return; } @@ -247,45 +247,45 @@ export class ErrorMessageValidation implements IValidation { passedPages.delete(pageName); } - pftDebugLog(`\n📊 ERROR MESSAGE VALIDATION SUMMARY (Test #${ErrorMessageValidation.testCounter}):`); - pftDebugLog(` Total pages validated for error messages: ${totalPages}`); - pftDebugLog(` Number of pages passed: ${passedPages.size}`); - pftDebugLog(` Number of pages failed: ${failedPages.size}`); - pftDebugLog(` Number of missing EMV methods: ${ErrorMessageValidation.missingEMVFiles.size}`); + console.log(`\n📊 ERROR MESSAGE VALIDATION SUMMARY (Test #${ErrorMessageValidation.testCounter}):`); + console.log(` Total pages validated for error messages: ${totalPages}`); + console.log(` Number of pages passed: ${passedPages.size}`); + console.log(` Number of pages failed: ${failedPages.size}`); + console.log(` Number of missing EMV methods: ${ErrorMessageValidation.missingEMVFiles.size}`); if (passedPages.size > 0) { - pftDebugLog(` Passed pages: ${Array.from(passedPages).join(', ')}`); + console.log(` Passed pages: ${Array.from(passedPages).join(', ')}`); } if (failedPages.size > 0) { - pftDebugLog(` Failed pages: ${Array.from(failedPages).join(', ')}`); + console.log(` Failed pages: ${Array.from(failedPages).join(', ')}`); } if (ErrorMessageValidation.missingEMVFiles.size > 0) { - pftDebugLog(` EMV methods not found: ${Array.from(ErrorMessageValidation.missingEMVFiles).join(', ')}`); + console.log(` EMV methods not found: ${Array.from(ErrorMessageValidation.missingEMVFiles).join(', ')}`); } // Show failure details if (failedPages.size > 0) { - pftDebugLog('\n❌ FAILED ERROR MESSAGE VALIDATIONS:'); + console.log('\n❌ FAILED ERROR MESSAGE VALIDATIONS:'); for (const pageName of Array.from(failedPages).sort()) { const details = failureDetails.get(pageName); - pftDebugLog(` Page: ${pageName}`); + console.log(` Page: ${pageName}`); if (details) { - pftDebugLog(` Expected: ${details.expected}`); - pftDebugLog(` Actual: ${details.actual}`); + console.log(` Expected: ${details.expected}`); + console.log(` Actual: ${details.actual}`); } - pftDebugLog(''); + console.log(''); } } if (failedPages.size > 0) { - pftDebugLog('❌ ERROR MESSAGE VALIDATIONS FAILED\n'); + console.log('❌ ERROR MESSAGE VALIDATIONS FAILED\n'); } else if (passedPages.size > 0) { - pftDebugLog('\n✅ ALL ERROR MESSAGE VALIDATIONS PASSED\n'); + console.log('\n✅ ALL ERROR MESSAGE VALIDATIONS PASSED\n'); } else if (ErrorMessageValidation.pagesWithEMV.size > 0) { - pftDebugLog('\n⚠️ EMV methods found but no validations performed\n'); + console.log('\n⚠️ EMV methods found but no validations performed\n'); } ErrorMessageValidation.clearResults(); diff --git a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts index f7f499700..7c8da7601 100644 --- a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { Page } from '@playwright/test'; -import { attachValidationFailureScreenshot, pftDebugLog, reportValidationFailure } from '../../common/pft-debug-log'; +import { attachValidationFailureScreenshot, reportValidationFailure } from '../../common/pft-debug-log'; import { escapeForRegex, exactTextWithOptionalWhitespaceRegex } from '../../common/string.utils'; import { IValidation } from '../../interfaces'; @@ -283,40 +283,40 @@ export class PageContentValidation implements IValidation { const failedCount = failedPages.size; const missingFilesCount = PageContentValidation.missingDataFiles.size; - pftDebugLog(`\n📊 PAGE CONTENT VALIDATION SUMMARY (Test #${PageContentValidation.testCounter}):`); - pftDebugLog(` Total pages validated: ${totalValidated}`); - pftDebugLog(` Number of pages passed: ${passedCount}`); - pftDebugLog(` Number of pages failed: ${failedCount}`); - pftDebugLog(` Missing data files: ${missingFilesCount}`); + console.log(`\n📊 PAGE CONTENT VALIDATION SUMMARY (Test #${PageContentValidation.testCounter}):`); + console.log(` Total pages validated: ${totalValidated}`); + console.log(` Number of pages passed: ${passedCount}`); + console.log(` Number of pages failed: ${failedCount}`); + console.log(` Missing data files: ${missingFilesCount}`); if (passedCount > 0) { - pftDebugLog(` Passed pages: ${Array.from(passedPages).join(', ')}`); + console.log(` Passed pages: ${Array.from(passedPages).join(', ')}`); } if (failedCount > 0) { - pftDebugLog(` Failed pages: ${Array.from(failedPages.keys()).join(', ')}`); + console.log(` Failed pages: ${Array.from(failedPages.keys()).join(', ')}`); } if (missingFilesCount > 0) { - pftDebugLog(` Page files not found: ${Array.from(PageContentValidation.missingDataFiles).join(', ')}`); + console.log(` Page files not found: ${Array.from(PageContentValidation.missingDataFiles).join(', ')}`); } if (failedPages.size > 0) { - pftDebugLog('\n❌ FAILED PAGE CONTENT VALIDATION:'); + console.log('\n❌ FAILED PAGE CONTENT VALIDATION:'); for (const [pageName, pageFailures] of failedPages) { - pftDebugLog(` Page: ${pageName}`); + console.log(` Page: ${pageName}`); let pageFailureCount = 0; for (const [elementType, elements] of pageFailures) { pageFailureCount += elements.length; - pftDebugLog(` ${elementType} elements (${elements.length}):`); - elements.forEach(element => pftDebugLog(` - ${element}`)); + console.log(` ${elementType} elements (${elements.length}):`); + elements.forEach(element => console.log(` - ${element}`)); } - pftDebugLog(` Total missing on this page: ${pageFailureCount}\n`); + console.log(` Total missing on this page: ${pageFailureCount}\n`); } } else if (totalValidated > 0) { - pftDebugLog('\n✅ VALIDATION PASSED: All intended pages validated successfully!\n'); + console.log('\n✅ VALIDATION PASSED: All intended pages validated successfully!\n'); } else if (missingFilesCount > 0) { - pftDebugLog('\n⚠️ NO VALIDATION: Missing data files for all pages\n'); + console.log('\n⚠️ NO VALIDATION: Missing data files for all pages\n'); } PageContentValidation.clearValidationResults(); diff --git a/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts b/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts index e931bb162..d2de7984f 100644 --- a/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageNavigation.validation.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { Page, expect } from '@playwright/test'; -import { pftDebugLog, reportValidationFailure } from '../../common/pft-debug-log'; +import { reportValidationFailure } from '../../common/pft-debug-log'; import { performAction } from '../../controller'; import { IValidation, validationRecord } from '../../interfaces'; @@ -387,8 +387,8 @@ export class PageNavigationValidation implements IValidation { PageNavigationValidation.missingNavigationFiles.size; if (totalPages === 0) { - pftDebugLog(`\n📊 NAVIGATION TESTS (Test #${PageNavigationValidation.testCounter}):`); - pftDebugLog(' No pages checked for navigation tests'); + console.log(`\n📊 NAVIGATION TESTS (Test #${PageNavigationValidation.testCounter}):`); + console.log(' No pages checked for navigation tests'); return; } @@ -417,7 +417,7 @@ export class PageNavigationValidation implements IValidation { }); } } else { - pftDebugLog(` ⚠️ Unattributed failure on ${result.pageName}: ${result.error}`); + console.log(` ⚠️ Unattributed failure on ${result.pageName}: ${result.error}`); } } } @@ -439,57 +439,57 @@ export class PageNavigationValidation implements IValidation { } } - pftDebugLog(`\n📊 NAVIGATION TESTS SUMMARY (Test #${PageNavigationValidation.testCounter}):`); - pftDebugLog(` Total pages with navigation tests: ${totalPages}`); - pftDebugLog(` Number of pages passed: ${passedPages.size}`); - pftDebugLog(` Number of pages failed: ${failedPages.size}`); - pftDebugLog( + console.log(`\n📊 NAVIGATION TESTS SUMMARY (Test #${PageNavigationValidation.testCounter}):`); + console.log(` Total pages with navigation tests: ${totalPages}`); + console.log(` Number of pages passed: ${passedPages.size}`); + console.log(` Number of pages failed: ${failedPages.size}`); + console.log( ` Missing navigation methods: ${PageNavigationValidation.missingNavigationMethods.size + PageNavigationValidation.missingNavigationFiles.size}` ); if (passedPages.size > 0) { - pftDebugLog(` Passed pages: ${Array.from(passedPages).join(', ')}`); + console.log(` Passed pages: ${Array.from(passedPages).join(', ')}`); } if (failedPages.size > 0) { - pftDebugLog(` Failed pages: ${Array.from(failedPages).join(', ')}`); + console.log(` Failed pages: ${Array.from(failedPages).join(', ')}`); } if (PageNavigationValidation.missingNavigationMethods.size > 0) { - pftDebugLog( + console.log( ` Navigation methods not found: ${Array.from(PageNavigationValidation.missingNavigationMethods).join(', ')}` ); } if (PageNavigationValidation.missingNavigationFiles.size > 0) { - pftDebugLog( + console.log( ` Navigation files not found: ${Array.from(PageNavigationValidation.missingNavigationFiles).join(', ')}` ); } if (failedPages.size > 0) { - pftDebugLog('\n❌ FAILED NAVIGATION TESTS:'); + console.log('\n❌ FAILED NAVIGATION TESTS:'); for (const pageName of Array.from(failedPages).sort()) { const details = failureDetails.get(pageName); - pftDebugLog(` Page: ${pageName}`); + console.log(` Page: ${pageName}`); if (details) { - pftDebugLog(` Expected: ${details.expected}`); - pftDebugLog(` Actual: ${details.actual}`); + console.log(` Expected: ${details.expected}`); + console.log(` Actual: ${details.actual}`); if (details.validationType === 'url') { - pftDebugLog(` Note: Page slug URL validation failed`); + console.log(` Note: Page slug URL validation failed`); } } - pftDebugLog(''); + console.log(''); } } if (failedPages.size > 0) { - pftDebugLog('❌ NAVIGATION TESTS FAILED\n'); + console.log('❌ NAVIGATION TESTS FAILED\n'); } else if (passedPages.size > 0) { - pftDebugLog('\n✅ ALL NAVIGATION TESTS PASSED\n'); + console.log('\n✅ ALL NAVIGATION TESTS PASSED\n'); } else if (pagesWithNavigationMethods.size > 0) { - pftDebugLog('\n⚠️ Navigation files found but no tests performed\n'); + console.log('\n⚠️ Navigation files found but no tests performed\n'); } PageNavigationValidation.clearResults(); From 80965e4461c21a097444a6bfef2d7c558c6e4a96 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 23:14:34 +0100 Subject: [PATCH 10/12] Added few failures --- src/test/ui/utils/common/pft-debug-log.ts | 29 ++++--------------- .../pageContent.validation.ts | 2 -- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/src/test/ui/utils/common/pft-debug-log.ts b/src/test/ui/utils/common/pft-debug-log.ts index 9707bd576..660d02381 100644 --- a/src/test/ui/utils/common/pft-debug-log.ts +++ b/src/test/ui/utils/common/pft-debug-log.ts @@ -6,23 +6,11 @@ import { test } from '@playwright/test'; import { enable_pft_debug_log } from '../../../../../playwright.config'; -/** - * Console: `[PFT check: …]` lines only when `ENABLE_PFT_DEBUG_LOG=true` (`enable_pft_debug_log`). - * Failure PNGs: `test.info().attach` from `attachValidationFailureScreenshot` and from - * `reportValidationFailure(..., attachScreenshot: true)` — not gated by `ENABLE_PFT_DEBUG_LOG` - * (Allure/HTML report pick up attachments from test results). - */ +// PFT console output respects ENABLE_PFT_DEBUG_LOG. Failure screenshots attach to the report regardless. -/** - * Shallow snapshot of `process.env` at the **start** of `test.beforeEach` (before any assignments). - * Used by `logTestBeforeEachContext()` to log only keys **added or changed** during that hook. - */ let envSnapshotBeforeBeforeEach: NodeJS.ProcessEnv | null = null; -/** - * Call as the **first** line of `test.beforeEach` (before `initializeExecutor` / any `process.env` writes). - * Pairs with `logTestBeforeEachContext()` at the end of the same hook. - */ +/** First line of `test.beforeEach`; pairs with `logTestBeforeEachContext` at the end. */ export function captureProcessEnvBeforeBeforeEach(): void { envSnapshotBeforeBeforeEach = { ...process.env }; } @@ -45,12 +33,7 @@ function envKeysChangedDuringBeforeEach(): string[] { return keys.sort(); } -/** - * Call at the **end** of `test.beforeEach` (after `process.env` / case creation is done). - * Prints one block only when `ENABLE_PFT_DEBUG_LOG=true`. - * Logs every **non-empty** `process.env` key whose value **differs** from the snapshot taken by - * `captureProcessEnvBeforeBeforeEach()` at the start of this `beforeEach`. - */ +/** End of `test.beforeEach`: logs env keys changed since `captureProcessEnvBeforeBeforeEach` (flag-gated). */ export function logTestBeforeEachContext(): void { if (enable_pft_debug_log !== 'true') { return; @@ -83,9 +66,7 @@ const validationLabel: Record = { 'page-navigation': 'page navigation', }; -/** - * Full-page screenshot attached to the test report (Allure / HTML). Not gated by `ENABLE_PFT_DEBUG_LOG`. - */ +/** Full-page screenshot → test report attachment. */ export async function attachValidationFailureScreenshot( page: Page, category: ValidationFailureCategory, @@ -110,6 +91,7 @@ export async function attachValidationFailureScreenshot( } } +/** Screenshot if `attachScreenshot`; console lines only when ENABLE_PFT_DEBUG_LOG. */ export async function reportValidationFailure( page: Page, category: ValidationFailureCategory, @@ -125,6 +107,7 @@ export async function reportValidationFailure( pftDebugReport({ page, pageLabel, category: label, expected, actual }); } +/** Structured `[PFT check: …]` console output (flag-gated). */ export function pftDebugReport(options: { page: Page; pageLabel: string; diff --git a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts index 7c8da7601..ca96487bd 100644 --- a/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts +++ b/src/test/ui/utils/validations/custom-validations/pageContent.validation.ts @@ -140,8 +140,6 @@ export class PageContentValidation implements IValidation { const pageData = await this.getPageData(pageName); if (!pageData) { - // Missing file is already handled in triggerPageFunctionalTests (trackMissingDataFile; no runPageContentValidation). - // Here we only surface load/parse/export failures when the file exists. const pageDataPath = path.join(__dirname, '../../../data/page-data', `${pageName}.page.data.ts`); if (fs.existsSync(pageDataPath)) { await reportValidationFailure( From 6ae210c5a8f6580fca4c0361dd8723c85df9c4e2 Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 23:40:28 +0100 Subject: [PATCH 11/12] Added few failures --- .../triggerPageFunctionalTests.action.ts | 11 +- src/test/ui/utils/common/pft-debug-log.ts | 111 ++++++++---------- src/test/ui/utils/common/string.utils.ts | 13 ++ 3 files changed, 68 insertions(+), 67 deletions(-) diff --git a/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts b/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts index 190727681..0554a51f6 100644 --- a/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts +++ b/src/test/ui/utils/actions/custom-actions/triggerPageFunctionalTests.action.ts @@ -9,7 +9,8 @@ import { enable_navigation_tests, enable_pft_debug_log, } from '../../../../../../playwright.config'; -import { pftDebugReport, shortUrl } from '../../common/pft-debug-log'; +import { logUnmappedPftUrl } from '../../common/pft-debug-log'; +import { shortUrl } from '../../common/string.utils'; import { IAction } from '../../interfaces'; import { ErrorMessageValidation, @@ -40,13 +41,7 @@ export class TriggerPageFunctionalTestsAction implements IAction { console.log(`[triggerFunctionalTests] entered url=${shortUrl(page.url())} page=${pageName ?? '(unmapped)'}`); } if (!pageName) { - pftDebugReport({ - page, - pageLabel: shortUrl(page.url()), - category: 'page functional tests', - expected: 'A matching key in urlToFileMapping.config.ts', - actual: 'No matching key — PFT skipped', - }); + logUnmappedPftUrl(page, shortUrl(page.url())); return; } diff --git a/src/test/ui/utils/common/pft-debug-log.ts b/src/test/ui/utils/common/pft-debug-log.ts index 660d02381..ab4a73457 100644 --- a/src/test/ui/utils/common/pft-debug-log.ts +++ b/src/test/ui/utils/common/pft-debug-log.ts @@ -6,80 +6,63 @@ import { test } from '@playwright/test'; import { enable_pft_debug_log } from '../../../../../playwright.config'; -// PFT console output respects ENABLE_PFT_DEBUG_LOG. Failure screenshots attach to the report regardless. +import { shortUrl, truncateForLog } from './string.utils'; -let envSnapshotBeforeBeforeEach: NodeJS.ProcessEnv | null = null; +// PFT: optional console when ENABLE_PFT_DEBUG_LOG; screenshots always on failure paths. -/** First line of `test.beforeEach`; pairs with `logTestBeforeEachContext` at the end. */ -export function captureProcessEnvBeforeBeforeEach(): void { - envSnapshotBeforeBeforeEach = { ...process.env }; -} +let envBeforeBeforeEach: NodeJS.ProcessEnv | null = null; -function envKeysChangedDuringBeforeEach(): string[] { - const before = envSnapshotBeforeBeforeEach; - if (!before) { - return []; - } - const keys: string[] = []; - for (const k of Object.keys(process.env)) { - const v = process.env[k]; - if (v === undefined || v === '') { - continue; - } - if (before[k] !== v) { - keys.push(k); - } - } - return keys.sort(); +export function captureProcessEnvBeforeBeforeEach(): void { + envBeforeBeforeEach = { ...process.env }; } -/** End of `test.beforeEach`: logs env keys changed since `captureProcessEnvBeforeBeforeEach` (flag-gated). */ export function logTestBeforeEachContext(): void { if (enable_pft_debug_log !== 'true') { return; } - const { title } = test.info(); - const changedKeys = envKeysChangedDuringBeforeEach(); - const lines = changedKeys.map(key => ` ${key}=${process.env[key]}`); + + 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') - : envSnapshotBeforeBeforeEach + : before ? ' (no process.env keys were added or changed during this beforeEach)' : ' (call captureProcessEnvBeforeBeforeEach() at the start of beforeEach to log env changes)'; - console.log(['[PFT debug: beforeEach context]', ` test: ${truncateForLog(title, 200)}`, body].join('\n')); -} -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)}…`; + const { title } = test.info(); + console.log(['[PFT debug: beforeEach context]', ` test: ${truncateForLog(title, 200)}`, body].join('\n')); } -export const shortUrl = (u: string, max = 88) => truncate(u, max); -export const truncateForLog = (s: string, max = 800) => truncate(s, max, true); - export type ValidationFailureCategory = 'page-content' | 'error-messages' | 'page-navigation'; -const validationLabel: Record = { +const categoryLabel: Record = { 'page-content': 'page content', 'error-messages': 'error messages', 'page-navigation': 'page navigation', }; -/** Full-page screenshot → test report attachment. */ export async function attachValidationFailureScreenshot( page: Page, category: ValidationFailureCategory, pageLabel: string ): Promise { - const debugLabel = validationLabel[category]; + const label = categoryLabel[category]; try { const safe = (pageLabel.trim() || 'page').slice(0, 80); - const fileName = `failure-${category}-${safe}-${Date.now()}.png`; - const out = test.info().outputPath('validation-failures', fileName); + 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 (${debugLabel}): ${safe}`, { + await test.info().attach(`Validation failure (${label}): ${safe}`, { path: out, contentType: 'image/png', }); @@ -91,7 +74,6 @@ export async function attachValidationFailureScreenshot( } } -/** Screenshot if `attachScreenshot`; console lines only when ENABLE_PFT_DEBUG_LOG. */ export async function reportValidationFailure( page: Page, category: ValidationFailureCategory, @@ -100,14 +82,30 @@ export async function reportValidationFailure( actual: string, attachScreenshot: boolean ): Promise { - const label = validationLabel[category]; if (attachScreenshot) { await attachValidationFailureScreenshot(page, category, pageLabel); } - pftDebugReport({ page, pageLabel, category: label, expected, actual }); + 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'); } -/** Structured `[PFT check: …]` console output (flag-gated). */ export function pftDebugReport(options: { page: Page; pageLabel: string; @@ -115,19 +113,14 @@ export function pftDebugReport(options: { expected: string; actual: string; }): void { - if (enable_pft_debug_log !== 'true') { - return; - } + if (enable_pft_debug_log !== 'true') {return;} const { page, pageLabel, category, expected, actual } = options; - const tag = `[PFT check: ${category}]`; - const testTitle = test.info().title; - console.log( - [ - `${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') - ); + 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)); } diff --git a/src/test/ui/utils/common/string.utils.ts b/src/test/ui/utils/common/string.utils.ts index 82061ac85..ff617a1de 100644 --- a/src/test/ui/utils/common/string.utils.ts +++ b/src/test/ui/utils/common/string.utils.ts @@ -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); +} From 27101864e9bb7f207332788f19beeb75924b66fb Mon Sep 17 00:00:00 2001 From: SameenaHMCTS Date: Mon, 30 Mar 2026 23:40:55 +0100 Subject: [PATCH 12/12] Added few failures --- src/test/ui/utils/common/pft-debug-log.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/ui/utils/common/pft-debug-log.ts b/src/test/ui/utils/common/pft-debug-log.ts index ab4a73457..617f9a9ea 100644 --- a/src/test/ui/utils/common/pft-debug-log.ts +++ b/src/test/ui/utils/common/pft-debug-log.ts @@ -113,7 +113,9 @@ export function pftDebugReport(options: { expected: string; actual: string; }): void { - if (enable_pft_debug_log !== 'true') {return;} + if (enable_pft_debug_log !== 'true') { + return; + } const { page, pageLabel, category, expected, actual } = options; console.log(formatPftCheck(page, pageLabel, category, expected, actual)); }