diff --git a/src/e2eTest/data/page-data-figma/page-data-common-component/globalSearch.page.data.ts b/src/e2eTest/data/page-data-figma/page-data-common-component/globalSearch.page.data.ts index c1c17affac..8af9e13556 100644 --- a/src/e2eTest/data/page-data-figma/page-data-common-component/globalSearch.page.data.ts +++ b/src/e2eTest/data/page-data-figma/page-data-common-component/globalSearch.page.data.ts @@ -18,14 +18,14 @@ export const globalSearch = otherReferenceInputText: 'Test reference', otherReferenceParagraph:'Any other reference to identify a case, for example National Insurance number, or system reference', nameLabel:'Name', - nameInputText: 'Smith', + nameInputText: 'Possession Claims Solicitor Org', nameParagraph: 'For example, name of a party or solicitor', firstLineOfAddressLabel: 'First line of address', firstLineOfAddressInputText: '2 Second Avenue', postCodeLabel: 'Postcode', postcodeInputText: 'W3 7RX', emailAddressLabel: 'Email address', - emailAddressInputText: 'test@test.com', + emailAddressInputText: 'pcs-solicitor1@test.com', dateOfBirthLabel: 'Date of birth', dateOfBirthDayLabel: 'Day', dateOfBirthMonthLabel: 'Month', @@ -46,5 +46,6 @@ export const globalSearch = searchButton: 'Search', invalidCaseReferenceInputText: '0000000000000000', invalidPostcodeInputText: 'ZZ99 9ZZ', - invalidNameInputText: 'XYZ_NonExistent_Name_ABC' + invalidNameInputText: 'XYZ_NonExistent_Name_ABC', + nextLink: 'Next page' } diff --git a/src/e2eTest/tests/CommonComponent/globalSearch.spec.ts b/src/e2eTest/tests/CommonComponent/globalSearch.spec.ts index a57f66a2c2..eefd83f10f 100644 --- a/src/e2eTest/tests/CommonComponent/globalSearch.spec.ts +++ b/src/e2eTest/tests/CommonComponent/globalSearch.spec.ts @@ -1,4 +1,5 @@ import { test } from '@utils/test-fixtures'; +import { expect, Page, BrowserContext } from '@playwright/test'; import { initializeExecutor, performAction, performValidation } from '@utils/controller'; import { globalSearch} from '@data/page-data-figma'; import { dismissCookieBanner } from '@config/cookie-banner'; @@ -9,8 +10,8 @@ import { user} from '@data/page-data'; test.use({ storageState: undefined }); const setupGlobalSearchUser = async ( - page: any, - context: any, + page: Page, + context: BrowserContext, loggedInUser: typeof user.ctscAdministrator ) => { await context.clearCookies(); @@ -43,6 +44,14 @@ test.afterEach(async () => { } }); +const runFieldSearch = async (label: string, value: string) => { + await performAction('accessingTheSearch'); + await performAction('inputText', label, value); + await performAction('select', globalSearch.servicesLabel, globalSearch.servicesDropdownOption2); + await performAction('executeSearch'); + await performAction('validateResultsWithRetry'); +}; + const runGlobalSearchScenarios = () => { test('Global search menu @smoke', async () => { await performAction('accessingTheSearch'); @@ -51,16 +60,32 @@ const runGlobalSearchScenarios = () => { test('Valid case reference using Mortgage and Landlord Possession Claim Service', async () => { await performAction('accessingTheSearch'); - await performAction('searchByCaseReference', process.env.CASE_NUMBER); + await performAction('searchByCaseReference', process.env.CASE_NUMBER, globalSearch.servicesDropdownOption2); await performAction('validateResults'); }); test('Valid case reference using All Services', async () => { await performAction('accessingTheSearch'); - await performAction('searchByCaseReference', process.env.CASE_NUMBER); + await performAction('searchByCaseReference', process.env.CASE_NUMBER, globalSearch.servicesDropdownOption1); await performAction('validateResults'); }); + test('Search by postcode', async () => { + await runFieldSearch(globalSearch.postCodeLabel, globalSearch.postcodeInputText); + }); + + test('Search by email address', async () => { + await runFieldSearch(globalSearch.emailAddressLabel, globalSearch.emailAddressInputText); + }); + + test('first line of address', async () => { + await runFieldSearch(globalSearch.firstLineOfAddressLabel, globalSearch.firstLineOfAddressInputText); + }); + + test('Search by party name', async () => { + await runFieldSearch(globalSearch.nameLabel, globalSearch.nameInputText); + }); + test('Invalid case reference', async () => { await performAction('accessingTheSearch'); await performAction('invalidCaseReferenceSearch', globalSearch.invalidCaseReferenceInputText); @@ -75,9 +100,9 @@ const runGlobalSearchScenarios = () => { [ { roleName: 'CTSC User', account: user.ctscAdministrator }, - { roleName: 'Judge', account: user.judge } + { roleName: 'Judge User', account: user.judge } ].forEach(({ roleName, account }) => { - test.describe(`[Global Search - ${roleName} - @nightly @CC @caseFlags]`, () => { + test.describe(`[Common Component Global Search] - ${roleName} - @nightly @CC @caseFlags`, () => { test.beforeEach(async ({ page, context }) => { await setupGlobalSearchUser(page, context, account); }); diff --git a/src/e2eTest/utils/actions/custom-actions/commonComponent/globalSearch.action.ts b/src/e2eTest/utils/actions/custom-actions/commonComponent/globalSearch.action.ts index 2fe1d6e8e1..bfe2270d32 100644 --- a/src/e2eTest/utils/actions/custom-actions/commonComponent/globalSearch.action.ts +++ b/src/e2eTest/utils/actions/custom-actions/commonComponent/globalSearch.action.ts @@ -1,18 +1,21 @@ -import { expect, Page } from '@playwright/test'; +import { expect, Locator, Page } from '@playwright/test'; import { performAction, performValidation } from '../../../controller'; import { actionRecord, IAction } from '@utils/interfaces'; import { globalSearch, noResultFound, searchResults, workAccess } from '@data/page-data-figma'; import { caseList, home } from '@data/page-data'; export class GlobalSearchCaseAction implements IAction { - async execute(page: Page, action: string, fieldName: string | actionRecord): Promise { + async execute(page: Page, action: string, fieldName: string | actionRecord, value?: string | actionRecord): Promise { const actionsMap = new Map Promise>([ ['accessingTheSearch', () => this.accessingTheSearch(page)], - ['searchByCaseReference', () => this.searchByCaseReference(fieldName as string, page)], - ['invalidCaseReferenceSearch', () => this.invalidCaseReferenceSearch(fieldName as string, page)], - ['changeSearchLink', () => this.changeSearchLink(fieldName as string, page)], + ['searchByCaseReference', () => this.searchByCaseReference(fieldName as string, page, value as string | undefined)], + ['invalidCaseReferenceSearch', () => this.invalidCaseReferenceSearch(page)], + ['changeSearchLink', () => this.changeSearchLink(page)], ['handleJudgeBookingPage', () => this.handleJudgeBookingPage(page)], - ['validateResults', () => this.validateResults(page)] + ['submitGlobalSearch', () => this.submitGlobalSearch(page)], + ['executeSearch', () => this.executeSearch(page)], + ['validateResults', () => this.validateResults(page)], + ['validateResultsWithRetry', () => this.validateResultsWithRetry(page)] ]); const actionToPerform = actionsMap.get(action); @@ -24,23 +27,21 @@ export class GlobalSearchCaseAction implements IAction { await performAction('clickButton', home.globalSearchTab); } - private async searchByCaseReference(caseReference: string, page: Page): Promise { + private async searchByCaseReference(caseReference: string, page: Page, serviceOption?: string): Promise { await performAction('inputText', globalSearch.DigitCaseReferenceLabel, caseReference); - await performAction('select', globalSearch.servicesLabel, globalSearch.servicesDropdownOption2); - await performAction('clickButton', globalSearch.searchButton); - await page.locator('button[type="submit"]').click(); + await performAction('select', globalSearch.servicesLabel, serviceOption ?? globalSearch.servicesDropdownOption2); + await this.executeSearch(page); await performValidation('mainHeader', searchResults.mainHeader); } - private async invalidCaseReferenceSearch(invalidCaseReference: string, page: Page): Promise { + private async invalidCaseReferenceSearch(page: Page): Promise { await performAction('inputText', globalSearch.DigitCaseReferenceLabel, globalSearch.invalidCaseReferenceInputText); await performAction('select', globalSearch.servicesLabel, globalSearch.servicesDropdownOption1); - await performAction('clickButton', globalSearch.searchButton); - await page.locator('button[type="submit"]').click(); + await this.executeSearch(page); await performValidation('mainHeader', noResultFound.mainHeader); } - private async changeSearchLink(changeSearch: string, page: Page): Promise { + private async changeSearchLink(page: Page): Promise { await performAction('clickLink', searchResults.changeSearchLink); await performValidation('mainHeader', globalSearch.mainHeader); } @@ -53,15 +54,27 @@ export class GlobalSearchCaseAction implements IAction { await performValidation('mainHeader', caseList.mainHeader); } + private async submitGlobalSearch(page: Page): Promise { + await page.locator('button[type="submit"]').click(); + } + + private async executeSearch(page: Page): Promise { + await performAction('clickButton', globalSearch.searchButton); + await this.submitGlobalSearch(page); + } + private async validateResults(page: Page): Promise { const caseReference = String(process.env.CASE_NUMBER ?? ''); - const resultRow = page.locator('tr').filter({ hasText: caseReference }); - const caseCell = resultRow.locator('td').first(); + const normalizedCaseReference = caseReference.replace(/\D/g, ''); await expect(page.getByRole('heading', { name: searchResults.mainHeader })).toBeVisible(); - await expect(resultRow).toContainText(caseReference); - await expect(caseCell).toContainText(caseReference); + + const resultRow = await this.findCaseReferenceRowAcrossPages(page, normalizedCaseReference); + const caseCell = resultRow.locator('td').first(); const caseCellText = (await caseCell.innerText()).trim(); - const caseNameText = caseCellText.replace(caseReference, '').trim(); + const normalizedCellText = caseCellText.replace(/\D/g, ''); + const formattedCaseReference = caseReference.replace(/(\d{4})(?=\d)/g, '$1-'); + expect(normalizedCellText).toContain(normalizedCaseReference); + const caseNameText = caseCellText.replace(caseReference, '').replace(formattedCaseReference, '').trim(); expect(caseNameText.length).toBeGreaterThan(0); await expect(resultRow).toContainText(searchResults.serviceLabel); await expect(resultRow).toContainText(searchResults.stateLabel); @@ -71,4 +84,60 @@ export class GlobalSearchCaseAction implements IAction { await performAction('clickTab', home.caseSummary); await performValidation('mainHeader', home.caseSummary); } + + private async validateResultsWithRetry(page: Page): Promise { + const maxRetries = 6; + + for (let retryCount = 0; retryCount < maxRetries; retryCount++) { + try { + await this.validateResults(page); + return; + } catch (error: any) { + const shouldRetry = String(error?.message ?? '').includes( + 'was not found on any paginated search result page' + ); + + if (!shouldRetry || retryCount === maxRetries - 1) { + throw error; + } + + await page.reload({ waitUntil: 'domcontentloaded' }); + await expect( + page.getByRole('heading', { name: searchResults.mainHeader }) + ).toBeVisible(); + } + } + } + + private async findCaseReferenceRowAcrossPages(page: Page, normalizedCaseReference: string): Promise { + const maxPagesToScan = 50; + const rows = page.locator('tbody tr'); + + for (let pageIndex = 0; pageIndex < maxPagesToScan; pageIndex++) { + const rowCount = await rows.count(); + for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) { + const row = rows.nth(rowIndex); + const firstCellText = (await row.locator('td').first().innerText()).trim(); + const normalizedRowCaseReference = firstCellText.replace(/\D/g, ''); + if (normalizedRowCaseReference.includes(normalizedCaseReference)) { + return row; + } + } + + const nextPageLink = page.getByRole('link', { name: globalSearch.nextLink, exact: true }); + if ((await nextPageLink.count()) === 0 || !(await nextPageLink.isVisible())) { + break; + } + + const currentPageMarker = page.locator('.hmcts-pagination__item--current').first(); + const previousMarkerText = (await currentPageMarker.count()) > 0 ? (await currentPageMarker.innerText()).trim() : ''; + await nextPageLink.click(); + if (previousMarkerText) { + await expect(currentPageMarker).not.toHaveText(previousMarkerText, { timeout: 10000 }); + } + await expect(rows.first()).toBeVisible({ timeout: 10000 }); + } + + throw new Error(`Case reference '${normalizedCaseReference}' was not found on any paginated search result page.`); + } } \ No newline at end of file diff --git a/src/e2eTest/utils/registry/action.registry.ts b/src/e2eTest/utils/registry/action.registry.ts index 250632af3c..41d6c84551 100644 --- a/src/e2eTest/utils/registry/action.registry.ts +++ b/src/e2eTest/utils/registry/action.registry.ts @@ -97,8 +97,10 @@ export class ActionRegistry { ['searchByCaseReference', new GlobalSearchCaseAction()], ['invalidCaseReferenceSearch', new GlobalSearchCaseAction()], ['changeSearchLink', new GlobalSearchCaseAction()], - ['handleJudgeBookingPage', new GlobalSearchCaseAction()], + ['submitGlobalSearch', new GlobalSearchCaseAction()], + ['executeSearch', new GlobalSearchCaseAction()], ['validateResults', new GlobalSearchCaseAction()], + ['validateResultsWithRetry', new GlobalSearchCaseAction()], ['selectClaimingCosts', new CreateCaseAction()], ['wantToUploadDocuments', new CreateCaseAction()], ['uploadAdditionalDocs', new CreateCaseAction()],